2 Commits

Author SHA1 Message Date
90f4bf35e9 Add checkbox for sparse file conversion
Some checks failed
continuous-integration/drone/push Build is failing
2025-05-29 12:18:26 +02:00
80d6fe0298 Can perform basic file copy 2025-05-29 12:15:52 +02:00
3 changed files with 84 additions and 10 deletions

View File

@ -123,3 +123,6 @@ pub const QEMU_IMAGE_PROGRAM: &str = "/usr/bin/qemu-img";
/// IP program path
pub const IP_PROGRAM: &str = "/usr/sbin/ip";
/// Copy program path
pub const COPY_PROGRAM: &str = "/bin/cp";

View File

@ -1,7 +1,8 @@
use crate::app_config::AppConfig;
use crate::constants;
use crate::utils::file_size_utils::FileSize;
use std::os::linux::fs::MetadataExt;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::UNIX_EPOCH;
@ -11,9 +12,11 @@ enum DisksError {
Parse(&'static str),
#[error("DiskCreateError")]
Create,
#[error("DiskConvertError: {0}")]
Convert(String),
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Copy, Clone)]
#[derive(Debug, serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq)]
#[serde(tag = "format")]
pub enum DiskFileFormat {
Raw {
@ -42,6 +45,7 @@ impl DiskFileFormat {
/// Disk file information
#[derive(serde::Serialize)]
pub struct DiskFileInfo {
pub file_path: PathBuf,
pub file_size: FileSize,
#[serde(flatten)]
pub format: DiskFileFormat,
@ -83,6 +87,7 @@ impl DiskFileInfo {
};
Ok(Self {
file_path: file.to_path_buf(),
name,
file_size: FileSize::from_bytes(metadata.len() as usize),
format,
@ -145,7 +150,62 @@ impl DiskFileInfo {
/// Copy / convert file disk image into a new destination with optionally a new file format
pub fn convert(&self, dest_file: &Path, dest_format: DiskFileFormat) -> anyhow::Result<()> {
todo!()
// Create a temporary directory to perform the operation
let temp_dir = tempfile::tempdir_in(&AppConfig::get().temp_dir)?;
let temp_file = temp_dir.path().join("temp_file");
// Prepare the conversion
let mut cmd = match (self.format, dest_format) {
// Dumb copy of file
(a, b) if a == b => {
let mut cmd = Command::new(constants::COPY_PROGRAM);
cmd.arg("--sparse=auto")
.arg(&self.file_path)
.arg(&temp_file);
cmd
}
// By default, conversion is unsupported
(src, dest) => {
return Err(DisksError::Convert(format!(
"Conversion from {src:?} to {dest:?} is not supported!"
))
.into());
}
};
// Execute the conversion
let command_s = format!(
"{} {}",
cmd.get_program().display(),
cmd.get_args()
.map(|a| format!("'{}'", a.display()))
.collect::<Vec<String>>()
.join(" ")
);
let cmd_output = cmd.output()?;
if !cmd_output.status.success() {
return Err(DisksError::Convert(format!(
"Command failed:\n{command_s}\nStatus: {}\nstdout: {}\nstderr: {}",
cmd_output.status,
String::from_utf8_lossy(&cmd_output.stdout),
String::from_utf8_lossy(&cmd_output.stderr)
))
.into());
}
// Check the file was created
if !temp_file.is_file() {
return Err(DisksError::Convert(
"Temporary was not created after execution of command!".to_string(),
)
.into());
}
// Move the file to its final location
std::fs::rename(temp_file, dest_file)?;
Ok(())
}
}

View File

@ -6,17 +6,16 @@ import {
DialogContentText,
DialogTitle,
} from "@mui/material";
import { DiskImage, DiskImageApi, DiskImageFormat } from "../api/DiskImageApi";
import React from "react";
import { FileDiskImageWidget } from "../widgets/FileDiskImageWidget";
import { FileInput } from "../widgets/forms/FileInput";
import { TextInput } from "../widgets/forms/TextInput";
import { DiskImage, DiskImageApi, DiskImageFormat } from "../api/DiskImageApi";
import { ServerApi } from "../api/ServerApi";
import { SelectInput } from "../widgets/forms/SelectInput";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { FileDiskImageWidget } from "../widgets/FileDiskImageWidget";
import { CheckboxInput } from "../widgets/forms/CheckboxInput";
import { SelectInput } from "../widgets/forms/SelectInput";
import { TextInput } from "../widgets/forms/TextInput";
export function ConvertDiskImageDialog(p: {
image: DiskImage;
@ -85,6 +84,18 @@ export function ConvertDiskImageDialog(p: {
]}
/>
{/* Check for sparse file */}
{format.format === "Raw" && (
<CheckboxInput
editable
label="Sparse file"
checked={format.is_sparse === true}
onValueChange={(c) => {
setFormat({ format: "Raw", is_sparse: c });
}}
/>
)}
{/* New image name */}
<TextInput
editable