Can get the list of uploaded disk images
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Pierre HUBERT 2025-05-27 21:17:16 +02:00
parent b55880b43c
commit 19ec9992be
3 changed files with 117 additions and 30 deletions

View File

@ -1,6 +1,7 @@
use crate::app_config::AppConfig;
use crate::constants;
use crate::controllers::HttpResult;
use crate::utils::file_disks_utils::DiskFileInfo;
use crate::utils::files_utils;
use actix_multipart::form::MultipartForm;
use actix_multipart::form::tempfile::TempFile;
@ -55,3 +56,14 @@ pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>)
Ok(HttpResponse::Ok().json("Successfully uploaded disk image!"))
}
/// Get disk images list
pub async fn get_list() -> HttpResult {
let mut list = vec![];
for entry in AppConfig::get().disk_images_storage_path().read_dir()? {
let entry = entry?;
list.push(DiskFileInfo::load_file(&entry.path())?);
}
Ok(HttpResponse::Ok().json(list))
}

View File

@ -1,4 +1,3 @@
use std::cmp::max;
use actix::Actor;
use actix_cors::Cors;
use actix_identity::IdentityMiddleware;
@ -14,6 +13,7 @@ use actix_web::middleware::Logger;
use actix_web::web::Data;
use actix_web::{App, HttpServer, web};
use light_openid::basic_state_manager::BasicStateManager;
use std::cmp::max;
use std::time::Duration;
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
@ -121,7 +121,10 @@ async fn main() -> std::io::Result<()> {
}))
.app_data(conn.clone())
// Uploaded files
.app_data(MultipartFormConfig::default().total_limit(max(constants::DISK_IMAGE_MAX_SIZE,constants::ISO_MAX_SIZE)))
.app_data(
MultipartFormConfig::default()
.total_limit(max(constants::DISK_IMAGE_MAX_SIZE, constants::ISO_MAX_SIZE)),
)
.app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
// Server controller
.route(
@ -337,6 +340,10 @@ async fn main() -> std::io::Result<()> {
"/api/disk_images/upload",
web::post().to(disk_images_controller::upload),
)
.route(
"/api/disk_images/list",
web::get().to(disk_images_controller::get_list),
)
// API tokens controller
.route(
"/api/token/create",

View File

@ -6,6 +6,7 @@ use lazy_regex::regex;
use std::os::linux::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::UNIX_EPOCH;
#[derive(thiserror::Error, Debug)]
enum DisksError {
@ -52,35 +53,28 @@ impl FileDisk {
pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
let file = Path::new(path);
if !file.is_file() {
return Err(DisksError::Parse("Path is not a file!").into());
}
let metadata = file.metadata()?;
let name = file.file_stem().and_then(|s| s.to_str()).unwrap_or("disk");
let ext = file.extension().and_then(|s| s.to_str()).unwrap_or("raw");
// Approximate raw file estimation
let is_raw_sparse = metadata.len() / 512 >= metadata.st_blocks();
let format = match ext {
"qcow2" => DiskFormat::QCow2,
"raw" => DiskFormat::Raw {
alloc_type: match is_raw_sparse {
true => DiskAllocType::Sparse,
false => DiskAllocType::Fixed,
},
},
_ => anyhow::bail!("Unsupported disk extension: {ext}!"),
};
let info = DiskFileInfo::load_file(file)?;
Ok(Self {
name: name.to_string(),
size: match format {
DiskFormat::Raw { .. } => metadata.len() as usize / (1000 * 1000),
DiskFormat::QCow2 => qcow_virt_size(path)? / (1000 * 1000),
name: info.name,
// Get only the virtual size of the file
size: match info.format {
DiskFileFormat::Raw { .. } => info.file_size,
DiskFileFormat::QCow2 { virtual_size } => virtual_size,
_ => anyhow::bail!("Unsupported image format: {:?}", info.format),
},
format: match info.format {
DiskFileFormat::Raw { is_sparse } => DiskFormat::Raw {
alloc_type: match is_sparse {
true => DiskAllocType::Sparse,
false => DiskAllocType::Fixed,
},
},
DiskFileFormat::QCow2 { .. } => DiskFormat::QCow2,
_ => anyhow::bail!("Unsupported image format: {:?}", info.format),
},
format,
delete: false,
})
}
@ -180,6 +174,74 @@ impl FileDisk {
}
}
#[derive(Debug, serde::Serialize)]
pub enum DiskFileFormat {
Raw { is_sparse: bool },
QCow2 { virtual_size: usize },
CompressedRaw,
CompressedQCow2,
}
/// Disk file information
#[derive(serde::Serialize)]
pub struct DiskFileInfo {
file_size: usize,
format: DiskFileFormat,
file_name: String,
name: String,
created: u64,
}
impl DiskFileInfo {
/// Get disk image file information
pub fn load_file(file: &Path) -> anyhow::Result<Self> {
if !file.is_file() {
return Err(DisksError::Parse("Path is not a file!").into());
}
// Get file metadata
let metadata = file.metadata()?;
let mut name = file
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("disk")
.to_string();
let ext = file.extension().and_then(|s| s.to_str()).unwrap_or("raw");
// Determine file format
let format = match ext {
"qcow2" => DiskFileFormat::QCow2 {
virtual_size: qcow_virt_size(file)? / (1000 * 1000),
},
"raw" => DiskFileFormat::Raw {
is_sparse: metadata.len() / 512 >= metadata.st_blocks(),
},
"gz" if name.ends_with(".qcow2") => {
name = name.strip_suffix(".qcow2").unwrap_or(&name).to_string();
DiskFileFormat::CompressedQCow2
}
"gz" => DiskFileFormat::CompressedRaw,
_ => anyhow::bail!("Unsupported disk extension: {ext}!"),
};
Ok(Self {
name,
file_size: metadata.len() as usize / (1000 * 1000),
format,
file_name: file
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string(),
created: metadata
.created()?
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
})
}
}
#[derive(serde::Deserialize)]
struct QCowInfoOutput {
#[serde(rename = "virtual-size")]
@ -187,10 +249,16 @@ struct QCowInfoOutput {
}
/// Get QCow2 virtual size
fn qcow_virt_size(path: &str) -> anyhow::Result<usize> {
fn qcow_virt_size(path: &Path) -> anyhow::Result<usize> {
// Run qemu-img
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
cmd.args(["info", path, "--output", "json", "--force-share"]);
cmd.args([
"info",
path.to_str().unwrap_or(""),
"--output",
"json",
"--force-share",
]);
let output = cmd.output()?;
if !output.status.success() {
anyhow::bail!(