image.rs 7.34 KB
use std::convert::TryFrom;
use std::io::SeekFrom;
use std::sync::Arc;

use crate::error::*;
use crate::routes::image::Size;
use crate::uuid::Uuid;
use crate::{schema::*, Pool, config::CONFIG};
use actix_web::http::StatusCode;
use async_std::fs::File;
use async_std::io::prelude::SeekExt;
use async_std::path::PathBuf;
use diesel::{Connection, insert_into, delete, update};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};

use async_std::io::ReadExt;

#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Identifiable)]
pub struct Image {
    pub id           :i32,
    pub upload_uuid  :Option<Vec<u8>>,
    pub uuid         :Option<Vec<u8>>,
    pub size         :i32,
    pub dim_x        :Option<i32>,
    pub dim_y        :Option<i32>,
    pub mime_type    :String,
    pub date_created :String,
    pub date_updated :String
}

#[derive(Debug, Insertable)]
#[table_name = "images"]
pub struct ImageNew<'a> {
    pub upload_uuid  :Option<&'a [u8]>,
    pub size         :i32,
    pub mime_type    :&'a str,
    pub date_created :&'a str,
    pub date_updated :&'a str
}

#[derive(Clone, Debug, Serialize, Deserialize, AsChangeset)]
#[table_name = "images"]
pub struct Upload {
    pub upload_uuid :Option<Vec<u8>>,
    pub size        :i32,
    pub mime_type   :String,
}

#[derive(Clone, Debug, Serialize, Deserialize, AsChangeset)]
#[table_name = "images"]
#[changeset_options(treat_none_as_null = "true")]
pub struct ImagePatch {
    pub upload_uuid  :Option<Vec<u8>>,
    pub uuid         :Option<Vec<u8>>,
    pub size         :i32,
    pub dim_x        :Option<i32>,
    pub dim_y        :Option<i32>,
    pub mime_type    :String,
    pub date_updated :String
}

pub struct ImageContext {
    pub image       :Image,
    base_path   :Option<PathBuf>,
    uuid_string :Option<String>,
    upload_path :Option<PathBuf>
}

impl From<Image> for ImagePatch {
    fn from(image: Image) -> Self {
        let now = chrono::Local::now().naive_local();

        Self { upload_uuid  :image.upload_uuid
             , uuid         :image.uuid
             , size         :image.size
             , dim_x        :image.dim_x
             , dim_y        :image.dim_y
             , mime_type    :image.mime_type
             , date_updated :format!("{}", now)
        }
    }
}

async fn read_at( f   :&mut File
                , pos :SeekFrom
                , buf :&mut [u8]) -> std::io::Result<()> {
    f.seek(pos).await?;
    f.read_exact(buf).await
}

async fn get_sample( f   :&mut File
                   , buf :&mut [u8]) -> std::io::Result<()> {
    let file_len = f.metadata().await?.len();
    let chunk_size = buf.len() / 3;

    read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?;
    if file_len >= 2 * chunk_size as u64 {
        read_at( f
               , SeekFrom::End(-(chunk_size as i64))
               , &mut buf[2*chunk_size..]).await?;
    }
    if file_len >= 3 * chunk_size as u64 {
        read_at( f
               , SeekFrom::Start((file_len-chunk_size as u64) / 2)
               , &mut buf[chunk_size..2*chunk_size]).await?;
    }

    Ok(())
}

impl Image {
    pub(crate) fn context(self) -> ImageContext {
        ImageContext { image       :self
                     , base_path   :None
                     , uuid_string :None
                     , upload_path :None }
    }
}

impl ImageContext {
    pub(crate) async fn upload_path(&mut self) -> Option<&PathBuf> {
        if self.upload_path.is_none() {
            let uuid = Uuid::try_from(self.image.upload_uuid.clone()?).ok()?;
            let uuid_string = format!("{}", uuid);

            let mut upload_path = PathBuf::from(CONFIG.upload_dir());
            upload_path.push(&uuid_string);

            self.upload_path = Some(upload_path);
        }

        self.upload_path.as_ref()
    }

