Commit 1667225a0db723c110603ec3f5f822584acdff52
1 parent
149cddde
Create database entries on file upload
Showing
12 changed files
with
200 additions
and
47 deletions
... | ... | @@ -20,3 +20,15 @@ pub struct MarkdownDiffJson { |
20 | 20 | pub id: i32, |
21 | 21 | pub date_created: String, |
22 | 22 | } |
23 | + | |
24 | +#[derive(Clone, Debug, Serialize, Deserialize)] | |
25 | +pub struct ImageJson { | |
26 | + pub upload_uuid :Option<Vec<u8>>, | |
27 | + pub uuid :Option<Vec<u8>>, | |
28 | + pub size :i32, | |
29 | + pub dim_x :Option<i32>, | |
30 | + pub dim_y :Option<i32>, | |
31 | + pub mime_type :String, | |
32 | + pub date_created :String, | |
33 | + pub date_updated :String | |
34 | +} | ... | ... |
No preview for this file type
migrations/2022-01-28-163413_images/down.sql
0 → 100644
migrations/2022-01-28-163413_images/up.sql
0 → 100644
1 | +CREATE TABLE "images" ( | |
2 | + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | |
3 | + -- identical uuid means identical file. | |
4 | + upload_uuid BLOB(16) UNIQUE, | |
5 | + uuid BLOB(16) UNIQUE, | |
6 | + size INTEGER NOT NULL, | |
7 | + dim_x INTEGER, | |
8 | + dim_y INTEGER, | |
9 | + mime_type VARCHAR(256) NOT NULL, | |
10 | + date_created TEXT NOT NULL, | |
11 | + date_updated TEXT NOT NULL | |
12 | +); | ... | ... |
... | ... | @@ -7,7 +7,7 @@ use r2d2; |
7 | 7 | type ParentError = Option<Pin<Box<dyn std::error::Error>>>; |
8 | 8 | |
9 | 9 | #[derive(Debug)] |
10 | -pub(crate) struct Error { | |
10 | +pub struct Error { | |
11 | 11 | source: ParentError, |
12 | 12 | message: String, |
13 | 13 | } |
... | ... | @@ -77,3 +77,11 @@ impl From<ParsePatchError> for Error { |
77 | 77 | } |
78 | 78 | } |
79 | 79 | } |
80 | + | |
81 | +impl From<uuid::Error> for Error { | |
82 | + fn from(source: uuid::Error) -> Self { | |
83 | + Self { source: Some(Box::pin(source)) | |
84 | + , message: String::from("UUID error") | |
85 | + } | |
86 | + } | |
87 | +} | ... | ... |
... | ... | @@ -6,11 +6,15 @@ mod models; |
6 | 6 | mod routes; |
7 | 7 | mod schema; |
8 | 8 | mod uuid; |
9 | +mod upload; | |
9 | 10 | |
11 | +use async_std::channel::Receiver; | |
12 | +use models::image::Image; | |
10 | 13 | use routes::markdown::*; |
11 | 14 | use routes::other::*; |
12 | 15 | use routes::user::*; |
13 | 16 | use routes::upload::*; |
17 | +use crate::upload::get_sample; | |
14 | 18 | use crate::uuid::Uuid; |
15 | 19 | |
16 | 20 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; |
... | ... | @@ -18,10 +22,10 @@ use async_std::{ channel::{ Sender, bounded } |
18 | 22 | , fs::File }; |
19 | 23 | use diesel::r2d2::{self, ConnectionManager}; |
20 | 24 | use diesel::SqliteConnection; |
21 | -use futures::{ AsyncReadExt, AsyncSeekExt, FutureExt, StreamExt, select | |
25 | +use futures::{ FutureExt, StreamExt, select | |
22 | 26 | , stream::FuturesUnordered }; |
23 | 27 | use listenfd::ListenFd; |
24 | -use std::io::SeekFrom; | |
28 | +use std::convert::TryFrom; | |
25 | 29 | use std::sync::Arc; |
26 | 30 | |
27 | 31 | pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; |
... | ... | @@ -29,34 +33,7 @@ pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; |
29 | 33 | #[derive(Clone)] |
30 | 34 | pub struct AppData { |
31 | 35 | pub database_pool: Arc<Pool>, |
32 | - pub tx_upload_worker: Sender<String>, | |
33 | -} | |
34 | - | |
35 | -async fn read_at( f :&mut File | |
36 | - , pos :SeekFrom | |
37 | - , buf :&mut [u8]) -> std::io::Result<()> { | |
38 | - f.seek(pos).await?; | |
39 | - f.read_exact(buf).await | |
40 | -} | |
41 | - | |
42 | -async fn get_sample( f :&mut File | |
43 | - , buf :&mut [u8]) -> std::io::Result<()> { | |
44 | - let file_len = f.metadata().await?.len(); | |
45 | - let chunk_size = buf.len() / 3; | |
46 | - | |
47 | - read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?; | |
48 | - if file_len >= 2 * chunk_size as u64 { | |
49 | - read_at( f | |
50 | - , SeekFrom::End(-(chunk_size as i64)) | |
51 | - , &mut buf[2*chunk_size..]).await?; | |
52 | - } | |
53 | - if file_len >= 3 * chunk_size as u64 { | |
54 | - read_at( f | |
55 | - , SeekFrom::Start((file_len-chunk_size as u64) / 2) | |
56 | - , &mut buf[chunk_size..2*chunk_size]).await?; | |
57 | - } | |
58 | - | |
59 | - Ok(()) | |
36 | + pub tx_upload_worker: Sender<Image>, | |
60 | 37 | } |
61 | 38 | |
62 | 39 | #[actix_rt::main] |
... | ... | @@ -65,7 +42,8 @@ async fn main() -> std::io::Result<()> { |
65 | 42 | |
66 | 43 | dotenv::dotenv().ok(); |
67 | 44 | |
68 | - let (tx_upload_worker, rx_upload_worker) = bounded(32); | |
45 | + let (tx_upload_worker, rx_upload_worker) | |
46 | + : (Sender<Image>, Receiver<Image>) = bounded(32); | |
69 | 47 | |
70 | 48 | let _upload_worker = actix_rt::spawn(async move { |
71 | 49 | let mut workers = FuturesUnordered::new(); |
... | ... | @@ -75,12 +53,14 @@ async fn main() -> std::io::Result<()> { |
75 | 53 | filename = rx_upload_worker.recv().fuse() => { |
76 | 54 | match filename { |
77 | 55 | Err(_) => break, |
78 | - Ok(filename) => workers.push(async move { | |
79 | - let mut f = File::open(&filename).await.unwrap(); | |
56 | + Ok(upload) => workers.push(async move { | |
57 | + let upload_uuid = Uuid::try_from(upload.upload_uuid.unwrap()).unwrap(); | |
58 | + let upload_filename = format!("/tmp/upload_{}", upload_uuid); | |
59 | + let mut f = File::open(&upload_filename).await.unwrap(); | |
80 | 60 | let mut buf = vec!['.' as u8; 3 * 4096]; |
81 | 61 | get_sample(&mut f, buf.as_mut()).await.unwrap(); |
82 | 62 | println!( "[UPLOAD WORKER] filename: {}" |
83 | - , filename ); | |
63 | + , upload_filename ); | |
84 | 64 | println!( "[UPLOAD WORKER] uuid: {}" |
85 | 65 | , Uuid::get( "some.unique.namespace" |
86 | 66 | , buf.as_mut() ) ); | ... | ... |
server/src/models/image.rs
0 → 100644
1 | +use std::sync::Arc; | |
2 | + | |
3 | +use crate::error::*; | |
4 | +use crate::{schema::*, Pool}; | |
5 | +use diesel::{Connection, insert_into}; | |
6 | +use diesel::prelude::*; | |
7 | +use serde::{Deserialize, Serialize}; | |
8 | + | |
9 | +#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Identifiable)] | |
10 | +pub struct Image { | |
11 | + pub id :i32, | |
12 | + pub upload_uuid :Option<Vec<u8>>, | |
13 | + pub uuid :Option<Vec<u8>>, | |
14 | + pub size :i32, | |
15 | + pub dim_x :Option<i32>, | |
16 | + pub dim_y :Option<i32>, | |
17 | + pub mime_type :String, | |
18 | + pub date_created :String, | |
19 | + pub date_updated :String | |
20 | +} | |
21 | + | |
22 | +#[derive(Debug, Insertable)] | |
23 | +#[table_name = "images"] | |
24 | +pub struct ImageNew<'a> { | |
25 | + pub upload_uuid :Option<&'a [u8]>, | |
26 | + pub size :i32, | |
27 | + pub mime_type :&'a str, | |
28 | + pub date_created :&'a str, | |
29 | + pub date_updated :&'a str | |
30 | +} | |
31 | + | |
32 | +#[derive(Clone, Debug, Serialize, Deserialize, AsChangeset)] | |
33 | +#[table_name = "images"] | |
34 | +pub struct Upload { | |
35 | + pub upload_uuid :Option<Vec<u8>>, | |
36 | + pub size :i32, | |
37 | + pub mime_type :String, | |
38 | +} | |
39 | + | |
40 | +pub(crate) fn upload( pool: Arc<Pool> | |
41 | + , item: Upload ) -> Result<Image> { | |
42 | + use crate::schema::images::dsl::*; | |
43 | + let db_connection = pool.get()?; | |
44 | + | |
45 | + let now = chrono::Local::now().naive_local(); | |
46 | + let new_image = ImageNew { | |
47 | + upload_uuid : item.upload_uuid.as_deref(), | |
48 | + size : item.size, | |
49 | + mime_type : &item.mime_type, | |
50 | + date_created : &format!("{}", now), | |
51 | + date_updated : &format!("{}", now) | |
52 | + }; | |
53 | + | |
54 | + Ok(db_connection.transaction(|| { | |
55 | + insert_into(images) . values(&new_image) | |
56 | + . execute(&db_connection)?; | |
57 | + images . order(id.desc()) | |
58 | + . first::<Image>(&db_connection) | |
59 | + })?) | |
60 | +} | ... | ... |
... | ... | @@ -4,25 +4,47 @@ use futures::stream::StreamExt; |
4 | 4 | use async_std::{fs::OpenOptions, io::WriteExt}; |
5 | 5 | use uuid::Uuid; |
6 | 6 | |
7 | -use crate::AppData; | |
7 | +use crate::{AppData, models::image::{Upload, self}}; | |
8 | 8 | |
9 | -pub async fn upload( app_data: web::Data<AppData> | |
10 | - , mut body: web::Payload) -> Result<HttpResponse, Error> | |
9 | +pub async fn upload( app_data :web::Data<AppData> | |
10 | + , mut body :web::Payload | |
11 | + , request :web::HttpRequest ) -> Result<HttpResponse, Error> | |
11 | 12 | { |
13 | + let pool = app_data.database_pool.clone(); | |
12 | 14 | let worker = app_data.tx_upload_worker.clone(); |
13 | 15 | |
14 | - let upload_filename = format!("/tmp/upload_{}", Uuid::new_v4()); | |
16 | + let random_uuid = Uuid::new_v4(); | |
17 | + let upload_uuid = Some(random_uuid.as_bytes().to_vec()); | |
18 | + let size = request.headers().get("content-length") | |
19 | + . and_then(|h| Some(h.to_str().unwrap().parse::<i32>())) | |
20 | + . unwrap().unwrap(); | |
21 | + let mime_type = String::from( request.headers().get("content-type") | |
22 | + . and_then(|h| Some(h.to_str().unwrap())) | |
23 | + . unwrap() ); | |
24 | + | |
25 | + let upload_filename = format!("/tmp/upload_{}", random_uuid); | |
15 | 26 | let mut output = OpenOptions::new(); |
16 | - output . create(true) | |
17 | - . write(true); | |
18 | - let mut output = output.open(&upload_filename).await?; | |
27 | + let mut output = output | |
28 | + . create(true) | |
29 | + . write(true) | |
30 | + . open(&upload_filename).await?; | |
19 | 31 | |
20 | 32 | while let Some(item) = body.next().await { |
21 | 33 | output.write_all(&item?).await?; |
22 | 34 | } |
23 | 35 | |
24 | - // TODO handle this as error response... | |
25 | - worker.send(upload_filename).await.unwrap(); | |
36 | + let upload = Upload { | |
37 | + upload_uuid, | |
38 | + size, | |
39 | + mime_type | |
40 | + }; | |
26 | 41 | |
27 | - Ok(HttpResponse::Ok().finish()) | |
42 | + Ok( match web::block(move || image::upload(pool, upload)).await { | |
43 | + Ok(image) => { | |
44 | + // TODO handle this as error response... | |
45 | + worker.send(image.clone()).await.unwrap(); | |
46 | + HttpResponse::Ok().json(image) | |
47 | + }, | |
48 | + Err(_) => HttpResponse::InternalServerError().finish() | |
49 | + } ) | |
28 | 50 | } | ... | ... |
1 | 1 | table! { |
2 | + images (id) { | |
3 | + id -> Integer, | |
4 | + upload_uuid -> Nullable<Binary>, | |
5 | + uuid -> Nullable<Binary>, | |
6 | + size -> Integer, | |
7 | + dim_x -> Nullable<Integer>, | |
8 | + dim_y -> Nullable<Integer>, | |
9 | + mime_type -> Text, | |
10 | + date_created -> Text, | |
11 | + date_updated -> Text, | |
12 | + } | |
13 | +} | |
14 | + | |
15 | +table! { | |
2 | 16 | markdown_diffs (markdown_id, diff_id) { |
3 | 17 | markdown_id -> Integer, |
4 | 18 | diff_id -> Integer, |
... | ... | @@ -28,6 +42,7 @@ table! { |
28 | 42 | } |
29 | 43 | |
30 | 44 | allow_tables_to_appear_in_same_query!( |
45 | + images, | |
31 | 46 | markdown_diffs, |
32 | 47 | markdowns, |
33 | 48 | users, | ... | ... |
server/src/upload.rs
0 → 100644
1 | +use std::io::SeekFrom; | |
2 | +use async_std::fs::File; | |
3 | +use futures::{AsyncSeekExt, AsyncReadExt}; | |
4 | + | |
5 | + | |
6 | +async fn read_at( f :&mut File | |
7 | + , pos :SeekFrom | |
8 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
9 | + f.seek(pos).await?; | |
10 | + f.read_exact(buf).await | |
11 | +} | |
12 | + | |
13 | +pub async fn get_sample( f :&mut File | |
14 | + , buf :&mut [u8]) -> std::io::Result<()> { | |
15 | + let file_len = f.metadata().await?.len(); | |
16 | + let chunk_size = buf.len() / 3; | |
17 | + | |
18 | + read_at(f, SeekFrom::Start(0), &mut buf[0..chunk_size]).await?; | |
19 | + if file_len >= 2 * chunk_size as u64 { | |
20 | + read_at( f | |
21 | + , SeekFrom::End(-(chunk_size as i64)) | |
22 | + , &mut buf[2*chunk_size..]).await?; | |
23 | + } | |
24 | + if file_len >= 3 * chunk_size as u64 { | |
25 | + read_at( f | |
26 | + , SeekFrom::Start((file_len-chunk_size as u64) / 2) | |
27 | + , &mut buf[chunk_size..2*chunk_size]).await?; | |
28 | + } | |
29 | + | |
30 | + Ok(()) | |
31 | +} | ... | ... |
1 | -use std::fmt::Display; | |
1 | +use std::{fmt::Display, convert::TryFrom}; | |
2 | + | |
3 | +use crate::error::Error; | |
2 | 4 | |
3 | 5 | #[derive(Clone,Copy,Debug)] |
4 | 6 | pub struct Uuid(pub uuid::Uuid); |
... | ... | @@ -20,3 +22,11 @@ impl Uuid { |
20 | 22 | Self(uuid::Uuid::new_v5(&ns!(ns), buf)) |
21 | 23 | } |
22 | 24 | } |
25 | + | |
26 | +impl TryFrom<Vec<u8>> for Uuid { | |
27 | + type Error = Error; | |
28 | + | |
29 | + fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> { | |
30 | + Ok(Self(uuid::Uuid::from_slice(value.as_slice())?)) | |
31 | + } | |
32 | +} | ... | ... |
Please
register
or
login
to post a comment