image.rs 4.7 KB
use std::convert::TryFrom;
use std::sync::Arc;

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

#[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
}

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)
        }
    }
}

#[macro_export]
macro_rules! upload_uuid {
    ($u:expr) => {
        match &$u.upload_uuid {
            Some(uuid) => $crate::uuid::Uuid::try_from(uuid.as_slice()).ok(),
            None       => None,
        }
    };
}

#[macro_export]
macro_rules! upload_filename {
    ($u:expr) => {
        $crate::upload_uuid!($u)
            . and_then(|uuid| Some(format!( "{}/upload_{}"
                                          , $crate::config::CONFIG.upload_dir()
                                          , uuid )))
    };
}


impl Image {
    pub(crate) fn path(&self, size :Size) -> String {
        let uuid = Uuid::try_from( self.uuid
                                 . as_ref()
                                 . unwrap()
                                 . as_slice() ).unwrap();
        let uuid_string = format!("{}", uuid);

        let mut image_path = PathBuf::from(CONFIG.images_dir());
        image_path.push(&uuid_string.as_str()[..1]);
        image_path.push(&uuid_string.as_str()[..2]);
        image_path.push(&uuid_string.as_str()[..3]);
        image_path.push(&format!("{}_{}", &uuid_string, size));

        image_path.into_os_string().into_string().unwrap()
    }
}

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)? )
}