    async fn uuid_string(&mut self) -> Result<&String> {
        if self.uuid_string.is_none() {
            let uuid :Uuid;

            if let Some(u) = &self.image.uuid {
                uuid = Uuid::try_from(u.as_ref())?;
            } else {
                let path = self
                         . upload_path().await
                         . ok_or(Error::new( "No upload and no uuid"
                                           , StatusCode::INTERNAL_SERVER_ERROR ))?;

                let mut f = File::open(&path).await?;
                let mut buf = vec!['.' as u8; 3 * 3 * 4096];

                get_sample(&mut f, buf.as_mut()).await?;
                uuid = Uuid::get(CONFIG.namespace(), buf.as_mut());

                self.image.uuid = Some(uuid.0.as_bytes().to_vec());
                self.image.upload_uuid = None;
            }

            self.uuid_string = Some(format!("{}", uuid));
        }

        Ok(self.uuid_string.as_ref().unwrap())
    }

    pub(crate) async fn base_path(&mut self) -> Option<&PathBuf> {
        if self.upload_path.is_none() {
            let uuid_string = self.uuid_string().await.ok()?;

            let mut base_path = PathBuf::from(CONFIG.images_dir());
            base_path.push(&uuid_string.as_str()[..1]);
            base_path.push(&uuid_string.as_str()[..2]);
            base_path.push(&uuid_string.as_str()[..3]);

            self.base_path = Some(base_path);
        }

        self.base_path.as_ref()
    }

    pub(crate) async fn path(&mut self, size :Size) -> Option<PathBuf> {
        let mut path = self.base_path().await?.to_owned();
        path.push(&format!("{}_{}", &self.uuid_string().await.ok()?, size));

        Some(path)
    }
}

impl Upload {
    pub(crate) async fn upload_path(&mut self) -> Option<PathBuf> {
        let uuid = Uuid::try_from(self.upload_uuid.clone()?).ok()?;
        let uuid_string = format!("{}", uuid);

        let mut upload_path = PathBuf::from(CONFIG.upload_dir());
        upload_path.push(&uuid_string);

        Some(upload_path)
    }
}

pub(crate) fn upload( pool: Arc<Pool>
                    , item: Upload ) -> Result<Image> {
    use crate::schema::images::dsl::*;
    let db_connection = pool.get()?;

    let now = chrono::Local::now().naive_local();
    let new_image = ImageNew {
        upload_uuid  : item.upload_uuid.as_deref(),
        size         : item.size,
        mime_type    : &item.mime_type,
        date_created : &format!("{}", now),
        date_updated : &format!("{}", now)
    };

    Ok(db_connection.transaction(|| {
        insert_into(images) . values(&new_image)
                            . execute(&db_connection)?;
        images . order(id.desc())
               . first::<Image>(&db_connection)
    })?)
}

pub(crate) fn finalize( pool :Arc<Pool>
                      , item :Image ) -> Result<Image> {
    use crate::schema::images::dsl::*;

    let db_connection = pool.get()?;
    let item_uuid = item.uuid.clone();

    match images . filter(uuid.eq(item_uuid))
                 . first::<Image>(&db_connection) {
        Ok(image) => {
            delete(images.find(item.id)).execute(&db_connection)?;
            Ok(image)
        },
        Err(_) => {
            let image = images.find(item.id);
            let patch = ImagePatch::from(item.clone());
            update(image).set(&patch).execute(&db_connection)?;
            Ok(item)
        },
    }
}

pub(crate) fn get_image( pool  :Arc<Pool>
                       , ident :i32 ) -> Result<Image>
{
    use crate::schema::images::dsl::*;

    let db_connection = pool.get()?;

    Ok( images
      . filter(id.eq(ident))
      . first::<Image>(&db_connection)? )
}