Commit 76eebfe80dd18350eacccc38c45e10fe2b441952
1 parent
abe77b68
Added first image processing decoupled from the upload
Showing
6 changed files
with
125 additions
and
30 deletions
| ... | ... | @@ -20,9 +20,10 @@ diffy = "0.2" |
| 20 | 20 | dotenv = "0.15.0" |
| 21 | 21 | flate2 = "^1.0" |
| 22 | 22 | futures = "^0.3" |
| 23 | +futures-util = { version = "0", features = ["std"] } | |
| 23 | 24 | listenfd = "0.3" |
| 24 | 25 | r2d2 = "0.8.9" |
| 25 | 26 | serde = { version = "^1.0", features = ["derive"] } |
| 26 | 27 | serde_derive = "1.0" |
| 27 | 28 | serde_json = "1.0" |
| 28 | -uuid = { version = "^0.8", features = ["v4"] } | |
| 29 | +uuid = { version = "^0.8", features = ["v4", "v5"] } | ... | ... |
| ... | ... | @@ -5,33 +5,94 @@ mod error; |
| 5 | 5 | mod models; |
| 6 | 6 | mod routes; |
| 7 | 7 | mod schema; |
| 8 | +mod uuid; | |
| 8 | 9 | |
| 9 | 10 | use crate::routes::markdown::*; |
| 10 | 11 | use crate::routes::other::*; |
| 11 | 12 | use crate::routes::user::*; |
| 13 | +use crate::uuid::Uuid; | |
| 12 | 14 | |
| 13 | 15 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; |
| 16 | +use async_std::channel::Receiver; | |
| 17 | +use async_std::channel::Sender; | |
| 18 | +use async_std::channel::bounded; | |
| 19 | +use async_std::fs::File; | |
| 14 | 20 | use diesel::r2d2::{self, ConnectionManager}; |
| 15 | 21 | use diesel::SqliteConnection; |
| 22 | +use futures::AsyncReadExt; | |
| 23 | +use futures::AsyncSeekExt; | |
| 16 | 24 | use listenfd::ListenFd; |
| 17 | 25 | use routes::markdown::get_markdown; |
| 18 | 26 | use routes::upload::upload; |
| 27 | +use std::io::SeekFrom; | |
| 28 | +use std::sync::Arc; | |
| 19 | 29 | |
| 20 | 30 | pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; |
| 21 | 31 | |
| 32 | +#[derive(Clone)] | |
| 33 | +pub struct AppData { | |
| 34 | + pub database_pool: Arc<Pool>, | |
| 35 | + pub tx_upload_worker: Sender<String>, | |
| 36 | +} | |
| 37 | + | |
| 38 | +async fn read_at( f :&mut File | |
| 39 | + , pos :SeekFrom | |
| 40 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
| 41 | + f.seek(pos).await?; | |
| 42 | + f.read_exact(buf).await | |
| 43 | +} | |
| 44 | + | |
| 45 | +async fn get_sample( f :&mut File | |
| 46 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
| 47 | + let file_len = f.metadata().await?.len(); | |
| 48 | + let chunk_size = buf.len() / 3; | |
| 49 | + | |
| 50 | + read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?; | |
| 51 | + if file_len >= 2 * chunk_size as u64 { | |
| 52 | + read_at( f | |
| 53 | + , SeekFrom::End(-(chunk_size as i64)) | |
| 54 | + , &mut buf[2*chunk_size..]).await?; | |
| 55 | + } | |
| 56 | + if file_len >= 3 * chunk_size as u64 { | |
| 57 | + read_at( f | |
| 58 | + , SeekFrom::Start((file_len-chunk_size as u64) / 2) | |
| 59 | + , &mut buf[chunk_size..2*chunk_size]).await?; | |
| 60 | + } | |
| 61 | + | |
| 62 | + Ok(()) | |
| 63 | +} | |
| 64 | + | |
| 22 | 65 | #[actix_rt::main] |
| 23 | 66 | async fn main() -> std::io::Result<()> { |
| 24 | 67 | let mut listenfd = ListenFd::from_env(); |
| 25 | 68 | |
| 26 | 69 | dotenv::dotenv().ok(); |
| 27 | 70 | |
| 71 | + let ( tx_upload_worker | |
| 72 | + , rx_upload_worker ) :( Sender<String> | |
| 73 | + , Receiver<String> ) = bounded(32); | |
| 74 | + | |
| 75 | + let _upload_worker = actix_rt::spawn(async move { | |
| 76 | + while let Ok(filename) = rx_upload_worker.recv().await { | |
| 77 | + let mut f = File::open(&filename).await.unwrap(); | |
| 78 | + let mut buf = vec!['.' as u8; 3 * 4096]; | |
| 79 | + get_sample(&mut f, buf.as_mut()).await.unwrap(); | |
| 80 | + println!("[UPLOAD WORKER] filename: {}", filename); | |
| 81 | + println!( "[UPLOAD WORKER] uuid: {}" | |
| 82 | + , Uuid::get("some.unique.namespace", buf.as_mut()) ); | |
| 83 | + } | |
| 84 | + }); | |
| 85 | + | |
| 28 | 86 | let database_url = std::env::var("DATABASE_URL").expect("NOT FOUND"); |
| 29 | 87 | let database_pool = Pool::builder() |
| 30 | 88 | .build(ConnectionManager::new(database_url)) |
| 31 | 89 | .unwrap(); |
| 32 | 90 | |
| 91 | + let database_pool = Arc::new(database_pool); | |
| 92 | + let app_data = AppData { database_pool, tx_upload_worker }; | |
| 93 | + | |
| 33 | 94 | let server = HttpServer::new(move || { |
| 34 | - App::new() . data(database_pool.clone()) | |
| 95 | + App::new() . data(app_data.clone()) | |
| 35 | 96 | . service(actix_files::Files::new("/static", "./static")) |
| 36 | 97 | . service( web::scope("/api/v0") |
| 37 | 98 | . service( web::resource("/upload") | ... | ... |
| 1 | -use crate::models::markdown; | |
| 2 | -use crate::Pool; | |
| 1 | +use crate::{models::markdown, AppData}; | |
| 3 | 2 | |
| 4 | 3 | use actix_web::{Error, HttpResponse, web}; |
| 5 | 4 | use anyhow::Result; |
| ... | ... | @@ -11,36 +10,40 @@ pub struct Patchset { |
| 11 | 10 | patch: Option<i32>, |
| 12 | 11 | } |
| 13 | 12 | |
| 14 | -pub async fn get_markdowns(pool: web::Data<Pool>) | |
| 13 | +pub async fn get_markdowns(app_data: web::Data<AppData>) | |
| 15 | 14 | -> Result<HttpResponse, Error> |
| 16 | 15 | { |
| 17 | - Ok( web::block(move || markdown::get_markdowns(pool.into_inner())) | |
| 16 | + let pool = app_data.database_pool.clone(); | |
| 17 | + | |
| 18 | + Ok( web::block(move || markdown::get_markdowns(pool)) | |
| 18 | 19 | . await |
| 19 | 20 | . map(|markdowns| HttpResponse::Ok().json(markdowns)) |
| 20 | 21 | . map_err(|_| HttpResponse::InternalServerError())? |
| 21 | 22 | ) |
| 22 | 23 | } |
| 23 | 24 | |
| 24 | -pub async fn get_markdown( pool: web::Data<Pool> | |
| 25 | +pub async fn get_markdown( app_data: web::Data<AppData> | |
| 25 | 26 | , name: web::Path<String> |
| 26 | 27 | , patch: web::Query<Patchset> |
| 27 | 28 | ) -> Result<HttpResponse, Error> |
| 28 | 29 | { |
| 29 | - let pool = pool.into_inner(); | |
| 30 | + let pool = app_data.database_pool.clone(); | |
| 30 | 31 | let name = name.into_inner(); |
| 31 | 32 | let patch = patch.into_inner(); |
| 32 | 33 | |
| 33 | - Ok( web::block(move || markdown::get_markdown(pool, name.as_str(), patch.patch)) | |
| 34 | + Ok( web::block(move || markdown::get_markdown( pool | |
| 35 | + , name.as_str() | |
| 36 | + , patch.patch) ) | |
| 34 | 37 | . await |
| 35 | 38 | . map(|markdowns| HttpResponse::Ok().json(markdowns)) |
| 36 | 39 | . map_err(|_| HttpResponse::InternalServerError())? |
| 37 | 40 | ) |
| 38 | 41 | } |
| 39 | 42 | |
| 40 | -pub async fn get_patches( pool: web::Data<Pool> | |
| 43 | +pub async fn get_patches( app_data: web::Data<AppData> | |
| 41 | 44 | , name: web::Path<String> |
| 42 | 45 | ) -> Result<HttpResponse, Error> { |
| 43 | - let pool = pool.into_inner(); | |
| 46 | + let pool = app_data.database_pool.clone(); | |
| 44 | 47 | let name = name.into_inner(); |
| 45 | 48 | |
| 46 | 49 | Ok( web::block(move || markdown::get_patches(pool, name.as_str())) |
| ... | ... | @@ -50,12 +53,12 @@ pub async fn get_patches( pool: web::Data<Pool> |
| 50 | 53 | ) |
| 51 | 54 | } |
| 52 | 55 | |
| 53 | -pub async fn update_markdown( pool: web::Data<Pool> | |
| 56 | +pub async fn update_markdown( app_data: web::Data<AppData> | |
| 54 | 57 | , name: web::Path<String> |
| 55 | 58 | , item: web::Json<MarkdownJson> ) |
| 56 | 59 | -> Result<HttpResponse, Error> |
| 57 | 60 | { |
| 58 | - let pool = pool.into_inner(); | |
| 61 | + let pool = app_data.database_pool.clone(); | |
| 59 | 62 | let name = name.into_inner(); |
| 60 | 63 | let item = item.into_inner(); |
| 61 | 64 | ... | ... |
| ... | ... | @@ -4,19 +4,25 @@ use futures::stream::StreamExt; |
| 4 | 4 | use async_std::{fs::OpenOptions, io::WriteExt}; |
| 5 | 5 | use uuid::Uuid; |
| 6 | 6 | |
| 7 | -pub async fn upload(mut body: web::Payload) -> Result<HttpResponse, Error> | |
| 7 | +use crate::AppData; | |
| 8 | + | |
| 9 | +pub async fn upload( app_data: web::Data<AppData> | |
| 10 | + , mut body: web::Payload) -> Result<HttpResponse, Error> | |
| 8 | 11 | { |
| 12 | + let worker = app_data.tx_upload_worker.clone(); | |
| 13 | + | |
| 14 | + let upload_filename = format!("/tmp/upload_{}", Uuid::new_v4()); | |
| 9 | 15 | let mut output = OpenOptions::new(); |
| 10 | 16 | output . create(true) |
| 11 | 17 | . write(true); |
| 12 | - let mut output = output | |
| 13 | - . open(format!("/tmp/upload_{}", Uuid::new_v4())) | |
| 14 | - . await | |
| 15 | - . unwrap(); | |
| 18 | + let mut output = output.open(&upload_filename).await?; | |
| 16 | 19 | |
| 17 | 20 | while let Some(item) = body.next().await { |
| 18 | - output.write_all(&item?).await.unwrap(); | |
| 21 | + output.write_all(&item?).await?; | |
| 19 | 22 | } |
| 20 | 23 | |
| 24 | + // TODO handle this as error response... | |
| 25 | + worker.send(upload_filename).await.unwrap(); | |
| 26 | + | |
| 21 | 27 | Ok(HttpResponse::Ok().finish()) |
| 22 | 28 | } | ... | ... |
| 1 | 1 | use crate::models::user::{self, Action}; |
| 2 | -use crate::Pool; | |
| 2 | +use crate::AppData; | |
| 3 | 3 | |
| 4 | 4 | use actix_web::{Error, HttpResponse, web}; |
| 5 | 5 | use anyhow::Result; |
| 6 | 6 | |
| 7 | -pub async fn create_user( pool: web::Data<Pool> | |
| 7 | +pub async fn create_user( app_data: web::Data<AppData> | |
| 8 | 8 | , item: web::Json<user::UserJson> ) |
| 9 | 9 | -> Result<HttpResponse, Error> |
| 10 | 10 | { |
| 11 | - let pool = pool.into_inner(); | |
| 11 | + let pool = app_data.database_pool.clone(); | |
| 12 | 12 | let item = item.into_inner(); |
| 13 | 13 | |
| 14 | 14 | Ok(web::block(move || user::create_user(pool, item)) |
| ... | ... | @@ -21,19 +21,21 @@ pub async fn create_user( pool: web::Data<Pool> |
| 21 | 21 | . map_err(|_| HttpResponse::InternalServerError())?) |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | -pub async fn get_users(pool: web::Data<Pool>) | |
| 24 | +pub async fn get_users(app_data: web::Data<AppData>) | |
| 25 | 25 | -> Result<HttpResponse, Error> |
| 26 | 26 | { |
| 27 | - Ok(web::block(move || user::get_users(pool.into_inner())) | |
| 27 | + let pool = app_data.database_pool.clone(); | |
| 28 | + | |
| 29 | + Ok(web::block(move || user::get_users(pool)) | |
| 28 | 30 | . await |
| 29 | 31 | . map(|users| HttpResponse::Ok().json(users)) |
| 30 | 32 | . map_err(|_| HttpResponse::InternalServerError())?) |
| 31 | 33 | } |
| 32 | 34 | |
| 33 | -pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>) | |
| 35 | +pub async fn get_user(app_data: web::Data<AppData>, id: web::Path<i32>) | |
| 34 | 36 | -> Result<HttpResponse, Error> |
| 35 | 37 | { |
| 36 | - let pool = pool.into_inner(); | |
| 38 | + let pool = app_data.database_pool.clone(); | |
| 37 | 39 | let id = id.into_inner(); |
| 38 | 40 | |
| 39 | 41 | Ok(web::block(move || user::get_user(pool, id)) |
| ... | ... | @@ -42,10 +44,10 @@ pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>) |
| 42 | 44 | . map_err(|_| HttpResponse::InternalServerError())?) |
| 43 | 45 | } |
| 44 | 46 | |
| 45 | -pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) | |
| 47 | +pub async fn delete_user(app_data: web::Data<AppData>, id: web::Path<i32>) | |
| 46 | 48 | -> Result<HttpResponse, Error> |
| 47 | 49 | { |
| 48 | - let pool = pool.into_inner(); | |
| 50 | + let pool = app_data.database_pool.clone(); | |
| 49 | 51 | let id = id.into_inner(); |
| 50 | 52 | |
| 51 | 53 | Ok(web::block(move || user::delete_user(pool, id)) |
| ... | ... | @@ -54,12 +56,12 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) |
| 54 | 56 | . map_err(|_| HttpResponse::InternalServerError())?) |
| 55 | 57 | } |
| 56 | 58 | |
| 57 | -pub async fn update_user( pool: web::Data<Pool> | |
| 59 | +pub async fn update_user( app_data: web::Data<AppData> | |
| 58 | 60 | , id: web::Path<i32> |
| 59 | 61 | , item: web::Json<user::UserJson> ) |
| 60 | 62 | -> Result<HttpResponse, Error> |
| 61 | 63 | { |
| 62 | - let pool = pool.into_inner(); | |
| 64 | + let pool = app_data.database_pool.clone(); | |
| 63 | 65 | let id = id.into_inner(); |
| 64 | 66 | let item = item.into_inner(); |
| 65 | 67 | ... | ... |
server/src/uuid.rs
0 → 100644
| 1 | +use std::fmt::Display; | |
| 2 | + | |
| 3 | +#[derive(Clone,Copy,Debug)] | |
| 4 | +pub struct Uuid(pub uuid::Uuid); | |
| 5 | + | |
| 6 | +impl Display for Uuid { | |
| 7 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| 8 | + write!(f, "{}", self.0) | |
| 9 | + } | |
| 10 | +} | |
| 11 | + | |
| 12 | +macro_rules! ns { | |
| 13 | + ($n:expr) => { | |
| 14 | + uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_DNS, $n.as_bytes()) | |
| 15 | + } | |
| 16 | +} | |
| 17 | + | |
| 18 | +impl Uuid { | |
| 19 | + pub fn get(ns: &str, buf: &mut [u8]) -> Self { | |
| 20 | + Self(uuid::Uuid::new_v5(&ns!(ns), buf)) | |
| 21 | + } | |
| 22 | +} | ... | ... |
Please
register
or
login
to post a comment