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