From e7ac0198abdbebde752f7d04bc4a3e85919086b8 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 29 May 2025 10:23:26 +0200 Subject: [PATCH] Can download disk images --- .../src/controllers/disk_images_controller.rs | 20 +++++++- virtweb_backend/src/main.rs | 4 ++ virtweb_frontend/src/api/DiskImageApi.ts | 18 +++++++ .../src/routes/DiskImagesRoute.tsx | 50 ++++++++++++++++++- 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/virtweb_backend/src/controllers/disk_images_controller.rs b/virtweb_backend/src/controllers/disk_images_controller.rs index 7e9e6c2..7c3aafc 100644 --- a/virtweb_backend/src/controllers/disk_images_controller.rs +++ b/virtweb_backend/src/controllers/disk_images_controller.rs @@ -3,9 +3,10 @@ use crate::constants; use crate::controllers::HttpResult; use crate::utils::file_disks_utils::DiskFileInfo; use crate::utils::files_utils; +use actix_files::NamedFile; use actix_multipart::form::MultipartForm; use actix_multipart::form::tempfile::TempFile; -use actix_web::{HttpResponse, web}; +use actix_web::{HttpRequest, HttpResponse, web}; #[derive(Debug, MultipartForm)] pub struct UploadDiskImageForm { @@ -73,6 +74,23 @@ pub struct DiskFilePath { filename: String, } +/// Download disk image +pub async fn download(p: web::Path, req: HttpRequest) -> HttpResult { + if !files_utils::check_file_name(&p.filename) { + return Ok(HttpResponse::BadRequest().json("Invalid file name!")); + } + + let file_path = AppConfig::get() + .disk_images_storage_path() + .join(&p.filename); + + if !file_path.exists() { + return Ok(HttpResponse::NotFound().json("Disk image does not exists!")); + } + + Ok(NamedFile::open(file_path)?.into_response(&req)) +} + /// Delete a disk image pub async fn delete(p: web::Path) -> HttpResult { if !files_utils::check_file_name(&p.filename) { diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 77844c3..71f8eb4 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -345,6 +345,10 @@ async fn main() -> std::io::Result<()> { "/api/disk_images/list", web::get().to(disk_images_controller::get_list), ) + .route( + "/api/disk_images/{filename}", + web::get().to(disk_images_controller::download), + ) .route( "/api/disk_images/{filename}", web::delete().to(disk_images_controller::delete), diff --git a/virtweb_frontend/src/api/DiskImageApi.ts b/virtweb_frontend/src/api/DiskImageApi.ts index 105166a..018303e 100644 --- a/virtweb_frontend/src/api/DiskImageApi.ts +++ b/virtweb_frontend/src/api/DiskImageApi.ts @@ -43,6 +43,24 @@ export class DiskImageApi { ).data; } + /** + * Download disk image file + */ + static async Download( + file: DiskImage, + progress: (p: number) => void + ): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: `/disk_images/${file.file_name}`, + downProgress(e) { + progress(Math.floor(100 * (e.progress / e.total))); + }, + }) + ).data; + } + /** * Delete disk image file */ diff --git a/virtweb_frontend/src/routes/DiskImagesRoute.tsx b/virtweb_frontend/src/routes/DiskImagesRoute.tsx index 8170147..5935d1a 100644 --- a/virtweb_frontend/src/routes/DiskImagesRoute.tsx +++ b/virtweb_frontend/src/routes/DiskImagesRoute.tsx @@ -2,7 +2,9 @@ import DeleteIcon from "@mui/icons-material/Delete"; import DownloadIcon from "@mui/icons-material/Download"; import RefreshIcon from "@mui/icons-material/Refresh"; import { + Alert, Button, + CircularProgress, IconButton, LinearProgress, Tooltip, @@ -22,6 +24,7 @@ import { DateWidget } from "../widgets/DateWidget"; import { FileInput } from "../widgets/forms/FileInput"; import { VirtWebPaper } from "../widgets/VirtWebPaper"; import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; +import { downloadBlob } from "../utils/FilesUtils"; export function DiskImagesRoute(): React.ReactElement { const [list, setList] = React.useState(); @@ -159,6 +162,24 @@ function DiskImageList(p: { const confirm = useConfirm(); const loadingMessage = useLoadingMessage(); + const [dlProgress, setDlProgress] = React.useState(); + + // Download disk image file + const downloadDiskImage = async (entry: DiskImage) => { + setDlProgress(0); + + try { + const blob = await DiskImageApi.Download(entry, setDlProgress); + + downloadBlob(blob, entry.file_name); + } catch (e) { + console.error(e); + alert(`Failed to download disk image file! ${e}`); + } + + setDlProgress(undefined); + }; + // Delete disk image const deleteDiskImage = async (entry: DiskImage) => { if ( @@ -220,7 +241,7 @@ function DiskImageList(p: { return ( <> - downloadIso(params.row)}> + downloadDiskImage(params.row)}> @@ -236,6 +257,31 @@ function DiskImageList(p: { ]; return ( - c.file_name} rows={p.list} columns={columns} /> + <> + {/* Download notification */} + {dlProgress !== undefined && ( + +
+ + Downloading... {dlProgress}% + + +
+
+ )} + c.file_name} rows={p.list} columns={columns} /> + ); }