diff --git a/src/main.rs b/src/main.rs index 5eb24f5..36c4b24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::{fmt::Debug, path::PathBuf, str::FromStr}; use actix_files::NamedFile; use actix_web::{ - get, http::StatusCode, middleware::Logger, post, web, App, HttpRequest, HttpResponse, + error::InternalError, get, http::StatusCode, middleware::Logger, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder, }; use actix_web_actors::ws; @@ -58,21 +58,22 @@ struct DownloadRequest { download: Option, } -#[derive(Template)] +#[derive(Serialize, Template)] #[template(path = "download.html")] -struct DownloadInfo<'a> { +struct DownloadInfo { file: StoredFile, - code: &'a str, + code: String, available: u64, + offsets: Option>, } #[get("/download")] async fn handle_download( req: HttpRequest, - download: web::Query, + query: web::Query, data: web::Data, ) -> actix_web::Result { - let code = &download.code; + let code = &query.code; if !store::is_valid_storage_code(code) { return not_found(req, data, true); } @@ -85,7 +86,7 @@ async fn handle_download( let storage_path = data.config.storage_dir.join(code); let file = File::open(&storage_path).await?; - if let Some(selection) = download.download { + if let Some(selection) = query.download { if let download::DownloadSelection::One(n) = selection { if let Some(ref files) = info.contents { if n >= files.len() { @@ -103,26 +104,60 @@ async fn handle_download( } .into_response(&req)) } else { + let offsets = info.contents.as_deref().map(zip::file_data_offsets); Ok(HttpResponse::Ok().body(DownloadInfo { file: info, - code, + code: code.clone(), available: file.metadata().await?.len(), + offsets, }.render().unwrap())) } } -fn not_found( +#[derive(Deserialize)] +struct InfoQuery { + code: String, +} + +#[get("/info")] +async fn download_info( + req: HttpRequest, + query: web::Query, + data: web::Data, +) -> actix_web::Result { + let code = &query.code; + if !store::is_valid_storage_code(code) { + return not_found(req, data, true); + } + let info = data.file_store.read().await.lookup_file(code); + let info = if let Some(i) = info { + i + } else { + return not_found(req, data, true) + }; + + let storage_path = data.config.storage_dir.join(code); + let offsets = info.contents.as_deref().map(zip::file_data_offsets); + Ok(web::Json(DownloadInfo { + file: info, + code: code.clone(), + available: File::open(&storage_path).await?.metadata().await?.len(), + offsets, + })) +} + +fn not_found( req: HttpRequest, data: web::Data, report: bool, -) -> actix_web::Result { +) -> actix_web::Result { if report { let ip_addr = get_ip_addr(&req, data.config.reverse_proxy); log_auth_failure(&ip_addr); } - Ok(NamedFile::open(data.config.static_dir.join("404.html"))? + Err(InternalError::from_response("Download not found", NamedFile::open(data.config.static_dir.join("404.html"))? .set_status_code(StatusCode::NOT_FOUND) - .into_response(&req)) + .into_response(&req)).into()) } #[get("/upload")] @@ -244,6 +279,7 @@ async fn main() -> std::io::Result<()> { Logger::default() }) .service(handle_download) + .service(download_info) .service(handle_upload) .service(check_upload_password) .service(upload_limits) diff --git a/src/zip.rs b/src/zip.rs index c0001e8..3160e84 100644 --- a/src/zip.rs +++ b/src/zip.rs @@ -43,6 +43,17 @@ pub fn file_data_offset(files: &[UploadedFile], idx: usize) -> u64 { + files[idx].name.len() as u64 } +pub fn file_data_offsets(files: &[UploadedFile]) -> Vec { + let mut offsets = Vec::new(); + let mut offset: u64 = 0; + for file in files.iter() { + offset += LOCAL_HEADER_SIZE_MINUS_FILENAME + file.name.len() as u64; + offsets.push(offset); + offset += file.size + DATA_DESCRIPTOR_SIZE; + } + offsets +} + fn central_directory_size(files: &[UploadedFile]) -> u64 { let mut total = 0; for file in files.iter() { diff --git a/static/js/download.js b/static/js/download.js new file mode 100644 index 0000000..ccd1168 --- /dev/null +++ b/static/js/download.js @@ -0,0 +1,14 @@ +document.addEventListener("DOMContentLoaded", () => { + const table = document.getElementById("download_contents").getElementsByTagName("tbody")[0]; + if (table.children.length === 0) { return; } + + setInterval(() => { + fetch(`info?code=${CODE}`) + .then((res) => res.json()) + .then((info) => { + for (const [index, offset] of info.offsets.entries()) { + table.children[index].className = (offset > info.available) ? "unavailable" : ""; + } + }); + }, 5000); +}); diff --git a/templates/download.html b/templates/download.html index 413cca4..f282db2 100644 --- a/templates/download.html +++ b/templates/download.html @@ -4,10 +4,12 @@ - + {{ file.name }} - transbeam @@ -25,16 +27,17 @@ {% when Some with (files) %}

Contents

- +
+ {% let offsets = offsets.as_ref().unwrap() %} {% for f in files %} - + {% endfor %} -
{{ bytesize::to_string(f.size.clone(), false).replace(" ", "") }} {{ f.name }}
+
{% else %} {% endmatch %}