Showing
6 changed files
with
160 additions
and
34 deletions
... | ... | @@ -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 | +} | ... | ... |
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 | 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