use dioxus::{ fullstack::{FileStream}, prelude::*, }; #[cfg(feature = "server")] use crate::config::ServerConfig; use crate::config::ClientConfig; use std::time::Duration; use std::fs; use std::fs::File; use std::io::Write; use futures::StreamExt; use dioxus_primitives::toast::{ ToastOptions, consume_toast }; 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; while res_size >= 1000.0 && current < sizes.len() { res_size /= 1000.0; current += 1; } ((res_size * 100.0).round() / 100.0).to_string() + " " + sizes[current] } #[post("/api/upload")] async fn upload_file(mut upload: FileStream) -> Result { let s_config = ServerConfig::load(); let c_config = ClientConfig::load(); 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(s_config.upload_folder.clone() + &cur).expect("can't check if file exists")) { break cur; } }; if let Some(size) = upload.size() { if size > c_config.upload_max_size.try_into().unwrap() { return HttpError::payload_too_large("this file is too large"); } } let mut file = match fs::File::create(s_config.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 > c_config.upload_max_size.try_into().unwrap() { error = Some("Uploaded file too 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(s_config.upload_folder.clone() + &filename); HttpError::internal_server_error(err)? } None => { Ok(filename) } } } pub fn build_table(files: Vec<(String, String, Option>)>) -> 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_url) => {let url = file_url.clone(); rsx! { input { type:"button", onclick: move |_| { let toast_api = consume_toast(); toast_api .success( "Success".to_string(), ToastOptions::new() .description("The url has been copied successfully") .duration(Duration::from_secs(5)) .permanent(false), ); let _ = web_sys::window().unwrap().navigator().clipboard().write_text(&url); }, value: "{file_url}" } } }, 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 { let config = ClientConfig::load(); let mut selected : Signal>)>> = use_signal(|| vec![]); rsx! { div { class : "p-2 h-full w-full", 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: false, class : "hidden", oninput: move |e| async move { let file = &e.files()[0]; selected.with_mut(|files| {files.push((file.name(), byte_to_human_size(file.size()), None)); } ); let idx = selected().len() - 1; if file.size() > config.upload_max_size as u64 { //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")) }); return ; } let res = match upload_file(file.clone().into()).await { Ok(file_id) => { let location = web_sys::window().unwrap().location(); let host = location.host().expect("unknown host"); let protocol = location.protocol().expect("unknown protocol"); Ok(protocol + "//" + &host + "/upload/" + &file_id) }, Err(err) => { Err(err) } }; selected.with_mut(|files| { files[idx].2 = Some(res) }); }} } } } }