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::app_config::AppConfig;
 | 
				
			||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::controllers::HttpResult;
 | 
					use crate::controllers::HttpResult;
 | 
				
			||||||
 | 
					use crate::utils::file_disks_utils::DiskFileInfo;
 | 
				
			||||||
use crate::utils::files_utils;
 | 
					use crate::utils::files_utils;
 | 
				
			||||||
use actix_multipart::form::MultipartForm;
 | 
					use actix_multipart::form::MultipartForm;
 | 
				
			||||||
use actix_multipart::form::tempfile::TempFile;
 | 
					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!"))
 | 
					    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::Actor;
 | 
				
			||||||
use actix_cors::Cors;
 | 
					use actix_cors::Cors;
 | 
				
			||||||
use actix_identity::IdentityMiddleware;
 | 
					use actix_identity::IdentityMiddleware;
 | 
				
			||||||
@@ -14,6 +13,7 @@ use actix_web::middleware::Logger;
 | 
				
			|||||||
use actix_web::web::Data;
 | 
					use actix_web::web::Data;
 | 
				
			||||||
use actix_web::{App, HttpServer, web};
 | 
					use actix_web::{App, HttpServer, web};
 | 
				
			||||||
use light_openid::basic_state_manager::BasicStateManager;
 | 
					use light_openid::basic_state_manager::BasicStateManager;
 | 
				
			||||||
 | 
					use std::cmp::max;
 | 
				
			||||||
use std::time::Duration;
 | 
					use std::time::Duration;
 | 
				
			||||||
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
 | 
					use virtweb_backend::actors::libvirt_actor::LibVirtActor;
 | 
				
			||||||
use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
 | 
					use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
 | 
				
			||||||
@@ -121,7 +121,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
            }))
 | 
					            }))
 | 
				
			||||||
            .app_data(conn.clone())
 | 
					            .app_data(conn.clone())
 | 
				
			||||||
            // Uploaded files
 | 
					            // 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))
 | 
					            .app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
 | 
				
			||||||
            // Server controller
 | 
					            // Server controller
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
@@ -337,6 +340,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                "/api/disk_images/upload",
 | 
					                "/api/disk_images/upload",
 | 
				
			||||||
                web::post().to(disk_images_controller::upload),
 | 
					                web::post().to(disk_images_controller::upload),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/disk_images/list",
 | 
				
			||||||
 | 
					                web::get().to(disk_images_controller::get_list),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // API tokens controller
 | 
					            // API tokens controller
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/token/create",
 | 
					                "/api/token/create",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ use lazy_regex::regex;
 | 
				
			|||||||
use std::os::linux::fs::MetadataExt;
 | 
					use std::os::linux::fs::MetadataExt;
 | 
				
			||||||
use std::path::{Path, PathBuf};
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
use std::process::Command;
 | 
					use std::process::Command;
 | 
				
			||||||
 | 
					use std::time::UNIX_EPOCH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(thiserror::Error, Debug)]
 | 
					#[derive(thiserror::Error, Debug)]
 | 
				
			||||||
enum DisksError {
 | 
					enum DisksError {
 | 
				
			||||||
@@ -52,35 +53,28 @@ impl FileDisk {
 | 
				
			|||||||
    pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
 | 
					    pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
 | 
				
			||||||
        let file = Path::new(path);
 | 
					        let file = Path::new(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if !file.is_file() {
 | 
					        let info = DiskFileInfo::load_file(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}!"),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
            name: name.to_string(),
 | 
					            name: info.name,
 | 
				
			||||||
            size: match format {
 | 
					
 | 
				
			||||||
                DiskFormat::Raw { .. } => metadata.len() as usize / (1000 * 1000),
 | 
					            // Get only the virtual size of the file
 | 
				
			||||||
                DiskFormat::QCow2 => qcow_virt_size(path)? / (1000 * 1000),
 | 
					            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,
 | 
					            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)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
struct QCowInfoOutput {
 | 
					struct QCowInfoOutput {
 | 
				
			||||||
    #[serde(rename = "virtual-size")]
 | 
					    #[serde(rename = "virtual-size")]
 | 
				
			||||||
@@ -187,10 +249,16 @@ struct QCowInfoOutput {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get QCow2 virtual size
 | 
					/// 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
 | 
					    // Run qemu-img
 | 
				
			||||||
    let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
 | 
					    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()?;
 | 
					    let output = cmd.output()?;
 | 
				
			||||||
    if !output.status.success() {
 | 
					    if !output.status.success() {
 | 
				
			||||||
        anyhow::bail!(
 | 
					        anyhow::bail!(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user