Commit e64f5307fe63fe1592e15ba3cc43d9921e298fe6

Authored by Georg Hopp
1 parent ab684c91

Add images api end point

@@ -8,14 +8,14 @@ repository = "https://gitlab.weird-web-workers.org/rust/artshop" @@ -8,14 +8,14 @@ repository = "https://gitlab.weird-web-workers.org/rust/artshop"
8 license = "GPL-3.0-or-later" 8 license = "GPL-3.0-or-later"
9 9
10 [dependencies] 10 [dependencies]
11 -actix-files = "0.2"  
12 -actix-rt = "1.1.1"  
13 -actix-web = "2.0" 11 +actix-files = "^0.5"
  12 +actix-rt = "^1.1"
  13 +actix-web = "^3.3"
14 anyhow = "1.0" 14 anyhow = "1.0"
15 artshop-common = { path = "../common" } 15 artshop-common = { path = "../common" }
16 -async-std = "^1.10" 16 +async-std = { version = "^1.10", features = ["unstable"] }
17 chrono = "0.4.15" 17 chrono = "0.4.15"
18 -diesel = { version = "1.4.7", features = ["mysql", "sqlite", "r2d2"]} 18 +diesel = { version = "1.4.7", features = ["mysql", "sqlite", "r2d2"] }
19 diffy = "0.2" 19 diffy = "0.2"
20 dotenv = "0.15.0" 20 dotenv = "0.15.0"
21 flate2 = "^1.0" 21 flate2 = "^1.0"
@@ -24,6 +24,7 @@ futures-util = { version = "0", features = ["std"] } @@ -24,6 +24,7 @@ futures-util = { version = "0", features = ["std"] }
24 image = "^0.23" 24 image = "^0.23"
25 listenfd = "0.3" 25 listenfd = "0.3"
26 once_cell = "^1.9" 26 once_cell = "^1.9"
  27 +mime = "^0.3"
27 r2d2 = "0.8.9" 28 r2d2 = "0.8.9"
28 serde = { version = "^1.0", features = ["derive"] } 29 serde = { version = "^1.0", features = ["derive"] }
29 serde_derive = "1.0" 30 serde_derive = "1.0"
@@ -14,6 +14,8 @@ use routes::markdown::*; @@ -14,6 +14,8 @@ use routes::markdown::*;
14 use routes::other::*; 14 use routes::other::*;
15 use routes::user::*; 15 use routes::user::*;
16 use routes::upload::*; 16 use routes::upload::*;
  17 +use routes::image::*;
  18 +
