Showing
6 changed files
with
160 additions
and
34 deletions
@@ -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 | +} |
server/src/routes/image.rs
0 → 100644
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 | -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