display file info

This commit is contained in:
2026-04-14 00:39:00 +02:00
parent 5a37834d4e
commit 52a14a71d6
16 changed files with 376 additions and 317 deletions

View File

@ -1,37 +0,0 @@
use dioxus::prelude::*;
//const ECHO_CSS: Asset = asset!("/assets/styling/echo.css");
#[component]
pub fn Echo() -> Element {
let mut response = use_signal(|| String::new());
rsx! {
// document::Link { rel: "stylesheet", href: ECHO_CSS }
div {
id: "echo",
h4 { "ServerFn Echo" }
input {
placeholder: "Type here to echo...",
oninput: move |event| async move {
let data = echo_server(event.value()).await.unwrap();
response.set(data);
},
}
if !response().is_empty() {
p {
"Server echoed: "
i { "{response}" }
}
}
}
}
}
#[post("/api/echo")]
async fn echo_server(input: String) -> Result<String> {
Ok(input)
}

View File

@ -1,21 +0,0 @@
use dioxus::prelude::*;
const HEADER_SVG: Asset = asset!("/assets/header.svg");
#[component]
pub fn Hero() -> Element {
rsx! {
div {
id: "hero",
img { src: HEADER_SVG, id: "header" }
div { id: "links",
a { href: "https://dioxuslabs.com/learn/0.7/", "📚 Learn Dioxus" }
a { href: "https://dioxuslabs.com/awesome", "🚀 Awesome Dioxus" }
a { href: "https://github.com/dioxus-community/", "📡📡 📡 📡 📡" }
a { href: "https://github.com/DioxusLabs/sdk", "⚙️ Dioxus Development Kit" }
a { href: "https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus", "some useless shit" }
a { href: "https://discord.gg/XgGxMSkvUM", "who tf uses discord ??" }
}
}
}
}

View File

@ -1,8 +1,7 @@
//mod hero;
//pub use hero::Hero;
mod hero;
pub use hero::Hero;
pub mod upload;
mod echo;
pub use echo::Echo;
pub mod progress;
pub mod toast;

View File

@ -0,0 +1,194 @@
use dioxus::{
fullstack::{FileStream},
prelude::*,
};
use httpserver::utils::byte_to_human_size;
#[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;
#[cfg(feature = "server")]
use httpserver::models::{GetFile,NewFile};
#[cfg(feature = "server")]
use diesel::prelude::*;
#[cfg(feature = "server")]
use httpserver::DB;
use dioxus_primitives::toast::{
ToastOptions,
consume_toast
};
#[post("/api/upload")]
async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
use httpserver::schema::file;
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");
}
}
if upload.file_name().len() > 255 {
return HttpError::bad_request("Filename too long");
}
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 => {
let new_file = NewFile{filename: upload.file_name(), file_size: &(total_len as i64), stored_filename: &filename};
DB.with(|pool| diesel::insert_into(file::table)
.values(&new_file)
.returning(GetFile::as_returning())
.get_result(&mut pool.get().unwrap())
.expect("Failed to save the file in db"));
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 UploadFile() -> 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) });
}}
}
}
}
}

View File

View File

