working(ish) upload
This commit is contained in:
@ -2,12 +2,12 @@ use crate::Route;
|
||||
use dioxus::prelude::*;
|
||||
use crate::components::{Hero};
|
||||
|
||||
const BLOG_CSS: Asset = asset!("/assets/styling/blog.css");
|
||||
//const BLOG_CSS: Asset = asset!("/assets/styling/blog.css");
|
||||
|
||||
#[component]
|
||||
pub fn Blog(id: i32) -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: BLOG_CSS }
|
||||
// document::Link { rel: "stylesheet", href: BLOG_CSS }
|
||||
|
||||
div {
|
||||
id: "blog",
|
||||
|
||||
@ -6,3 +6,6 @@ pub use blog::Blog;
|
||||
|
||||
mod navbar;
|
||||
pub use navbar::Navbar;
|
||||
|
||||
mod upload;
|
||||
pub use upload::Upload;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use crate::Route;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
|
||||
//const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
|
||||
// document::Link { rel: "stylesheet", href: NAVBAR_CSS }
|
||||
|
||||
div {
|
||||
id: "navbar",
|
||||
|
||||
154
src/views/upload.rs
Normal file
154
src/views/upload.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use dioxus::{
|
||||
fullstack::{ByteStream, FileStream},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use dioxus_html::{FileData, HasFileData};
|
||||
use futures::StreamExt;
|
||||
|
||||
use std::{ env, fs, io::Write };
|
||||
|
||||
|
||||
use random_string;
|
||||
|
||||
pub fn byte_to_human_size(size: u64) -> String {
|
||||
let sizes = vec!["B", "KB", "MB", "GB", "TB", "WTFAREYOUDOINGB"];
|
||||
let mut current = 0;
|
||||
|
||||
let mut res_size: f64 = size as f64;
|
||||
let mut res: String;
|
||||
|
||||
while (res_size >= 1000.0 && current < sizes.len()) {
|
||||
res_size /= 1000.0;
|
||||
current += 1;
|
||||
}
|
||||
|
||||
res = ((res_size * 100.0).round() / 100.0).to_string() + " " + sizes[current];
|
||||
res
|
||||
}
|
||||
|
||||
#[post("/api/upload")]
|
||||
async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
|
||||
let UPLOAD_SIZE_LIMIT: u64 = 1024 * 1024 * 1024 * 1;
|
||||
let UPLOAD_FOLDER: String = match env::var("UPLOAD_FOLDER") {
|
||||
Ok(value) => { value },
|
||||
Err(_) => { return HttpError::internal_server_error("UPLOAD_FOLDER not set"); }
|
||||
};
|
||||
|
||||
let mut total_len: u64 = 0;
|
||||
let mut error: Option<&str> = None;
|
||||
|
||||
let filename: String = loop {
|
||||
let cur = random_string::generate(20, "ABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789");
|
||||
if (!fs::exists(UPLOAD_FOLDER.clone() + &cur).expect("can't check if file exists")) {
|
||||
break cur;
|
||||
}
|
||||
};
|
||||
|
||||
if upload.size().unwrap() > UPLOAD_SIZE_LIMIT {
|
||||
return HttpError::payload_too_large("this file is too large");
|
||||
}
|
||||
|
||||
|
||||
let mut file = match fs::File::create(UPLOAD_FOLDER.clone() + &filename) {
|
||||
Ok(val) => { val },
|
||||
Err(_) => { return HttpError::internal_server_error("failed to open the output file") }
|
||||
};
|
||||
|
||||
while let Some(chunk) = upload.next().await {
|
||||
match chunk {
|
||||
Ok(bytes) => {
|
||||
total_len += bytes.len() as u64;
|
||||
if total_len > UPLOAD_SIZE_LIMIT {
|
||||
error = Some("Uploaded file to large");
|
||||
break;
|
||||
}
|
||||
|
||||
if file.write(&bytes).is_err() { error == Some("failed write"); break; };
|
||||
},
|
||||
Err(_) => { error = Some("unknown"); break; }
|
||||
}
|
||||
}
|
||||
|
||||
match error {
|
||||
Some(err)=> {
|
||||
file.sync_data();
|
||||
fs::remove_file(UPLOAD_FOLDER.clone() + &filename);
|
||||
HttpError::internal_server_error(err)?
|
||||
}
|
||||
None => { Ok(filename) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn build_table(files: Vec<(String, String, Option<Result<String, HttpError>>)>) -> Element {
|
||||
rsx! {
|
||||
table {
|
||||
tr {
|
||||
th { "filename" }
|
||||
th { "size" }
|
||||
th { "url" }
|
||||
}
|
||||
|
||||
{ files.iter().map(|file| rsx! {
|
||||
tr {
|
||||
td { class: "px-2", "{file.0}" }
|
||||
td { class: "px-2", "{file.1}" }
|
||||
td { class: "px-2",
|
||||
match &file.2 {
|
||||
Some(res) => { match res {
|
||||
Ok(file_id) => { rsx! { input { type:"button" , "/upload/dl/{file_id}"} } },
|
||||
Err(e) => {
|
||||
let msg = e.message.clone().unwrap();
|
||||
rsx! { p { "Upload failed, reason : {msg}" } }
|
||||
}
|
||||
} }
|
||||
None => { rsx! { p { "Waiting for file to be uploaded" } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Upload() -> Element {
|
||||
//TODO: global config struct
|
||||
let UPLOAD_SIZE_LIMIT: u64 = 1024 * 1024 * 1024 * 1;
|
||||
|
||||
let mut selected : Signal<Vec<(String, String, Option<Result<String, HttpError>>)>> = use_signal(|| vec![]);
|
||||
|
||||
rsx! {
|
||||
div { class : "p-2",
|
||||
p { class: "text-4xl font-bold p-1", "Upload a file" }
|
||||
|
||||
form {
|
||||
|
||||
label { for :"file", {
|
||||
if selected.len() == 0 {
|
||||
rsx! { "No file selected" }
|
||||
} else {
|
||||
build_table(selected())
|
||||
}
|
||||
} }
|
||||
|
||||
input { id: "file", r#type : "file" ,multiple: true, class : "hidden", oninput: move |e| async move {
|
||||
for file in e.files() {
|
||||
selected.with_mut(|files| {files.push((file.name(), byte_to_human_size(file.size()), None)); } );
|
||||
let idx = selected().len() - 1;
|
||||
|
||||
if (file.size() > UPLOAD_SIZE_LIMIT) {
|
||||
//messy but firefox can't handle when server returns early
|
||||
selected.with_mut(|files| { files[idx].2 = Some(HttpError::payload_too_large("This file is too large")) });
|
||||
continue;
|
||||
}
|
||||
|
||||
let res = upload_file(file.clone().into()).await;
|
||||
selected.with_mut(|files| { files[idx].2 = Some(res) });
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user