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