Can get the list of uploaded disk images
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		@@ -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))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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 info = DiskFileInfo::load_file(file)?;
 | 
			
		||||
 | 
			
		||||
        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");
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: info.name,
 | 
			
		||||
 | 
			
		||||
        // Approximate raw file estimation
 | 
			
		||||
        let is_raw_sparse = metadata.len() / 512 >= metadata.st_blocks();
 | 
			
		||||
            // 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),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
        let format = match ext {
 | 
			
		||||
            "qcow2" => DiskFormat::QCow2,
 | 
			
		||||
            "raw" => DiskFormat::Raw {
 | 
			
		||||
                alloc_type: match is_raw_sparse {
 | 
			
		||||
            format: match info.format {
 | 
			
		||||
                DiskFileFormat::Raw { is_sparse } => DiskFormat::Raw {
 | 
			
		||||
                    alloc_type: match is_sparse {
 | 
			
		||||
                        true => DiskAllocType::Sparse,
 | 
			
		||||
                        false => DiskAllocType::Fixed,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            _ => anyhow::bail!("Unsupported disk extension: {ext}!"),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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),
 | 
			
		||||
                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!(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user