Commit a54e9551c6e939b713098f90e152f940f973ff28

Authored by Georg Hopp
1 parent 18bb86b1

Copyrights and error handling.

Lots of removed unwraps and added copyright info for uploaded
images.
@@ -2,6 +2,7 @@ Cargo.lock @@ -2,6 +2,7 @@ Cargo.lock
2 **/*.rs.bk 2 **/*.rs.bk
3 3
4 **/target 4 **/target
  5 +/data/copyright.png
5 /pkg 6 /pkg
6 /static/ui/ 7 /static/ui/
7 /var 8 /var
@@ -4,6 +4,3 @@ members = [ "common", "server" ] @@ -4,6 +4,3 @@ members = [ "common", "server" ]
4 [profile.release] 4 [profile.release]
5 lto = true 5 lto = true
6 opt-level = "s" 6 opt-level = "s"
7 -  
8 -[patch.crates-io]  
9 -diffy = { path = "./target/patch/diffy-0.2.1" }  
@@ -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"
  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