Commit a54e9551c6e939b713098f90e152f940f973ff28
1 parent
18bb86b1
Copyrights and error handling.
Lots of removed unwraps and added copyright info for uploaded images.
Showing
16 changed files
with
498 additions
and
210 deletions
... | ... | @@ -39,7 +39,7 @@ start: |
39 | 39 | cargo watch -i static/ -i var/ \ |
40 | 40 | -s "PROFILE=$(PROFILE) make run" |
41 | 41 | |
42 | -run: build wasm | |
42 | +run: build wasm data/copyright.png | |
43 | 43 | $(call msgnl,RUN SERVER) |
44 | 44 | @PROFILE=$(PROFILE) cargo run $(CARGO_PROFILE) --bin artshop-server |
45 | 45 | |
... | ... | @@ -54,6 +54,8 @@ clean: |
54 | 54 | @rm -Rf ./static/ui |
55 | 55 | $(call msgnl,CLEAN INSTALLED WASM) |
56 | 56 | @pushd ui; PROFILE=$(PROFILE) cargo clean; popd |
57 | + $(call msgnl,CLEAN COPYRIGHT PNG) | |
58 | + @rm -Rf ./data/copyright.png | |
57 | 59 | |
58 | 60 | $(WASM_TARGET): $(WASM_SOURCES) |
59 | 61 | $(call msgnl,BUILD WASM UI) |
... | ... | @@ -71,6 +73,12 @@ $(SERVER_TARGET): $(SERVER_SOURCES) |
71 | 73 | $(call msgnl,BUILD SERVER) |
72 | 74 | @PROFILE=$(PROFILE) cargo build $(CARGO_PROFILE) --bin artshop-server |
73 | 75 | |
76 | +data/copyright.png: data/copyright.txt | |
77 | + $(call msgnl,CREATE COPYRIGHT PNG) | |
78 | + @cat $< | convert -pointsize 24 -font Helvetica \ | |
79 | + -background transparent -fill "rgba(255,255,255,0.35)" \ | |
80 | + text:- -trim +repage $@ | |
81 | + | |
74 | 82 | release: |
75 | 83 | docker build -t artshop -f build/Dockerfile . |
76 | 84 | |
... | ... | @@ -99,6 +107,8 @@ downdb: |
99 | 107 | @docker stop mariadb-dev||true |
100 | 108 | $(call msgnl,REMOVE DB CONTAINER) |
101 | 109 | @docker rm -v mariadb-dev||true |
110 | + $(call msgnl,REMOVE IMAGES) | |
111 | + @rm -Rf var/lib/artshop/images | |
102 | 112 | |
103 | 113 | devdb: |
104 | 114 | $(call msgnl,CREATE DB CONTAINER) | ... | ... |
... | ... | @@ -5,5 +5,17 @@ namespace = "artshop.shome.steffers.org" |
5 | 5 | # url = "./var/lib/artshop/database" |
6 | 6 | |
7 | 7 | [locations] |
8 | -upload = "/tmp/artshop/uploads" | |
8 | +data = "./data" | |
9 | 9 | images = "./var/lib/artshop/images" |
10 | +upload = "/tmp/artshop/uploads" | |
11 | + | |
12 | +[sizes] | |
13 | +large = { width = 1280, height = 1280 } | |
14 | +medium = { width = 800, height = 800 } | |
15 | +small = { width = 400, height = 400 } | |
16 | +thumbnail = {width = 100, height = 100 } | |
17 | + | |
18 | +[copyright] | |
19 | +image_path = "./data/copyright.png" | |
20 | +steganography = "Copyright © 2022, Stefanies Artshop, All rights reserved" | |
21 | +exiv = "Copyright, Stefanies Artshop, 2022. All rights reserved" | ... | ... |
data/copyright.txt
0 → 100644
1 | +Copyright © 2022, Stefanies Artshop | ... | ... |
... | ... | @@ -70,3 +70,12 @@ and parallel: |
70 | 70 | https://api.github.com/users/xxxxxxx/events/public |
71 | 71 | |
72 | 72 | where xxxxxxx is the github username. Then search for email. |
73 | + | |
74 | +# Create a Copyright Watermark ... | |
75 | + | |
76 | + cat text.txt | convert -pointsize 24 -font Helvetica -background black \ | |
77 | + -fill white text:- -trim +repage text.jpg | |
78 | + | |
79 | + cat copyright.txt | convert -pointsize 24 -font Helvetica \ | |
80 | + -background transparent -fill "rgba(255,255,255,0.35)" \ | |
81 | + text:- -trim +repage copyright.png | ... | ... |
... | ... | @@ -16,7 +16,7 @@ artshop-common = { path = "../common" } |
16 | 16 | async-std = { version = "^1.10", features = ["unstable"] } |
17 | 17 | chrono = "0.4.15" |
18 | 18 | diesel = { version = "1.4.7", features = ["mysql", "sqlite", "r2d2"] } |
19 | -diffy = "0.2.1" | |
19 | +diffy = "0.2.2" | |
20 | 20 | dotenv = "0.15.0" |
21 | 21 | flate2 = "^1.0" |
22 | 22 | futures = "^0.3" |
... | ... | @@ -26,13 +26,10 @@ listenfd = "0.3" |
26 | 26 | once_cell = "^1.9" |
27 | 27 | mime = "^0.3" |
28 | 28 | r2d2 = "0.8.9" |
29 | +rexiv2 = "^0.9" | |
29 | 30 | serde = { version = "^1.0", features = ["derive"] } |
30 | 31 | serde_derive = "1.0" |
31 | 32 | serde_json = "1.0" |
33 | +steganography = { git = "https://github.com/teovoinea/steganography" } | |
32 | 34 | toml = "^0.5" |
33 | 35 | uuid = { version = "^0.8", features = ["v4", "v5"] } |
34 | - | |
35 | -[package.metadata.patch.diffy] | |
36 | -version = "0.2.1" | |
37 | -patches = ["./patches/diffy-0.2.1_fix-adjacent-hunk-parsing.patch"] | |
38 | - | ... | ... |
1 | 1 | use std::fs::File; |
2 | 2 | use std::io::Read; |
3 | +use image::{ DynamicImage, io::Reader as ImageReader }; | |
3 | 4 | use once_cell::sync::Lazy; |
4 | 5 | use serde::Deserialize; |
6 | +use anyhow::Result; | |
7 | +use crate::routes::image::Size as ImageSize; | |
5 | 8 | |
6 | 9 | #[derive(Debug, Deserialize)] |
7 | 10 | struct Database { url :Option<String> } |
8 | 11 | |
9 | 12 | #[derive(Debug, Deserialize)] |
10 | -struct Locations { upload :String | |
11 | - , images :String } | |
13 | +struct Locations { data :String | |
14 | + , images :String | |
15 | + , upload :String } | |
12 | 16 | |
13 | 17 | #[derive(Debug, Deserialize)] |
14 | -pub(crate) struct Config { namespace :String | |
15 | - , database :Database | |
16 | - , locations :Locations } | |
18 | +struct Size { width :u32 | |
19 | + , height :u32 } | |
17 | 20 | |
18 | -pub(crate) static CONFIG :Lazy<Config> = Lazy::new(|| Config::load()); | |
21 | +#[derive(Debug, Deserialize)] | |
22 | +struct Sizes { large :Size | |
23 | + , medium :Size | |
24 | + , small :Size | |
25 | + , thumbnail :Size } | |
26 | + | |
27 | +#[derive(Debug, Deserialize)] | |
28 | +struct Copyright { image_path :String | |
29 | + , steganography :String | |
30 | + , exiv :String } | |
31 | + | |
32 | +#[derive(Debug, Deserialize)] | |
33 | +pub(crate) struct ConfigFile { namespace :String | |
34 | + , database :Database | |
35 | + , locations :Locations | |
36 | + , sizes :Sizes | |
37 | + , copyright :Copyright } | |
38 | + | |
39 | +pub(crate) struct Config { config_file :ConfigFile | |
40 | + , copyright_image :DynamicImage } | |
41 | + | |
42 | +pub(crate) static CONFIG :Lazy<Config> = | |
43 | + Lazy::new(|| Config::load().unwrap()); | |
19 | 44 | |
20 | 45 | impl Config { |
21 | - pub fn load() -> Config { | |
22 | - let filename = std::env::var("CONFIG").unwrap(); | |
46 | + pub fn load() -> Result<Self> { | |
47 | + let filename = std::env::var("CONFIG")?; | |
23 | 48 | |
24 | 49 | let mut buffer = vec![]; |
25 | - let mut file = File::open(filename).unwrap(); | |
50 | + let mut file = File::open(filename)?; | |
26 | 51 | |
27 | - file.read_to_end(&mut buffer).unwrap(); | |
28 | - let mut config :Config = toml::from_slice(&buffer).unwrap(); | |
52 | + file.read_to_end(&mut buffer)?; | |
53 | + let mut config_file :ConfigFile = toml::from_slice(&buffer)?; | |
29 | 54 | |
30 | - config.database.url = match config.database.url { | |
55 | + config_file.database.url = match config_file.database.url { | |
31 | 56 | Some(url) => Some(url), |
32 | 57 | None => std::env::var("DATABASE_URL").ok() |
33 | 58 | }; |
34 | 59 | |
35 | - config | |
60 | + let copyright_image = ImageReader::open(&config_file.copyright.image_path)? | |
61 | + . with_guessed_format()? | |
62 | + . decode()?; | |
63 | + | |
64 | + Ok(Self { config_file, copyright_image }) | |
36 | 65 | } |
37 | 66 | |
38 | 67 | pub fn namespace(&self) -> &str { |
39 | - self.namespace.as_str() | |
68 | + self.config_file.namespace.as_str() | |
40 | 69 | } |
41 | 70 | |
42 | 71 | pub fn upload_dir(&self) -> &str { |
43 | - self.locations.upload.as_str() | |
72 | + self.config_file.locations.upload.as_str() | |
44 | 73 | } |
45 | 74 | |
46 | 75 | pub fn images_dir(&self) -> &str { |
47 | - self.locations.images.as_str() | |
76 | + self.config_file.locations.images.as_str() | |
77 | + } | |
78 | + | |
79 | + pub fn width(&self, size :ImageSize) -> Option<u32> { | |
80 | + match size { | |
81 | + ImageSize::Original => None, | |
82 | + ImageSize::Large => Some(self.config_file.sizes.large.width), | |
83 | + ImageSize::Medium => Some(self.config_file.sizes.medium.width), | |
84 | + ImageSize::Small => Some(self.config_file.sizes.small.width), | |
85 | + ImageSize::Thumbnail => Some(self.config_file.sizes.thumbnail.width), | |
86 | + } | |
87 | + } | |
88 | + | |
89 | + pub fn height(&self, size :ImageSize) -> Option<u32> { | |
90 | + match size { | |
91 | + ImageSize::Original => None, | |
92 | + ImageSize::Large => Some(self.config_file.sizes.large.height), | |
93 | + ImageSize::Medium => Some(self.config_file.sizes.medium.height), | |
94 | + ImageSize::Small => Some(self.config_file.sizes.small.height), | |
95 | + ImageSize::Thumbnail => Some(self.config_file.sizes.thumbnail.height), | |
96 | + } | |
97 | + } | |
98 | + | |
99 | + pub fn copyright_image(&self) -> &DynamicImage { | |
100 | + &self.copyright_image | |
101 | + } | |
102 | + | |
103 | + pub fn copyright_steganography(&self) -> &str { | |
104 | + self.config_file.copyright.steganography.as_str() | |
105 | + } | |
106 | + | |
107 | + pub fn copyright_exiv(&self) -> &str { | |
108 | + self.config_file.copyright.exiv.as_str() | |
48 | 109 | } |
49 | 110 | } | ... | ... |
1 | -use std::{fmt::Display, pin::Pin}; | |
1 | +use std::{fmt::Display, pin::Pin, sync::Arc}; | |
2 | 2 | |
3 | +use actix_rt::blocking::BlockingError; | |
4 | +use actix_web::{http::{StatusCode, header::ToStrError}, ResponseError}; | |
5 | +use async_std::channel::SendError; | |
3 | 6 | use diesel::result; |
4 | -use diffy::ParsePatchError; | |
7 | +use diffy::{ParsePatchError, ApplyError}; | |
8 | +use image::ImageError; | |
9 | +use mime::FromStrError; | |
5 | 10 | use r2d2; |
11 | +use rexiv2::Rexiv2Error; | |
12 | + | |
13 | +use crate::{Pool, models::image::Image}; | |
6 | 14 | |
7 | 15 | type ParentError = Option<Pin<Box<dyn std::error::Error>>>; |
8 | 16 | |
9 | 17 | #[derive(Debug)] |
10 | 18 | pub struct Error { |
11 | - source: ParentError, | |
12 | - message: String, | |
19 | + source :ParentError, | |
20 | + message :String, | |
21 | + status :Option<StatusCode> | |
13 | 22 | } |
14 | 23 | |
15 | 24 | unsafe impl Send for Error {} |
... | ... | @@ -22,19 +31,36 @@ impl std::error::Error for Error { |
22 | 31 | } |
23 | 32 | } |
24 | 33 | |
34 | +impl ResponseError for Error { | |
35 | + fn status_code(&self) -> StatusCode { | |
36 | + self.status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) | |
37 | + } | |
38 | +} | |
39 | + | |
25 | 40 | impl Display for Error { |
26 | 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
27 | 42 | match self { |
28 | - Error { source: Some(source), message } => | |
29 | - write!(f, "{}: {}", message, source), | |
30 | - Error { source: None, message } => write!(f, "{}", message), | |
43 | + Error { source: Some(source), message, status } => | |
44 | + write!( f | |
45 | + , "[{}] {}: {}" | |
46 | + , status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) | |
47 | + , message | |
48 | + , source ), | |
49 | + Error { source: None, message, status } => | |
50 | + write!( f | |
51 | + , "[{}] {}" | |
52 | + , status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) | |
53 | + , message ), | |
31 | 54 | } |
32 | 55 | } |
33 | 56 | } |
34 | 57 | |
35 | -impl From<&str> for Error { | |
36 | - fn from(message: &str) -> Self { | |
37 | - Self { source: None, message: String::from(message) } | |
58 | +impl Error { | |
59 | + pub(crate) fn new(message :&str, status :StatusCode) -> Self { | |
60 | + Self { source: None | |
61 | + , message: String::from(message) | |
62 | + , status: Some(status) | |
63 | + } | |
38 | 64 | } |
39 | 65 | } |
40 | 66 | |
... | ... | @@ -42,6 +68,7 @@ impl From<result::Error> for Error { |
42 | 68 | fn from(source: result::Error) -> Self { |
43 | 69 | Self { source: Some(Box::pin(source)) |
44 | 70 | , message: String::from("Diesel Result Error") |
71 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
45 | 72 | } |
46 | 73 | } |
47 | 74 | } |
... | ... | @@ -49,7 +76,8 @@ impl From<result::Error> for Error { |
49 | 76 | impl From<r2d2::Error> for Error { |
50 | 77 | fn from(source: r2d2::Error) -> Self { |
51 | 78 | Self { source: Some(Box::pin(source)) |
52 | - , message: String::from("Diesel Result Error") | |
79 | + , message: String::from("R2D2 Pool Error") | |
80 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
53 | 81 | } |
54 | 82 | } |
55 | 83 | } |
... | ... | @@ -58,6 +86,7 @@ impl From<std::io::Error> for Error { |
58 | 86 | fn from(source: std::io::Error) -> Self { |
59 | 87 | Self { source: Some(Box::pin(source)) |
60 | 88 | , message: String::from("IO Error") |
89 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
61 | 90 | } |
62 | 91 | } |
63 | 92 | } |
... | ... | @@ -65,7 +94,8 @@ impl From<std::io::Error> for Error { |
65 | 94 | impl From<std::str::Utf8Error> for Error { |
66 | 95 | fn from(source: std::str::Utf8Error) -> Self { |
67 | 96 | Self { source: Some(Box::pin(source)) |
68 | - , message: String::from("IO Error") | |
97 | + , message: String::from("UTF8 Error") | |
98 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
69 | 99 | } |
70 | 100 | } |
71 | 101 | } |
... | ... | @@ -73,7 +103,17 @@ impl From<std::str::Utf8Error> for Error { |
73 | 103 | impl From<ParsePatchError> for Error { |
74 | 104 | fn from(source: ParsePatchError) -> Self { |
75 | 105 | Self { source: Some(Box::pin(source)) |
76 | - , message: String::from("IO Error") | |
106 | + , message: String::from("Diffy Error") | |
107 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
108 | + } | |
109 | + } | |
110 | +} | |
111 | + | |
112 | +impl From<ApplyError> for Error { | |
113 | + fn from(source: ApplyError) -> Self { | |
114 | + Self { source: Some(Box::pin(source)) | |
115 | + , message: String::from("Diffy Error") | |
116 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
77 | 117 | } |
78 | 118 | } |
79 | 119 | } |
... | ... | @@ -82,6 +122,61 @@ impl From<uuid::Error> for Error { |
82 | 122 | fn from(source: uuid::Error) -> Self { |
83 | 123 | Self { source: Some(Box::pin(source)) |
84 | 124 | , message: String::from("UUID error") |
125 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
126 | + } | |
127 | + } | |
128 | +} | |
129 | + | |
130 | +impl From<FromStrError> for Error { | |
131 | + fn from(source: FromStrError) -> Self { | |
132 | + Self { source: Some(Box::pin(source)) | |
133 | + , message: String::from("Mime error") | |
134 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
135 | + } | |
136 | + } | |
137 | +} | |
138 | + | |
139 | +impl From<ToStrError> for Error { | |
140 | + fn from(source: ToStrError) -> Self { | |
141 | + Self { source: Some(Box::pin(source)) | |
142 | + , message: String::from("Header error") | |
143 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
144 | + } | |
145 | + } | |
146 | +} | |
147 | + | |
148 | +impl From<SendError<(Arc<Pool>, Image)>> for Error { | |
149 | + fn from(source: SendError<(Arc<Pool>, Image)>) -> Self { | |
150 | + Self { source: Some(Box::pin(source)) | |
151 | + , message: String::from("Image Worker send error") | |
152 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
153 | + } | |
154 | + } | |
155 | +} | |
156 | + | |
157 | +impl From<ImageError> for Error { | |
158 | + fn from(source: ImageError) -> Self { | |
159 | + Self { source: Some(Box::pin(source)) | |
160 | + , message: String::from("Image processing error") | |
161 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
162 | + } | |
163 | + } | |
164 | +} | |
165 | + | |
166 | +impl From<Rexiv2Error> for Error { | |
167 | + fn from(source: Rexiv2Error) -> Self { | |
168 | + Self { source: Some(Box::pin(source)) | |
169 | + , message: String::from("Exiv error") | |
170 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
171 | + } | |
172 | + } | |
173 | +} | |
174 | + | |
175 | +impl From<BlockingError<Error>> for Error { | |
176 | + fn from(source: BlockingError<Error>) -> Self { | |
177 | + Self { source: Some(Box::pin(source)) | |
178 | + , message: String::from("web::block error") | |
179 | + , status: Some(StatusCode::INTERNAL_SERVER_ERROR) | |
85 | 180 | } |
86 | 181 | } |
87 | 182 | } | ... | ... |
1 | 1 | use std::convert::TryFrom; |
2 | +use std::io::SeekFrom; | |
2 | 3 | use std::sync::Arc; |
3 | 4 | |
4 | 5 | use crate::error::*; |
5 | 6 | use crate::routes::image::Size; |
6 | 7 | use crate::uuid::Uuid; |
7 | 8 | use crate::{schema::*, Pool, config::CONFIG}; |
9 | +use actix_web::http::StatusCode; | |
10 | +use async_std::fs::File; | |
11 | +use async_std::io::prelude::SeekExt; | |
8 | 12 | use async_std::path::PathBuf; |
9 | 13 | use diesel::{Connection, insert_into, delete, update}; |
10 | 14 | use diesel::prelude::*; |
11 | 15 | use serde::{Deserialize, Serialize}; |
12 | 16 | |
17 | +use async_std::io::ReadExt; | |
18 | + | |
13 | 19 | #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Identifiable)] |
14 | 20 | pub struct Image { |
15 | 21 | pub id :i32, |
... | ... | @@ -54,6 +60,13 @@ pub struct ImagePatch { |
54 | 60 | pub date_updated :String |
55 | 61 | } |
56 | 62 | |
63 | +pub struct ImageContext { | |
64 | + pub image :Image, | |
65 | + base_path :Option<PathBuf>, | |
66 | + uuid_string :Option<String>, | |
67 | + upload_path :Option<PathBuf> | |
68 | +} | |
69 | + | |
57 | 70 | impl From<Image> for ImagePatch { |
58 | 71 | fn from(image: Image) -> Self { |
59 | 72 | let now = chrono::Local::now().naive_local(); |
... | ... | @@ -69,44 +82,117 @@ impl From<Image> for ImagePatch { |
69 | 82 | } |
70 | 83 | } |
71 | 84 | |
72 | -#[macro_export] | |
73 | -macro_rules! upload_uuid { | |
74 | - ($u:expr) => { | |
75 | - match &$u.upload_uuid { | |
76 | - Some(uuid) => $crate::uuid::Uuid::try_from(uuid.as_slice()).ok(), | |
77 | - None => None, | |
78 | - } | |
79 | - }; | |
85 | +async fn read_at( f :&mut File | |
86 | + , pos :SeekFrom | |
87 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
88 | + f.seek(pos).await?; | |
89 | + f.read_exact(buf).await | |
80 | 90 | } |
81 | 91 | |
82 | -#[macro_export] | |
83 | -macro_rules! upload_filename { | |
84 | - ($u:expr) => { | |
85 | - $crate::upload_uuid!($u) | |
86 | - . and_then(|uuid| Some(format!( "{}/upload_{}" | |
87 | - , $crate::config::CONFIG.upload_dir() | |
88 | - , uuid ))) | |
89 | - }; | |
90 | -} | |
92 | +async fn get_sample( f :&mut File | |
93 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
94 | + let file_len = f.metadata().await?.len(); | |
95 | + let chunk_size = buf.len() / 3; | |
96 | + | |
97 | + read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?; | |
98 | + if file_len >= 2 * chunk_size as u64 { | |
99 | + read_at( f | |
100 | + , SeekFrom::End(-(chunk_size as i64)) | |
101 | + , &mut buf[2*chunk_size..]).await?; | |
102 | + } | |
103 | + if file_len >= 3 * chunk_size as u64 { | |
104 | + read_at( f | |
105 | + , SeekFrom::Start((file_len-chunk_size as u64) / 2) | |
106 | + , &mut buf[chunk_size..2*chunk_size]).await?; | |
107 | + } | |
91 | 108 | |
109 | + Ok(()) | |
110 | +} | |
92 | 111 | |
93 | 112 | impl Image { |
94 | - // TODO some handling for files that are not processed now | |
95 | - // that is uuid is still None. | |
96 | - pub(crate) fn path(&self, size :Size) -> String { | |
97 | - let uuid = Uuid::try_from( self.uuid | |
98 | - . as_ref() | |
99 | - . unwrap() | |
100 | - . as_slice() ).unwrap(); | |
113 | + pub(crate) fn context(self) -> ImageContext { | |
114 | + ImageContext { image :self | |
115 | + , base_path :None | |
116 | + , uuid_string :None | |
117 | + , upload_path :None } | |
118 | + } | |
119 | +} | |
120 | + | |
121 | +impl ImageContext { | |
122 | + pub(crate) async fn upload_path(&mut self) -> Option<&PathBuf> { | |
123 | + if self.upload_path.is_none() { | |
124 | + let uuid = Uuid::try_from(self.image.upload_uuid.clone()?).ok()?; | |
125 | + let uuid_string = format!("{}", uuid); | |
126 | + | |
127 | + let mut upload_path = PathBuf::from(CONFIG.upload_dir()); | |
128 | + upload_path.push(&uuid_string); | |
129 | + | |
130 | + self.upload_path = Some(upload_path); | |
131 | + } | |
132 | + | |
133 | + self.upload_path.as_ref() | |
134 | + } | |
135 | + | |
136 | + async fn uuid_string(&mut self) -> Result<&String> { | |
137 | + if self.uuid_string.is_none() { | |
138 | + let uuid :Uuid; | |
139 | + | |
140 | + if let Some(u) = &self.image.uuid { | |
141 | + uuid = Uuid::try_from(u.as_ref())?; | |
142 | + } else { | |
143 | + let path = self | |
144 | + . upload_path().await | |
145 | + . ok_or(Error::new( "No upload and no uuid" | |
146 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
147 | + | |
148 | + let mut f = File::open(&path).await?; | |
149 | + let mut buf = vec!['.' as u8; 3 * 3 * 4096]; | |
150 | + | |
151 | + get_sample(&mut f, buf.as_mut()).await?; | |
152 | + uuid = Uuid::get(CONFIG.namespace(), buf.as_mut()); | |
153 | + | |
154 | + self.image.uuid = Some(uuid.0.as_bytes().to_vec()); | |
155 | + self.image.upload_uuid = None; | |
156 | + } | |
157 | + | |
158 | + self.uuid_string = Some(format!("{}", uuid)); | |
159 | + } | |
160 | + | |
161 | + Ok(self.uuid_string.as_ref().unwrap()) | |
162 | + } | |
163 | + | |
164 | + pub(crate) async fn base_path(&mut self) -> Option<&PathBuf> { | |
165 | + if self.upload_path.is_none() { | |
166 | + let uuid_string = self.uuid_string().await.ok()?; | |
167 | + | |
168 | + let mut base_path = PathBuf::from(CONFIG.images_dir()); | |
169 | + base_path.push(&uuid_string.as_str()[..1]); | |
170 | + base_path.push(&uuid_string.as_str()[..2]); | |
171 | + base_path.push(&uuid_string.as_str()[..3]); | |
172 | + | |
173 | + self.base_path = Some(base_path); | |
174 | + } | |
175 | + | |
176 | + self.base_path.as_ref() | |
177 | + } | |
178 | + | |
179 | + pub(crate) async fn path(&mut self, size :Size) -> Option<PathBuf> { | |
180 | + let mut path = self.base_path().await?.to_owned(); | |
181 | + path.push(&format!("{}_{}", &self.uuid_string().await.ok()?, size)); | |
182 | + | |
183 | + Some(path) | |
184 | + } | |
185 | +} | |
186 | + | |
187 | +impl Upload { | |
188 | + pub(crate) async fn upload_path(&mut self) -> Option<PathBuf> { | |
189 | + let uuid = Uuid::try_from(self.upload_uuid.clone()?).ok()?; | |
101 | 190 | let uuid_string = format!("{}", uuid); |
102 | 191 | |
103 | - let mut image_path = PathBuf::from(CONFIG.images_dir()); | |
104 | - image_path.push(&uuid_string.as_str()[..1]); | |
105 | - image_path.push(&uuid_string.as_str()[..2]); | |
106 | - image_path.push(&uuid_string.as_str()[..3]); | |
107 | - image_path.push(&format!("{}_{}", &uuid_string, size)); | |
192 | + let mut upload_path = PathBuf::from(CONFIG.upload_dir()); | |
193 | + upload_path.push(&uuid_string); | |
108 | 194 | |
109 | - image_path.into_os_string().into_string().unwrap() | |
195 | + Some(upload_path) | |
110 | 196 | } |
111 | 197 | } |
112 | 198 | |
... | ... | @@ -132,8 +218,8 @@ pub(crate) fn upload( pool: Arc<Pool> |
132 | 218 | })?) |
133 | 219 | } |
134 | 220 | |
135 | -pub(crate) fn finalize( pool: Arc<Pool> | |
136 | - , item: Image ) -> Result<Image> { | |
221 | +pub(crate) fn finalize( pool :Arc<Pool> | |
222 | + , item :Image ) -> Result<Image> { | |
137 | 223 | use crate::schema::images::dsl::*; |
138 | 224 | |
139 | 225 | let db_connection = pool.get()?; |
... | ... | @@ -154,8 +240,8 @@ pub(crate) fn finalize( pool: Arc<Pool> |
154 | 240 | } |
155 | 241 | } |
156 | 242 | |
157 | -pub(crate) fn get_image( pool: Arc<Pool> | |
158 | - , ident: i32 ) -> Result<Image> | |
243 | +pub(crate) fn get_image( pool :Arc<Pool> | |
244 | + , ident :i32 ) -> Result<Image> | |
159 | 245 | { |
160 | 246 | use crate::schema::images::dsl::*; |
161 | 247 | ... | ... |
... | ... | @@ -162,7 +162,7 @@ pub(crate) fn get_markdown( pool: Arc<Pool> |
162 | 162 | let patch_data = patch.get_diff_as_string()?; |
163 | 163 | let decomp = Patch::from_str(&patch_data)?; |
164 | 164 | |
165 | - markdown.content = apply(&markdown.content, &decomp).unwrap(); | |
165 | + markdown.content = apply(&markdown.content, &decomp)?; | |
166 | 166 | markdown.date_updated = patch.date_created; |
167 | 167 | } |
168 | 168 | }; |
... | ... | @@ -211,8 +211,8 @@ pub(crate) fn update_markdown( pool: Arc<Pool> |
211 | 211 | let patch = format!( "{}", create_patch( item.content.as_str() |
212 | 212 | , markdown.content.as_str() )); |
213 | 213 | let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best()); |
214 | - encoder.write_all(patch.as_bytes()).unwrap(); | |
215 | - let compressed = encoder.finish().unwrap(); | |
214 | + encoder.write_all(patch.as_bytes())?; | |
215 | + let compressed = encoder.finish()?; | |
216 | 216 | |
217 | 217 | let last_diff = match markdown_diffs . filter(markdown_id.eq(markdown.id)) |
218 | 218 | . order(diff_id.desc()) |
... | ... | @@ -240,7 +240,7 @@ pub(crate) fn update_markdown( pool: Arc<Pool> |
240 | 240 | update(&markdown).set(MarkdownChange::from(&item)).execute(&db_connection)?; |
241 | 241 | |
242 | 242 | Ok(()) |
243 | - }).unwrap(); | |
243 | + })?; | |
244 | 244 | |
245 | 245 | markdown.name = item.name; |
246 | 246 | markdown.content = item.content; | ... | ... |
1 | 1 | use std::fmt::Display; |
2 | 2 | |
3 | -use crate::{models::image, AppData}; | |
3 | +use crate::{models::image, AppData, error::Error}; | |
4 | 4 | |
5 | -use actix_web::{Error, web}; | |
5 | +use actix_web::{Error as ActixError, web, http::StatusCode}; | |
6 | 6 | use anyhow::Result; |
7 | 7 | use serde::{Deserialize, Serialize}; |
8 | 8 | |
9 | -#[derive(Debug, Deserialize, Serialize)] | |
9 | +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] | |
10 | 10 | #[serde (rename_all = "lowercase")] |
11 | 11 | pub enum Size { |
12 | 12 | Original, |
13 | 13 | Large, |
14 | 14 | Medium, |
15 | - Small | |
15 | + Small, | |
16 | + Thumbnail | |
16 | 17 | } |
17 | 18 | |
18 | 19 | #[derive(Debug, Deserialize, Serialize)] |
... | ... | @@ -26,7 +27,8 @@ impl Display for Size { |
26 | 27 | Size::Original => "original", |
27 | 28 | Size::Large => "large", |
28 | 29 | Size::Medium => "medium", |
29 | - Size::Small => "small" | |
30 | + Size::Small => "small", | |
31 | + Size::Thumbnail => "thumbnail" | |
30 | 32 | }; |
31 | 33 | |
32 | 34 | write!(f, "{}", size_str) |
... | ... | @@ -36,16 +38,21 @@ impl Display for Size { |
36 | 38 | pub async fn get_image( app_data: web::Data<AppData> |
37 | 39 | , ident: web::Path<i32> |
38 | 40 | , size: web::Query<SizeQuery> |
39 | - ) -> Result<actix_files::NamedFile, Error> | |
41 | + ) -> Result<actix_files::NamedFile, ActixError> | |
40 | 42 | { |
41 | 43 | let pool = app_data.database_pool.clone(); |
42 | 44 | let ident = ident.into_inner(); |
43 | - let size = size.into_inner().size.or(Some(Size::Large)).unwrap(); | |
45 | + let size = size.into_inner().size.unwrap_or(Size::Large); | |
44 | 46 | |
45 | 47 | let image = web::block(move || image::get_image(pool, ident)).await?; |
46 | - let path = image.path(size); | |
48 | + let path = image.clone().context() | |
49 | + . path(size).await | |
50 | + . ok_or(Error::new( "Image not ready" | |
51 | + , StatusCode::SERVICE_UNAVAILABLE ))?; | |
47 | 52 | |
48 | 53 | Ok( actix_files::NamedFile::open(path)? |
49 | - . set_content_type(image.mime_type.parse().unwrap()) | |
54 | + . set_content_type( image.mime_type | |
55 | + . parse() | |
56 | + . map_err(|e| Error::from(e))? ) | |
50 | 57 | . disable_content_disposition() ) |
51 | 58 | } | ... | ... |
1 | -use actix_web::{Error, HttpResponse, web}; | |
1 | +use actix_web::{Error as ActixError, HttpResponse, web, http::StatusCode}; | |
2 | 2 | use anyhow::Result; |
3 | 3 | use async_std::fs::DirBuilder; |
4 | 4 | use futures::{stream::StreamExt, AsyncWriteExt}; |
5 | 5 | use async_std::fs::OpenOptions; |
6 | 6 | |
7 | -use crate::{AppData, models::image::{Upload, self}, upload_filename}; | |
7 | +use crate::{AppData, models::image::{Upload, self}, error::Error}; | |
8 | 8 | use crate::config::CONFIG; |
9 | -use std::convert::TryFrom; | |
10 | 9 | |
11 | 10 | pub async fn upload( app_data :web::Data<AppData> |
12 | 11 | , mut body :web::Payload |
13 | - , request :web::HttpRequest ) -> Result<HttpResponse, Error> | |
12 | + , request :web::HttpRequest | |
13 | + ) -> Result<HttpResponse, ActixError> | |
14 | 14 | { |
15 | 15 | let pool = app_data.database_pool.clone(); |
16 | 16 | let worker = app_data.tx_upload_worker.clone(); |
17 | 17 | |
18 | 18 | let upload_uuid = Some(uuid::Uuid::new_v4().as_bytes().to_vec()); |
19 | 19 | let size = request.headers().get("content-length") |
20 | - . and_then(|h| Some(h.to_str().unwrap().parse::<i32>())) | |
21 | - . unwrap().unwrap(); | |
22 | - let mime_type = String::from( request.headers().get("content-type") | |
23 | - . and_then(|h| Some(h.to_str().unwrap())) | |
24 | - . unwrap() ); | |
20 | + . and_then(|h| h.to_str().ok()) | |
21 | + . and_then(|s| s.parse::<i32>().ok()); | |
22 | + let mime_type = request.headers().get("content-type") | |
23 | + . and_then(|h| h.to_str().ok()) | |
24 | + . ok_or(Error::new( "Upload expects content-type" | |
25 | + , StatusCode::BAD_REQUEST ))?; | |
26 | + let mime_type = String::from(mime_type); | |
25 | 27 | |
26 | - let upload = Upload { | |
28 | + let mut upload = Upload { | |
27 | 29 | upload_uuid, |
28 | - size, | |
30 | + size: size.unwrap_or(0), | |
29 | 31 | mime_type |
30 | 32 | }; |
31 | 33 | |
... | ... | @@ -33,24 +35,33 @@ pub async fn upload( app_data :web::Data<AppData> |
33 | 35 | . create(CONFIG.upload_dir()) |
34 | 36 | . await?; |
35 | 37 | |
36 | - let upload_filename = upload_filename!(upload).unwrap(); | |
38 | + let mut upload_size = 0; | |
39 | + let upload_filename = upload.upload_path().await.unwrap(); | |
37 | 40 | let mut output = OpenOptions::new(); |
38 | 41 | let mut output = output |
39 | 42 | . create(true) |
40 | 43 | . write(true) |
41 | 44 | . open(&upload_filename).await?; |
42 | 45 | while let Some(item) = body.next().await { |
43 | - output.write_all(&item?).await?; | |
46 | + let item = item?; | |
47 | + output.write_all(&item).await?; | |
48 | + upload_size += item.len() as i32; | |
49 | + } | |
50 | + output.flush().await?; | |
51 | + | |
52 | + if let Some(size) = size { | |
53 | + if size != upload_size { | |
54 | + Err(Error::new( "Did not receive expected size" | |
55 | + , StatusCode::BAD_REQUEST ))? | |
56 | + } | |
57 | + } else { | |
58 | + upload.size = upload_size; | |
44 | 59 | } |
45 | - output.flush().await.unwrap(); | |
46 | 60 | |
47 | 61 | let pool_for_worker = pool.clone(); |
48 | - Ok( match web::block(move || image::upload(pool, upload)).await { | |
49 | - Ok(image) => { | |
50 | - // TODO handle this as error response... | |
51 | - worker.send((pool_for_worker, image.clone())).await.unwrap(); | |
52 | - HttpResponse::Ok().json(image) | |
53 | - }, | |
54 | - Err(_) => HttpResponse::InternalServerError().finish() | |
55 | - } ) | |
62 | + let image = web::block(move || image::upload(pool, upload)).await?; | |
63 | + worker . send((pool_for_worker, image.clone())).await | |
64 | + . map_err(|e| Error::from(e))?; | |
65 | + | |
66 | + Ok(HttpResponse::Accepted().json(image)) | |
56 | 67 | } | ... | ... |
1 | -use std::{ io::{SeekFrom, ErrorKind} | |
2 | - , sync::Arc | |
3 | - , convert::TryFrom }; | |
4 | -use actix_web::web; | |
5 | -use async_std::{ fs::{File, DirBuilder, copy, metadata, remove_file} | |
1 | +use std::{ io::ErrorKind | |
2 | + , sync::Arc }; | |
3 | +use actix_web::{web, http::StatusCode}; | |
4 | +use async_std::{ fs::{DirBuilder, copy, metadata, remove_file} | |
6 | 5 | , channel::{Sender, Receiver, bounded} |
7 | - , path::PathBuf | |
8 | - , io::Result, task::spawn_blocking }; | |
9 | -use futures::{ AsyncSeekExt, AsyncReadExt, FutureExt, StreamExt, select | |
6 | + , task::spawn_blocking }; | |
7 | +use futures::{ FutureExt, StreamExt, select | |
10 | 8 | , stream::FuturesUnordered}; |
9 | +use rexiv2::Metadata; | |
10 | +use steganography::encoder::Encoder; | |
11 | 11 | |
12 | -use crate::{ models::image::{Image, finalize} | |
13 | - , upload_filename | |
12 | +use crate::{ models::image::{Image, finalize, ImageContext} | |
14 | 13 | , config::CONFIG |
15 | 14 | , Pool |
16 | 15 | , routes::image::Size |
17 | - , uuid::Uuid }; | |
16 | + , error::Error }; | |
18 | 17 | |
19 | 18 | use image::{ io::Reader as ImageReader |
20 | 19 | , GenericImageView |
21 | - , imageops::FilterType::Lanczos3 | |
22 | - , ImageFormat::Jpeg }; | |
20 | + , imageops::{FilterType::Lanczos3, overlay} | |
21 | + , ImageFormat::Jpeg, DynamicImage }; | |
23 | 22 | |
24 | 23 | pub fn launch() -> Sender<(Arc<Pool>, Image)> { |
25 | 24 | let (tx_upload_worker, rx_upload_worker) |
... | ... | @@ -48,105 +47,99 @@ pub fn launch() -> Sender<(Arc<Pool>, Image)> { |
48 | 47 | tx_upload_worker |
49 | 48 | } |
50 | 49 | |
50 | +async fn store_original(context :&mut ImageContext) -> Result<(), Error> { | |
51 | + let upload_path = context | |
52 | + . upload_path().await | |
53 | + . ok_or(Error::new( "Can't retreive upload path" | |
54 | + , StatusCode::INTERNAL_SERVER_ERROR ))? | |
55 | + . to_owned(); | |
56 | + let original_path = context | |
57 | + . path(Size::Original).await | |
58 | + . ok_or(Error::new( "No path for given size" | |
59 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
60 | + | |
61 | + match metadata(&original_path).await { | |
62 | + Err(e) if e.kind() == ErrorKind::NotFound => { | |
63 | + copy(&upload_path, &original_path).await?; | |
64 | + remove_file(&upload_path).await?; | |
65 | + Ok(()) | |
66 | + }, | |
67 | + Err(e) => Err(e)?, | |
68 | + Ok(_) => Ok(()), | |
69 | + } | |
70 | +} | |
51 | 71 | |
52 | -async fn worker(pool :Arc<Pool>, mut image :Image) { | |
53 | - let upload_filename = upload_filename!(image).unwrap(); | |
54 | - let mut f = File::open(&upload_filename).await.unwrap(); | |
72 | +async fn load_original(context :&mut ImageContext) -> Result<DynamicImage, Error> { | |
73 | + let original_path = context | |
74 | + . path(Size::Original).await | |
75 | + . ok_or(Error::new( "Unable to load original image" | |
76 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
55 | 77 | |
56 | - let mut buf = vec!['.' as u8; 3 * 3 * 4096]; | |
57 | - get_sample(&mut f, buf.as_mut()).await.unwrap(); | |
58 | - let uuid = Uuid::get(CONFIG.namespace(), buf.as_mut()); | |
59 | - let uuid_string = format!("{}", uuid); | |
78 | + spawn_blocking(move || -> Result<DynamicImage, Error> { | |
79 | + Ok(ImageReader::open(&original_path)? . with_guessed_format()? | |
80 | + . decode()?) | |
81 | + }).await | |
82 | +} | |
60 | 83 | |
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(); | |
84 | +async fn save_resized( original :&DynamicImage | |
85 | + , context :&mut ImageContext | |
86 | + , size :Size ) -> Result<(), Error> { | |
87 | + let width = CONFIG.width(size) | |
88 | + . ok_or(Error::new( "Can't get width for size" | |
89 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
90 | + let height = CONFIG.height(size) | |
91 | + . ok_or(Error::new( "Can't get height for size" | |
92 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
93 | + | |
94 | + let path = context.path(size).await | |
95 | + . ok_or(Error::new( "Can't get path for size" | |
96 | + , StatusCode::INTERNAL_SERVER_ERROR ))?; | |
97 | + | |
98 | + let original = original.to_owned(); | |
99 | + | |
100 | + spawn_blocking(move || -> Result<(), Error> { | |
101 | + let mut scaled = original.resize(width, height, Lanczos3); | |
102 | + overlay(&mut scaled, CONFIG.copyright_image(), 0_u32, 0_u32); | |
103 | + | |
104 | + if let Size::Thumbnail = size { | |
105 | + scaled.save_with_format(&path, Jpeg)?; | |
106 | + } else { | |
107 | + let stegonography = CONFIG.copyright_steganography().as_bytes(); | |
108 | + let encoder = Encoder::new(stegonography, scaled); | |
109 | + let scaled = encoder.encode_alpha(); | |
110 | + scaled.save_with_format(&path, Jpeg)?; | |
111 | + } | |
66 | 112 | |
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]); | |
113 | + let exiv = Metadata::new_from_path(&path)?; | |
114 | + exiv.set_tag_string("Exif.Image.Copyright", CONFIG.copyright_exiv())?; | |
115 | + exiv.save_to_file(&path)?; | |
72 | 116 | |
73 | - DirBuilder::new() . recursive(true) | |
74 | - . create(&$p) | |
75 | - . await | |
76 | - . unwrap(); | |
117 | + Ok(()) | |
118 | + }).await | |
119 | +} | |
77 | 120 | |
78 | - $p.push(&format!("{}_{}", &uuid_string, $n)); | |
79 | - ) | |
80 | - } | |
121 | +async fn worker(pool :Arc<Pool>, image :Image) -> Result<(), Error> { | |
122 | + let mut context = image.context(); | |
81 | 123 | |
82 | - prepare!(orig_path, Size::Original); | |
83 | - prepare!(large_path, Size::Large); | |
84 | - prepare!(medium_path, Size::Medium); | |
85 | - prepare!(small_path, Size::Small); | |
124 | + DirBuilder::new() . recursive(true) | |
125 | + . create(context.base_path().await.unwrap()) | |
126 | + . await | |
127 | + . unwrap(); | |
86 | 128 | |
87 | - image.upload_uuid = None; | |
88 | - image.uuid = Some(uuid.0.as_bytes().to_vec()); | |
129 | + store_original(&mut context).await.unwrap(); | |
89 | 130 | |
90 | - match metadata(&orig_path).await { | |
91 | - Err(e) if e.kind() == ErrorKind::NotFound => { | |
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); | |
115 | - }, | |
116 | - Err(e) => { | |
117 | - let e :Result<()> = Err(e); | |
118 | - e.unwrap(); | |
119 | - }, | |
120 | - Ok(_) => {}, | |
121 | - } | |
122 | - | |
123 | - remove_file(&upload_filename).await.unwrap(); | |
124 | - web::block(move || finalize(pool, image)).await.unwrap(); | |
125 | -} | |
131 | + let original = load_original(&mut context).await.unwrap(); | |
132 | + let (dim_x, dim_y) = original.dimensions(); | |
126 | 133 | |
127 | -async fn read_at( f :&mut File | |
128 | - , pos :SeekFrom | |
129 | - , buf :&mut [u8]) -> std::io::Result<()> { | |
130 | - f.seek(pos).await?; | |
131 | - f.read_exact(buf).await | |
132 | -} | |
134 | + context.image.dim_x = Some(dim_x as i32); | |
135 | + context.image.dim_y = Some(dim_y as i32); | |
133 | 136 | |
134 | -async fn get_sample( f :&mut File | |
135 | - , buf :&mut [u8]) -> std::io::Result<()> { | |
136 | - let file_len = f.metadata().await?.len(); | |
137 | - let chunk_size = buf.len() / 3; | |
137 | + save_resized(&original, &mut context, Size::Large).await?; | |
138 | + save_resized(&original, &mut context, Size::Medium).await?; | |
139 | + save_resized(&original, &mut context, Size::Small).await?; | |
140 | + save_resized(&original, &mut context, Size::Thumbnail).await?; | |
138 | 141 | |
139 | - read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?; | |
140 | - if file_len >= 2 * chunk_size as u64 { | |
141 | - read_at( f | |
142 | - , SeekFrom::End(-(chunk_size as i64)) | |
143 | - , &mut buf[2*chunk_size..]).await?; | |
144 | - } | |
145 | - if file_len >= 3 * chunk_size as u64 { | |
146 | - read_at( f | |
147 | - , SeekFrom::Start((file_len-chunk_size as u64) / 2) | |
148 | - , &mut buf[chunk_size..2*chunk_size]).await?; | |
149 | - } | |
142 | + web::block(move || finalize(pool, context.image)).await?; | |
150 | 143 | |
151 | 144 | Ok(()) |
152 | 145 | } | ... | ... |
... | ... | @@ -30,3 +30,11 @@ impl TryFrom<&[u8]> for Uuid { |
30 | 30 | Ok(Self(uuid::Uuid::from_slice(value)?)) |
31 | 31 | } |
32 | 32 | } |
33 | + | |
34 | +impl TryFrom<Vec<u8>> for Uuid { | |
35 | + type Error = Error; | |
36 | + | |
37 | + fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> { | |
38 | + Ok(Self(uuid::Uuid::from_slice(value.as_slice())?)) | |
39 | + } | |
40 | +} | ... | ... |
Please
register
or
login
to post a comment