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,7 +39,7 @@ start: | ||
39 | cargo watch -i static/ -i var/ \ | 39 | cargo watch -i static/ -i var/ \ |
40 | -s "PROFILE=$(PROFILE) make run" | 40 | -s "PROFILE=$(PROFILE) make run" |
41 | 41 | ||
42 | -run: build wasm | 42 | +run: build wasm data/copyright.png |
43 | $(call msgnl,RUN SERVER) | 43 | $(call msgnl,RUN SERVER) |
44 | @PROFILE=$(PROFILE) cargo run $(CARGO_PROFILE) --bin artshop-server | 44 | @PROFILE=$(PROFILE) cargo run $(CARGO_PROFILE) --bin artshop-server |
45 | 45 | ||
@@ -54,6 +54,8 @@ clean: | @@ -54,6 +54,8 @@ clean: | ||
54 | @rm -Rf ./static/ui | 54 | @rm -Rf ./static/ui |
55 | $(call msgnl,CLEAN INSTALLED WASM) | 55 | $(call msgnl,CLEAN INSTALLED WASM) |
56 | @pushd ui; PROFILE=$(PROFILE) cargo clean; popd | 56 | @pushd ui; PROFILE=$(PROFILE) cargo clean; popd |
57 | + $(call msgnl,CLEAN COPYRIGHT PNG) | ||
58 | + @rm -Rf ./data/copyright.png | ||
57 | 59 | ||
58 | $(WASM_TARGET): $(WASM_SOURCES) | 60 | $(WASM_TARGET): $(WASM_SOURCES) |
59 | $(call msgnl,BUILD WASM UI) | 61 | $(call msgnl,BUILD WASM UI) |
@@ -71,6 +73,12 @@ $(SERVER_TARGET): $(SERVER_SOURCES) | @@ -71,6 +73,12 @@ $(SERVER_TARGET): $(SERVER_SOURCES) | ||
71 | $(call msgnl,BUILD SERVER) | 73 | $(call msgnl,BUILD SERVER) |
72 | @PROFILE=$(PROFILE) cargo build $(CARGO_PROFILE) --bin artshop-server | 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 | release: | 82 | release: |
75 | docker build -t artshop -f build/Dockerfile . | 83 | docker build -t artshop -f build/Dockerfile . |
76 | 84 | ||
@@ -99,6 +107,8 @@ downdb: | @@ -99,6 +107,8 @@ downdb: | ||
99 | @docker stop mariadb-dev||true | 107 | @docker stop mariadb-dev||true |
100 | $(call msgnl,REMOVE DB CONTAINER) | 108 | $(call msgnl,REMOVE DB CONTAINER) |
101 | @docker rm -v mariadb-dev||true | 109 | @docker rm -v mariadb-dev||true |
110 | + $(call msgnl,REMOVE IMAGES) | ||
111 | + @rm -Rf var/lib/artshop/images | ||
102 | 112 | ||
103 | devdb: | 113 | devdb: |
104 | $(call msgnl,CREATE DB CONTAINER) | 114 | $(call msgnl,CREATE DB CONTAINER) |
@@ -5,5 +5,17 @@ namespace = "artshop.shome.steffers.org" | @@ -5,5 +5,17 @@ namespace = "artshop.shome.steffers.org" | ||
5 | # url = "./var/lib/artshop/database" | 5 | # url = "./var/lib/artshop/database" |
6 | 6 | ||
7 | [locations] | 7 | [locations] |
8 | -upload = "/tmp/artshop/uploads" | 8 | +data = "./data" |
9 | images = "./var/lib/artshop/images" | 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,3 +70,12 @@ and parallel: | ||
70 | https://api.github.com/users/xxxxxxx/events/public | 70 | https://api.github.com/users/xxxxxxx/events/public |
71 | 71 | ||
72 | where xxxxxxx is the github username. Then search for email. | 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,7 +16,7 @@ artshop-common = { path = "../common" } | ||
16 | async-std = { version = "^1.10", features = ["unstable"] } | 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.1" | 19 | +diffy = "0.2.2" |
20 | dotenv = "0.15.0" | 20 | dotenv = "0.15.0" |
21 | flate2 = "^1.0" | 21 | flate2 = "^1.0" |
22 | futures = "^0.3" | 22 | futures = "^0.3" |
@@ -26,13 +26,10 @@ listenfd = "0.3" | @@ -26,13 +26,10 @@ listenfd = "0.3" | ||
26 | once_cell = "^1.9" | 26 | once_cell = "^1.9" |
27 | mime = "^0.3" | 27 | mime = "^0.3" |
28 | r2d2 = "0.8.9" | 28 | r2d2 = "0.8.9" |
29 | +rexiv2 = "^0.9" | ||
29 | serde = { version = "^1.0", features = ["derive"] } | 30 | serde = { version = "^1.0", features = ["derive"] } |
30 | serde_derive = "1.0" | 31 | serde_derive = "1.0" |
31 | serde_json = "1.0" | 32 | serde_json = "1.0" |
33 | +steganography = { git = "https://github.com/teovoinea/steganography" } | ||
32 | toml = "^0.5" | 34 | toml = "^0.5" |
33 | uuid = { version = "^0.8", features = ["v4", "v5"] } | 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 | use std::fs::File; | 1 | use std::fs::File; |
2 | use std::io::Read; | 2 | use std::io::Read; |
3 | +use image::{ DynamicImage, io::Reader as ImageReader }; | ||
3 | use once_cell::sync::Lazy; | 4 | use once_cell::sync::Lazy; |
4 | use serde::Deserialize; | 5 | use serde::Deserialize; |
6 | +use anyhow::Result; | ||
7 | +use crate::routes::image::Size as ImageSize; | ||
5 | 8 | ||
6 | #[derive(Debug, Deserialize)] | 9 | #[derive(Debug, Deserialize)] |
7 | struct Database { url :Option<String> } | 10 | struct Database { url :Option<String> } |
8 | 11 | ||
9 | #[derive(Debug, Deserialize)] | 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 | #[derive(Debug, Deserialize)] | 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 | impl Config { | 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 | let mut buffer = vec![]; | 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 | Some(url) => Some(url), | 56 | Some(url) => Some(url), |
32 | None => std::env::var("DATABASE_URL").ok() | 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 | pub fn namespace(&self) -> &str { | 67 | pub fn namespace(&self) -> &str { |
39 | - self.namespace.as_str() | 68 | + self.config_file.namespace.as_str() |
40 | } | 69 | } |
41 | 70 | ||
42 | pub fn upload_dir(&self) -> &str { | 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 | pub fn images_dir(&self) -> &str { | 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 | use diesel::result; | 6 | use diesel::result; |
4 | -use diffy::ParsePatchError; | 7 | +use diffy::{ParsePatchError, ApplyError}; |
8 | +use image::ImageError; | ||
9 | +use mime::FromStrError; | ||
5 | use r2d2; | 10 | use r2d2; |
11 | +use rexiv2::Rexiv2Error; | ||
12 | + | ||
13 | +use crate::{Pool, models::image::Image}; | ||
6 | 14 | ||
7 | type ParentError = Option<Pin<Box<dyn std::error::Error>>>; | 15 | type ParentError = Option<Pin<Box<dyn std::error::Error>>>; |
8 | 16 | ||
9 | #[derive(Debug)] | 17 | #[derive(Debug)] |
10 | pub struct Error { | 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 | unsafe impl Send for Error {} | 24 | unsafe impl Send for Error {} |
@@ -22,19 +31,36 @@ impl std::error::Error 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 | impl Display for Error { | 40 | impl Display for Error { |
26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
27 | match self { | 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,6 +68,7 @@ impl From<result::Error> for Error { | ||
42 | fn from(source: result::Error) -> Self { | 68 | fn from(source: result::Error) -> Self { |
43 | Self { source: Some(Box::pin(source)) | 69 | Self { source: Some(Box::pin(source)) |
44 | , message: String::from("Diesel Result Error") | 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,7 +76,8 @@ impl From<result::Error> for Error { | ||
49 | impl From<r2d2::Error> for Error { | 76 | impl From<r2d2::Error> for Error { |
50 | fn from(source: r2d2::Error) -> Self { | 77 | fn from(source: r2d2::Error) -> Self { |
51 | Self { source: Some(Box::pin(source)) | 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,6 +86,7 @@ impl From<std::io::Error> for Error { | ||
58 | fn from(source: std::io::Error) -> Self { | 86 | fn from(source: std::io::Error) -> Self { |
59 | Self { source: Some(Box::pin(source)) | 87 | Self { source: Some(Box::pin(source)) |
60 | , message: String::from("IO Error") | 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,7 +94,8 @@ impl From<std::io::Error> for Error { | ||
65 | impl From<std::str::Utf8Error> for Error { | 94 | impl From<std::str::Utf8Error> for Error { |
66 | fn from(source: std::str::Utf8Error) -> Self { | 95 | fn from(source: std::str::Utf8Error) -> Self { |
67 | Self { source: Some(Box::pin(source)) | 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,7 +103,17 @@ impl From<std::str::Utf8Error> for Error { | ||
73 | impl From<ParsePatchError> for Error { | 103 | impl From<ParsePatchError> for Error { |
74 | fn from(source: ParsePatchError) -> Self { | 104 | fn from(source: ParsePatchError) -> Self { |
75 | Self { source: Some(Box::pin(source)) | 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,6 +122,61 @@ impl From<uuid::Error> for Error { | ||
82 | fn from(source: uuid::Error) -> Self { | 122 | fn from(source: uuid::Error) -> Self { |
83 | Self { source: Some(Box::pin(source)) | 123 | Self { source: Some(Box::pin(source)) |
84 | , message: String::from("UUID error") | 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 | use std::convert::TryFrom; | 1 | use std::convert::TryFrom; |
2 | +use std::io::SeekFrom; | ||
2 | use std::sync::Arc; | 3 | use std::sync::Arc; |
3 | 4 | ||
4 | use crate::error::*; | 5 | use crate::error::*; |
5 | use crate::routes::image::Size; | 6 | use crate::routes::image::Size; |
6 | use crate::uuid::Uuid; | 7 | use crate::uuid::Uuid; |
7 | use crate::{schema::*, Pool, config::CONFIG}; | 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 | use async_std::path::PathBuf; | 12 | use async_std::path::PathBuf; |
9 | use diesel::{Connection, insert_into, delete, update}; | 13 | use diesel::{Connection, insert_into, delete, update}; |
10 | use diesel::prelude::*; | 14 | use diesel::prelude::*; |
11 | use serde::{Deserialize, Serialize}; | 15 | use serde::{Deserialize, Serialize}; |
12 | 16 | ||
17 | +use async_std::io::ReadExt; | ||
18 | + | ||
13 | #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Identifiable)] | 19 | #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Identifiable)] |
14 | pub struct Image { | 20 | pub struct Image { |
15 | pub id :i32, | 21 | pub id :i32, |
@@ -54,6 +60,13 @@ pub struct ImagePatch { | @@ -54,6 +60,13 @@ pub struct ImagePatch { | ||
54 | pub date_updated :String | 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 | impl From<Image> for ImagePatch { | 70 | impl From<Image> for ImagePatch { |
58 | fn from(image: Image) -> Self { | 71 | fn from(image: Image) -> Self { |
59 | let now = chrono::Local::now().naive_local(); | 72 | let now = chrono::Local::now().naive_local(); |
@@ -69,44 +82,117 @@ impl From<Image> for ImagePatch { | @@ -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 | impl Image { | 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 | let uuid_string = format!("{}", uuid); | 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,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 | use crate::schema::images::dsl::*; | 223 | use crate::schema::images::dsl::*; |
138 | 224 | ||
139 | let db_connection = pool.get()?; | 225 | let db_connection = pool.get()?; |
@@ -154,8 +240,8 @@ pub(crate) fn finalize( pool: Arc<Pool> | @@ -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 | use crate::schema::images::dsl::*; | 246 | use crate::schema::images::dsl::*; |
161 | 247 |
@@ -162,7 +162,7 @@ pub(crate) fn get_markdown( pool: Arc<Pool> | @@ -162,7 +162,7 @@ pub(crate) fn get_markdown( pool: Arc<Pool> | ||
162 | let patch_data = patch.get_diff_as_string()?; | 162 | let patch_data = patch.get_diff_as_string()?; |
163 | let decomp = Patch::from_str(&patch_data)?; | 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 | markdown.date_updated = patch.date_created; | 166 | markdown.date_updated = patch.date_created; |
167 | } | 167 | } |
168 | }; | 168 | }; |
@@ -211,8 +211,8 @@ pub(crate) fn update_markdown( pool: Arc<Pool> | @@ -211,8 +211,8 @@ pub(crate) fn update_markdown( pool: Arc<Pool> | ||
211 | let patch = format!( "{}", create_patch( item.content.as_str() | 211 | let patch = format!( "{}", create_patch( item.content.as_str() |
212 | , markdown.content.as_str() )); | 212 | , markdown.content.as_str() )); |
213 | let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best()); | 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 | let last_diff = match markdown_diffs . filter(markdown_id.eq(markdown.id)) | 217 | let last_diff = match markdown_diffs . filter(markdown_id.eq(markdown.id)) |
218 | . order(diff_id.desc()) | 218 | . order(diff_id.desc()) |
@@ -240,7 +240,7 @@ pub(crate) fn update_markdown( pool: Arc<Pool> | @@ -240,7 +240,7 @@ pub(crate) fn update_markdown( pool: Arc<Pool> | ||
240 | update(&markdown).set(MarkdownChange::from(&item)).execute(&db_connection)?; | 240 | update(&markdown).set(MarkdownChange::from(&item)).execute(&db_connection)?; |
241 | 241 | ||
242 | Ok(()) | 242 | Ok(()) |
243 | - }).unwrap(); | 243 | + })?; |
244 | 244 | ||
245 | markdown.name = item.name; | 245 | markdown.name = item.name; |
246 | markdown.content = item.content; | 246 | markdown.content = item.content; |
1 | use std::fmt::Display; | 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 | use anyhow::Result; | 6 | use anyhow::Result; |
7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
8 | 8 | ||
9 | -#[derive(Debug, Deserialize, Serialize)] | 9 | +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] |
10 | #[serde (rename_all = "lowercase")] | 10 | #[serde (rename_all = "lowercase")] |
11 | pub enum Size { | 11 | pub enum Size { |
12 | Original, | 12 | Original, |
13 | Large, | 13 | Large, |
14 | Medium, | 14 | Medium, |
15 | - Small | 15 | + Small, |
16 | + Thumbnail | ||
16 | } | 17 | } |
17 | 18 | ||
18 | #[derive(Debug, Deserialize, Serialize)] | 19 | #[derive(Debug, Deserialize, Serialize)] |
@@ -26,7 +27,8 @@ impl Display for Size { | @@ -26,7 +27,8 @@ impl Display for Size { | ||
26 | Size::Original => "original", | 27 | Size::Original => "original", |
27 | Size::Large => "large", | 28 | Size::Large => "large", |
28 | Size::Medium => "medium", | 29 | Size::Medium => "medium", |
29 | - Size::Small => "small" | 30 | + Size::Small => "small", |
31 | + Size::Thumbnail => "thumbnail" | ||
30 | }; | 32 | }; |
31 | 33 | ||
32 | write!(f, "{}", size_str) | 34 | write!(f, "{}", size_str) |
@@ -36,16 +38,21 @@ impl Display for Size { | @@ -36,16 +38,21 @@ impl Display for Size { | ||
36 | pub async fn get_image( app_data: web::Data<AppData> | 38 | pub async fn get_image( app_data: web::Data<AppData> |
37 | , ident: web::Path<i32> | 39 | , ident: web::Path<i32> |
38 | , size: web::Query<SizeQuery> | 40 | , size: web::Query<SizeQuery> |
39 | - ) -> Result<actix_files::NamedFile, Error> | 41 | + ) -> Result<actix_files::NamedFile, ActixError> |
40 | { | 42 | { |
41 | let pool = app_data.database_pool.clone(); | 43 | let pool = app_data.database_pool.clone(); |
42 | let ident = ident.into_inner(); | 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 | let image = web::block(move || image::get_image(pool, ident)).await?; | 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 | Ok( actix_files::NamedFile::open(path)? | 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 | . disable_content_disposition() ) | 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 | use anyhow::Result; | 2 | use anyhow::Result; |
3 | use async_std::fs::DirBuilder; | 3 | use async_std::fs::DirBuilder; |
4 | use futures::{stream::StreamExt, AsyncWriteExt}; | 4 | use futures::{stream::StreamExt, AsyncWriteExt}; |
5 | use async_std::fs::OpenOptions; | 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 | use crate::config::CONFIG; | 8 | use crate::config::CONFIG; |
9 | -use std::convert::TryFrom; | ||
10 | 9 | ||
11 | pub async fn upload( app_data :web::Data<AppData> | 10 | pub async fn upload( app_data :web::Data<AppData> |
12 | , mut body :web::Payload | 11 | , mut body :web::Payload |
13 | - , request :web::HttpRequest ) -> Result<HttpResponse, Error> | 12 | + , request :web::HttpRequest |
13 | + ) -> Result<HttpResponse, ActixError> | ||
14 | { | 14 | { |
15 | let pool = app_data.database_pool.clone(); | 15 | let pool = app_data.database_pool.clone(); |
16 | let worker = app_data.tx_upload_worker.clone(); | 16 | let worker = app_data.tx_upload_worker.clone(); |
17 | 17 | ||
18 | let upload_uuid = Some(uuid::Uuid::new_v4().as_bytes().to_vec()); | 18 | let upload_uuid = Some(uuid::Uuid::new_v4().as_bytes().to_vec()); |
19 | let size = request.headers().get("content-length") | 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 | upload_uuid, | 29 | upload_uuid, |
28 | - size, | 30 | + size: size.unwrap_or(0), |
29 | mime_type | 31 | mime_type |
30 | }; | 32 | }; |
31 | 33 | ||
@@ -33,24 +35,33 @@ pub async fn upload( app_data :web::Data<AppData> | @@ -33,24 +35,33 @@ pub async fn upload( app_data :web::Data<AppData> | ||
33 | . create(CONFIG.upload_dir()) | 35 | . create(CONFIG.upload_dir()) |
34 | . await?; | 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 | let mut output = OpenOptions::new(); | 40 | let mut output = OpenOptions::new(); |
38 | let mut output = output | 41 | let mut output = output |
39 | . create(true) | 42 | . create(true) |
40 | . write(true) | 43 | . write(true) |
41 | . open(&upload_filename).await?; | 44 | . open(&upload_filename).await?; |
42 | while let Some(item) = body.next().await { | 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 | let pool_for_worker = pool.clone(); | 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 | , channel::{Sender, Receiver, bounded} | 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 | , stream::FuturesUnordered}; | 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 | , config::CONFIG | 13 | , config::CONFIG |
15 | , Pool | 14 | , Pool |
16 | , routes::image::Size | 15 | , routes::image::Size |
17 | - , uuid::Uuid }; | 16 | + , error::Error }; |
18 | 17 | ||
19 | use image::{ io::Reader as ImageReader | 18 | use image::{ io::Reader as ImageReader |
20 | , GenericImageView | 19 | , GenericImageView |
21 | - , imageops::FilterType::Lanczos3 | ||
22 | - , ImageFormat::Jpeg }; | 20 | + , imageops::{FilterType::Lanczos3, overlay} |
21 | + , ImageFormat::Jpeg, DynamicImage }; | ||
23 | 22 | ||
24 | pub fn launch() -> Sender<(Arc<Pool>, Image)> { | 23 | pub fn launch() -> Sender<(Arc<Pool>, Image)> { |
25 | let (tx_upload_worker, rx_upload_worker) | 24 | let (tx_upload_worker, rx_upload_worker) |
@@ -48,105 +47,99 @@ pub fn launch() -> Sender<(Arc<Pool>, Image)> { | @@ -48,105 +47,99 @@ pub fn launch() -> Sender<(Arc<Pool>, Image)> { | ||
48 | tx_upload_worker | 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 | Ok(()) | 144 | Ok(()) |
152 | } | 145 | } |
@@ -30,3 +30,11 @@ impl TryFrom<&[u8]> for Uuid { | @@ -30,3 +30,11 @@ impl TryFrom<&[u8]> for Uuid { | ||
30 | Ok(Self(uuid::Uuid::from_slice(value)?)) | 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 | +} |
@@ -21,7 +21,7 @@ impl UploadApi { | @@ -21,7 +21,7 @@ impl UploadApi { | ||
21 | , upload.size() | 21 | , upload.size() |
22 | , upload.data() ).await?; | 22 | , upload.data() ).await?; |
23 | match response.status() { | 23 | match response.status() { |
24 | - 200 => Ok(self), | 24 | + 202 => Ok(self), |
25 | status => Err(Self::status_error(status)), | 25 | status => Err(Self::status_error(status)), |
26 | } | 26 | } |
27 | } | 27 | } |
Please
register
or
login
to post a comment