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 8 license = "GPL-3.0-or-later"
9 9
10 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 14 anyhow = "1.0"
15 15 artshop-common = { path = "../common" }
16   -async-std = "^1.10"
  16 +async-std = { version = "^1.10", features = ["unstable"] }
17 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 19 diffy = "0.2"
20 20 dotenv = "0.15.0"
21 21 flate2 = "^1.0"
... ... @@ -24,6 +24,7 @@ futures-util = { version = "0", features = ["std"] }
24 24 image = "^0.23"
25 25 listenfd = "0.3"
26 26 once_cell = "^1.9"
  27 +mime = "^0.3"
27 28 r2d2 = "0.8.9"
28 29 serde = { version = "^1.0", features = ["derive"] }
29 30 serde_derive = "1.0"
... ...
... ... @@ -14,6 +14,8 @@ use routes::markdown::*;
14 14 use routes::other::*;
15 15 use routes::user::*;
16 16 use routes::upload::*;
  17 +use routes::image::*;
  18 +
17 19
18 20 use actix_web::{guard, web, App, HttpResponse, HttpServer};
19 21 use async_std::channel::Sender;
... ... @@ -21,7 +23,6 @@ use diesel::r2d2::{self, ConnectionManager};
21 23 use diesel::MysqlConnection;
22 24 use listenfd::ListenFd;
23 25 use std::sync::Arc;
24   -use std::ops::Deref;
25 26
26 27 pub(crate) type Pool = r2d2::Pool<ConnectionManager<MysqlConnection>>;
27 28
... ... @@ -37,8 +38,6 @@ async fn main() -> std::io::Result<()> {
37 38
38 39 dotenv::dotenv().ok();
39 40
40   - println!("CONFIG: {:?}", config::CONFIG.deref());
41   -
42 41 let tx_upload_worker = upload_worker::launch();
43 42
44 43 let database_url = std::env::var("DATABASE_URL").expect("NOT FOUND");
... ... @@ -56,6 +55,9 @@ async fn main() -> std::io::Result<()> {
56 55 . service( web::resource("/upload")
57 56 . route(web::post().to(upload))
58 57 )
  58 + . service( web::resource("/images/{id}")
  59 + . route(web::get().to(get_image))
  60 + )
59 61 . service( web::resource("/markdowns")
60 62 . route(web::get().to(get_markdowns))
61 63 )
... ...
  1 +use std::convert::TryFrom;
1 2 use std::sync::Arc;
2 3
3 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 9 use diesel::{Connection, insert_into, delete, update};
6 10 use diesel::prelude::*;
7 11 use serde::{Deserialize, Serialize};
... ... @@ -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 111 pub(crate) fn upload( pool: Arc<Pool>
90 112 , item: Upload ) -> Result<Image> {
91 113 use crate::schema::images::dsl::*;
... ... @@ -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 2 pub(crate) mod markdown;
2 3 pub(crate) mod other;
3 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 4 use actix_web::web;
3 5 use async_std::{ fs::{File, DirBuilder, copy, metadata, remove_file}
4 6 , channel::{Sender, Receiver, bounded}
5 7 , path::PathBuf
6   - , io::Result };
  8 + , io::Result, task::spawn_blocking };
7 9 use futures::{ AsyncSeekExt, AsyncReadExt, FutureExt, StreamExt, select
8 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 24 pub fn launch() -> Sender<(Arc<Pool>, Image)> {
17 25 let (tx_upload_worker, rx_upload_worker)
... ... @@ -50,31 +58,60 @@ async fn worker(pool :Arc<Pool>, mut image :Image) {
50 58 let uuid = Uuid::get(CONFIG.namespace(), buf.as_mut());
51 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 87 image.upload_uuid = None;
65 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 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 116 Err(e) => {
80 117 let e :Result<()> = Err(e);
... ...
Please register or login to post a comment