182 lines
6.3 KiB
Rust
182 lines
6.3 KiB
Rust
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<String, HttpError> {
|
|
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<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_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<Vec<(String, String, Option<Result<String, HttpError>>)>> = 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) });
|
|
}}
|
|
}
|
|
}
|
|
}
|
|
}
|