17 19
18 use actix_web::{guard, web, App, HttpResponse, HttpServer}; 20 use actix_web::{guard, web, App, HttpResponse, HttpServer};
19 use async_std::channel::Sender; 21 use async_std::channel::Sender;
@@ -21,7 +23,6 @@ use diesel::r2d2::{self, ConnectionManager}; @@ -21,7 +23,6 @@ use diesel::r2d2::{self, ConnectionManager};
21 use diesel::MysqlConnection; 23 use diesel::MysqlConnection;
22 use listenfd::ListenFd; 24 use listenfd::ListenFd;
23 use std::sync::Arc; 25 use std::sync::Arc;
24 -use std::ops::Deref;  
25 26
26 pub(crate) type Pool = r2d2::Pool<ConnectionManager<MysqlConnection>>; 27 pub(crate) type Pool = r2d2::Pool<ConnectionManager<MysqlConnection>>;
27 28
@@ -37,8 +38,6 @@ async fn main() -> std::io::Result<()> { @@ -37,8 +38,6 @@ async fn main() -> std::io::Result<()> {
37 38
38 dotenv::dotenv().ok(); 39 dotenv::dotenv().ok();
39 40
40 - println!("CONFIG: {:?}", config::CONFIG.deref());  
41 -  
42 let tx_upload_worker = upload_worker::launch(); 41 let tx_upload_worker = upload_worker::launch();
43 42
44 let database_url = std::env::var("DATABASE_URL").expect("NOT FOUND"); 43 let database_url = std::env::var("DATABASE_URL").expect("NOT FOUND");
@@ -56,6 +55,9 @@ async fn main() -> std::io::Result<()> { @@ -56,6 +55,9 @@ async fn main() -> std::io::Result<()> {
56 . service( web::resource("/upload") 55 . service( web::resource("/upload")
57 . route(web::post().to(upload)) 56 . route(web::post().to(upload))
58 ) 57 )
  58 + . service( web::resource("/images/{id}")
  59 + . route(web::get().to(get_image))
  60 + )
59 . service( web::resource("/markdowns") 61 . service( web::resource("/markdowns")
60 . route(web::get().to(get_markdowns)) 62 . route(web::get().to(get_markdowns))
61 ) 63 )
  1 +use std::convert::TryFrom;
1 use std::sync::Arc; 2 use std::sync::Arc;
2 3
3 use crate::error::*; 4 use crate::error::*;
4 -use crate::{schema::*, Pool}; 5 +use crate::routes::image::Size;
  6 +use crate::uuid::Uuid;
  7 +use crate::{schema::*, Pool, config::CONFIG};
  8 +use async_std::path::PathBuf;
5 use diesel::{Connection, insert_into, delete, update}; 9 use diesel::{Connection, insert_into, delete, update};
6 use diesel::prelude::*; 10 use diesel::prelude::*;
7 use serde::{Deserialize, Serialize}; 11 use serde::{Deserialize, Serialize};
@@ -86,6 +90,24 @@ macro_rules! upload_filename { @@ -86,6 +90,24 @@ macro_rules! upload_filename {
86 } 90 }
87 91
88 92
  93 +impl Image {
  94 + pub(crate) fn path(&self, size :Size) -> String {
  95 + let uuid = Uuid::try_from( self.uuid
  96 + . as_ref()
  97 + . unwrap()
  98 + . as_slice() ).unwrap();
  99 + let uuid_string = format!("{}", uuid);
  100 +
  101 + let mut image_path = PathBuf::from(CONFIG.images_dir());
  102 + image_path.push(&uuid_string.as_str()[..1]);
  103 + image_path.push(&uuid_string.as_str()[..2]);
  104 + image_path.push(&uuid_string.as_str()[..3]);
  105 + image_path.push(&format!("{}_{}", &uuid_string, size));
  106 +
  107 + image_path.into_os_string().into_string().unwrap()
  108 + }
  109 +}
  110 +
89 pub(crate) fn upload( pool: Arc<Pool> 111 pub(crate) fn upload( pool: Arc<Pool>
90 , item: Upload ) -> Result<Image> { 112 , item: Upload ) -> Result<Image> {
91 use crate::schema::images::dsl::*; 113 use crate::schema::images::dsl::*;
@@ -129,3 +151,15 @@ pub(crate) fn finalize( pool: Arc<Pool> @@ -129,3 +151,15 @@ pub(crate) fn finalize( pool: Arc<Pool>
129 }, 151 },
130 } 152 }
131 } 153 }
  154 +
  155 +pub(crate) fn get_image( pool: Arc<Pool>
  156 + , ident: i32 ) -> Result<Image>
  157 +{
  158 + use crate::schema::images::dsl::*;
  159 +
  160 + let db_connection = pool.get()?;
  161 +
  162 + Ok( images
  163 + . filter(id.eq(ident))
  164 + . first::<Image>(&db_connection)? )
  165 +}
  1 +use std::fmt::Display;
  2 +
  3 +use crate::{models::image, AppData};
  4 +
  5 +use actix_web::{Error, web};
  6 +use anyhow::Result;
  7 +use serde::{Deserialize, Serialize};
  8 +
  9 +#[derive(Debug, Deserialize, Serialize)]
  10 +#[serde (rename_all = "lowercase")]
  11 +pub enum Size {
  12 + Original,
  13 + Large,
  14 + Medium,
  15 + Small
  16 +}
  17 +
  18 +#[derive(Debug, Deserialize, Serialize)]
  19 +pub struct SizeQuery {
  20 + size :Option<Size>,
  21 +}
  22 +
  23 +impl Display for Size {
  24 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  25 + let size_str = match self {
  26 + Size::Original => "original",
  27 + Size::Large => "large",
  28 + Size::Medium => "medium",
  29 + Size::Small => "small"
  30 + };
  31 +
  32 + write!(f, "{}", size_str)
  33 + }
  34 +}
  35 +
  36 +pub async fn get_image( app_data: web::Data<AppData>
  37 + , ident: web::Path<i32>
  38 + , size: web::Query<SizeQuery>
  39 + ) -> Result<actix_files::NamedFile, Error>
  40 +{
  41 + let pool = app_data.database_pool.clone();
  42 + let ident = ident.into_inner();
  43 + let size = size.into_inner().size.or(Some(Size::Large)).unwrap();
  44 +
  45 + let image = web::block(move || image::get_image(pool, ident)).await?;
  46 + let path = image.path(size);
  47 +
  48 + Ok( actix_files::NamedFile::open(path)?
  49 + . set_content_type(image.mime_type.parse().unwrap())
  50 + . disable_content_disposition() )
  51 +}
  1 +pub(crate) mod image;
1 pub(crate) mod markdown; 2 pub(crate) mod markdown;
2 pub(crate) mod other; 3 pub(crate) mod other;
3 pub(crate) mod upload; 4 pub(crate) mod upload;
1 -use std::{io::{SeekFrom, ErrorKind}, sync::Arc}; 1 +use std::{ io::{SeekFrom, ErrorKind}
  2 + , sync::Arc
  3 + , convert::TryFrom };
2 use actix_web::web; 4 use actix_web::web;
3 use async_std::{ fs::{File, DirBuilder, copy, metadata, remove_file} 5 use async_std::{ fs::{File, DirBuilder, copy, metadata, remove_file}
4 , channel::{Sender, Receiver, bounded} 6 , channel::{Sender, Receiver, bounded}
5 , path::PathBuf 7 , path::PathBuf
6 - , io::Result }; 8 + , io::Result, task::spawn_blocking };
7 use futures::{ AsyncSeekExt, AsyncReadExt, FutureExt, StreamExt, select 9 use futures::{ AsyncSeekExt, AsyncReadExt, FutureExt, StreamExt, select
8 , stream::FuturesUnordered}; 10 , stream::FuturesUnordered};
9 11
10 -use crate::{models::image::{Image, finalize}, upload_filename, config::CONFIG, Pool};  
11 -use crate::uuid::Uuid; 12 +use crate::{ models::image::{Image, finalize}
  13 + , upload_filename
  14 + , config::CONFIG
  15 + , Pool
  16 + , routes::image::Size
  17 + , uuid::Uuid };
12 18
13 -use std::convert::TryFrom;  
14 -use image::{io::Reader as ImageReader, GenericImageView}; 19 +use image::{ io::Reader as ImageReader
  20 + , GenericImageView
  21 + , imageops::FilterType::Lanczos3
  22 + , ImageFormat::Jpeg };
15 23
16 pub fn launch() -> Sender<(Arc<Pool>, Image)> { 24 pub fn launch() -> Sender<(Arc<Pool>, Image)> {
17 let (tx_upload_worker, rx_upload_worker) 25 let (tx_upload_worker, rx_upload_worker)
@@ -50,31 +58,60 @@ async fn worker(pool :Arc<Pool>, mut image :Image) { @@ -50,31 +58,60 @@ async fn worker(pool :Arc<Pool>, mut image :Image) {
50 let uuid = Uuid::get(CONFIG.namespace(), buf.as_mut()); 58 let uuid = Uuid::get(CONFIG.namespace(), buf.as_mut());
51 let uuid_string = format!("{}", uuid); 59 let uuid_string = format!("{}", uuid);
52 60
53 - let mut image_path = PathBuf::from(CONFIG.images_dir());  
54 - image_path.push(&uuid_string.as_str()[..2]);  
55 - image_path.push(&uuid_string.as_str()[..5]);  
56 -  
57 - DirBuilder::new() . recursive(true)  
58 - . create(&image_path)  
59 - . await  
60 - . unwrap(); 61 + let image_path = PathBuf::from(CONFIG.images_dir());
  62 + let mut orig_path = image_path.clone();
  63 + let mut large_path = image_path.clone();
  64 + let mut medium_path = image_path.clone();
  65 + let mut small_path = image_path.clone();
  66 +
  67 + macro_rules! prepare {
  68 + ($p:expr, $n:expr) => (
  69 + $p.push(&uuid_string.as_str()[..1]);
  70 + $p.push(&uuid_string.as_str()[..2]);
  71 + $p.push(&uuid_string.as_str()[..3]);
  72 +
  73 + DirBuilder::new() . recursive(true)
  74 + . create(&$p)
  75 + . await
  76 + . unwrap();
  77 +
  78 + $p.push(&format!("{}_{}", &uuid_string, $n));
  79 + )
  80 + }
61 81
62 - image_path.push(&uuid_string); 82 + prepare!(orig_path, Size::Original);
  83 + prepare!(large_path, Size::Large);
  84 + prepare!(medium_path, Size::Medium);
  85 + prepare!(small_path, Size::Small);
63 86
64 image.upload_uuid = None; 87 image.upload_uuid = None;
65 image.uuid = Some(uuid.0.as_bytes().to_vec()); 88 image.uuid = Some(uuid.0.as_bytes().to_vec());
66 89
67 - match metadata(&image_path).await { 90 + match metadata(&orig_path).await {
68 Err(e) if e.kind() == ErrorKind::NotFound => { 91 Err(e) if e.kind() == ErrorKind::NotFound => {
69 - copy(&upload_filename, &image_path).await.unwrap();  
70 -  
71 - let img = ImageReader::open(&image_path).unwrap()  
72 - . with_guessed_format().unwrap()  
73 - . decode().unwrap();  
74 - let (dim_x, dim_y) = img.dimensions();  
75 -  
76 - image.dim_x = Some(dim_x as i32);  
77 - image.dim_y = Some(dim_y as i32); 92 + copy(&upload_filename, &orig_path).await.unwrap();
  93 +
  94 + let (dim_x, dim_y) = spawn_blocking(move || {
  95 + let img = ImageReader::open(&orig_path).unwrap()
  96 + . with_guessed_format().unwrap()
  97 + . decode().unwrap();
  98 + let (dim_x, dim_y) = img.dimensions();
  99 +
  100 + img . resize(1280, 1280, Lanczos3)
  101 + . save_with_format(&large_path, Jpeg)
  102 + . unwrap();
  103 + img . resize(800, 800, Lanczos3)
  104 + . save_with_format(&medium_path, Jpeg)
  105 + . unwrap();
  106 + img . resize(400, 400, Lanczos3)
  107 + . save_with_format(&small_path, Jpeg)
  108 + . unwrap();
  109 +
  110 + (dim_x as i32, dim_y as i32)
  111 + }).await;
  112 +
  113 + image.dim_x = Some(dim_x);
  114 + image.dim_y = Some(dim_y);
78 }, 115 },
79 Err(e) => { 116 Err(e) => {
80 let e :Result<()> = Err(e); 117 let e :Result<()> = Err(e);
Please register or login to post a comment