@ -0,0 +1,84 @@
use std::time::SystemTime;
use chrono::DateTime;
use chrono::Utc;
use dioxus::fullstack::httperror;
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
use httpserver::utils::byte_to_human_size;
#[cfg(feature = "server")]
use httpserver::models::GetFile;
#[cfg(feature = "server")]
use diesel::prelude::*;
#[cfg(feature = "server")]
use httpserver::DB;
#[cfg(feature = "server")]
use httpserver::schema;
#[derive(Serialize, Deserialize)]
struct FetchedInfo {
filename: String,
size: i64,
created_at: DateTime<Utc>,
deletion_pos: Option<u32>,
}
#[post("/api/upload/info")]
async fn get_file_info(filename: String) -> Result<FetchedInfo, HttpError> {
use schema::file;
let result = DB.with(|pool| schema::file::table.select((file::filename, file::file_size, file::created_at))
.filter(file::stored_filename.eq(filename))
.filter(file::deleted.eq(false))
.load::<(String, i64, SystemTime)>(&mut pool.get().unwrap()));
if let Err(_) = result {
return HttpError::internal_server_error("Database request failed");
}
let db_file = result.unwrap();
if db_file.len() == 0 {
return HttpError::internal_server_error("The requested file does not exist");
}
let db_file = &db_file[0];
Ok(FetchedInfo { filename: db_file.0.clone(), size: db_file.1, created_at: db_file.2.into(), deletion_pos: None })
}
fn show_file_info(file_id: String, file_info: &FetchedInfo) -> Element {
let creation_date = file_info.created_at.format("%Y-%m-%d at %T");
rsx! {
div { class: "p-2",
h1 { class: "text-4xl font-bold", "download a file" }
div { class : "flex flex-col",
p { "File name : {file_info.filename}" }
p { "This file was uploaded on the {creation_date}" }
p { "File size : {byte_to_human_size(file_info.size.try_into().unwrap())}" }
p { "Position in the deletion queue : ",
match file_info.deletion_pos {
Some(pos) => pos.to_string(),
None => "unknown".to_string()
}
}
a { class: "height-20px text-blue-800 underline", href: "/upload/{file_id}/dl", "click here to download the file"}
}
}
}
}
#[component]
pub fn FileInfo(file: String) -> Element {
let file_id = file.clone();
let file_info = use_resource(move || get_file_info(file.clone()));
match &*file_info.read_unchecked() {
Some(Ok(info)) => { show_file_info(file_id, info) },
Some(Err(err)) => { rsx! { p {"server retrned an error : {err}"} } },
_ => { rsx! { p {"Loading ..."} } }
}
}

View File

@ -0,0 +1,5 @@
mod create;
pub use create::UploadFile;
mod info;
pub use info::FileInfo;

View File

@ -3,6 +3,8 @@ use diesel::pg::PgConnection;
#[cfg(feature = "server")]
use diesel::prelude::*;
pub mod utils;
pub mod config;
#[cfg(feature = "server")]

View File

@ -1,6 +1,6 @@
use dioxus::prelude::*;
use views::{Upload};
use crate::components::upload::{FileInfo, UploadFile};
use tracing::Level;
pub mod config;
@ -10,12 +10,12 @@ use crate::components::toast::ToastProvider;
mod components;
mod views;
#[derive(Debug, Clone, Routable, PartialEq)]
#[rustfmt::skip]
enum Route {
#[route("/upload")]
Upload {},
}
//#[derive(Debug, Clone, Routable, PartialEq)]
//#[rustfmt::skip]
//enum Route {
// #[route("/upload")]
// Upload { },
//}
const FAVICON: Asset = asset!("/assets/favicon.ico");
@ -34,10 +34,10 @@ fn App() -> Element {
div {
class: "h-screen w-screen bg-zinc-900 text-white",
ToastProvider {
Upload {}
FileInfo {file: "ULI0GZWJQH9BGEZR1VZ1"}
// Router::<Route> {}
}
}
// Router::<Route> {}
}
}

14
src/utils.rs Normal file
View File

@ -0,0 +1,14 @@
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]
}

View File

@ -1,2 +1,2 @@
mod upload;
pub use upload::Upload;
//mod upload;
//pub use upload::Upload;

View File

@ -6,17 +6,6 @@ use dioxus::{
#[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;
#[cfg(feature = "server")]
use httpserver::models::{GetFile,NewFile};
#[cfg(feature = "server")]
@ -25,178 +14,7 @@ use diesel::prelude::*;
use httpserver::DB;
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> {
use httpserver::schema::file;
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 => {
let new_file = NewFile{filename: upload.file_name(), file_size: &(total_len as i64), stored_filename: &filename};
DB.with(|pool| diesel::insert_into(file::table)
.values(&new_file)
.returning(GetFile::as_returning())
.get_result(&mut pool.get().unwrap())
.expect("Failed to save the file in db"));
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) });
}}
}
}
}
}