Can rename disk image files
This commit is contained in:
		@@ -189,6 +189,46 @@ pub async fn handle_convert_request(
 | 
			
		||||
    Ok(HttpResponse::Accepted().json("Successfully converted disk file"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct RenameDiskImageRequest {
 | 
			
		||||
    name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Rename disk image
 | 
			
		||||
pub async fn rename(
 | 
			
		||||
    p: web::Path<DiskFilePath>,
 | 
			
		||||
    req: web::Json<RenameDiskImageRequest>,
 | 
			
		||||
) -> HttpResult {
 | 
			
		||||
    // Check source
 | 
			
		||||
    if !files_utils::check_file_name(&p.filename) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid src file name!"));
 | 
			
		||||
    }
 | 
			
		||||
    let src_path = AppConfig::get().disk_images_file_path(&p.filename);
 | 
			
		||||
    if !src_path.exists() {
 | 
			
		||||
        return Ok(HttpResponse::NotFound().json("Disk image does not exists!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check destination
 | 
			
		||||
    if !files_utils::check_file_name(&req.name) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid dst file name!"));
 | 
			
		||||
    }
 | 
			
		||||
    let dst_path = AppConfig::get().disk_images_file_path(&req.name);
 | 
			
		||||
    if dst_path.exists() {
 | 
			
		||||
        return Ok(HttpResponse::Conflict().json("Destination name already exists!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check extension
 | 
			
		||||
    let disk = DiskFileInfo::load_file(&src_path)?;
 | 
			
		||||
    if !disk.format.ext().iter().any(|e| req.name.ends_with(e)) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Perform rename
 | 
			
		||||
    std::fs::rename(&src_path, &dst_path)?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Accepted().finish())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Delete a disk image
 | 
			
		||||
pub async fn delete(p: web::Path<DiskFilePath>) -> HttpResult {
 | 
			
		||||
    if !files_utils::check_file_name(&p.filename) {
 | 
			
		||||
 
 | 
			
		||||
@@ -352,6 +352,10 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/disk_images/{filename}/convert",
 | 
			
		||||
                web::post().to(disk_images_controller::convert),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/disk_images/{filename}/rename",
 | 
			
		||||
                web::post().to(disk_images_controller::rename),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/disk_images/{filename}",
 | 
			
		||||
                web::delete().to(disk_images_controller::delete),
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,17 @@ export class DiskImageApi {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rename disk image file
 | 
			
		||||
   */
 | 
			
		||||
  static async Rename(file: DiskImage, name: string): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      uri: `/disk_images/${file.file_name}/rename`,
 | 
			
		||||
      jsonData: { name },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete disk image file
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
import DownloadIcon from "@mui/icons-material/Download";
 | 
			
		||||
import LoopIcon from "@mui/icons-material/Loop";
 | 
			
		||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
 | 
			
		||||
import RefreshIcon from "@mui/icons-material/Refresh";
 | 
			
		||||
import {
 | 
			
		||||
  Alert,
 | 
			
		||||
@@ -8,6 +9,10 @@ import {
 | 
			
		||||
  CircularProgress,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  LinearProgress,
 | 
			
		||||
  ListItemIcon,
 | 
			
		||||
  ListItemText,
 | 
			
		||||
  Menu,
 | 
			
		||||
  MenuItem,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
@@ -164,15 +169,11 @@ function DiskImageList(p: {
 | 
			
		||||
  const confirm = useConfirm();
 | 
			
		||||
  const loadingMessage = useLoadingMessage();
 | 
			
		||||
 | 
			
		||||
  const [dlProgress, setDlProgress] = React.useState<undefined | number>();
 | 
			
		||||
 | 
			
		||||
  const [currConversion, setCurrConversion] = React.useState<
 | 
			
		||||
    DiskImage | undefined
 | 
			
		||||
  >();
 | 
			
		||||
  const [dlProgress, setDlProgress] = React.useState<undefined | number>();
 | 
			
		||||
 | 
			
		||||
  // Convert disk image file
 | 
			
		||||
  const convertDiskImage = (entry: DiskImage) => {
 | 
			
		||||
    setCurrConversion(entry);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Download disk image file
 | 
			
		||||
  const downloadDiskImage = async (entry: DiskImage) => {
 | 
			
		||||
@@ -190,6 +191,11 @@ function DiskImageList(p: {
 | 
			
		||||
    setDlProgress(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Convert disk image file
 | 
			
		||||
  const convertDiskImage = (entry: DiskImage) => {
 | 
			
		||||
    setCurrConversion(entry);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Delete disk image
 | 
			
		||||
  const deleteDiskImage = async (entry: DiskImage) => {
 | 
			
		||||
    if (
 | 
			
		||||
@@ -221,7 +227,7 @@ function DiskImageList(p: {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  const columns: GridColDef<(typeof p.list)[number]>[] = [
 | 
			
		||||
    { field: "file_name", headerName: "File name", flex: 3 },
 | 
			
		||||
    { field: "file_name", headerName: "File name", flex: 3, editable: true },
 | 
			
		||||
    {
 | 
			
		||||
      field: "format",
 | 
			
		||||
      headerName: "Format",
 | 
			
		||||
@@ -260,28 +266,21 @@ function DiskImageList(p: {
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      field: "actions",
 | 
			
		||||
      type: "actions",
 | 
			
		||||
      headerName: "",
 | 
			
		||||
      width: 140,
 | 
			
		||||
      renderCell(params) {
 | 
			
		||||
        return (
 | 
			
		||||
          <>
 | 
			
		||||
            <Tooltip title="Convert disk image">
 | 
			
		||||
              <IconButton onClick={() => { convertDiskImage(params.row); }}>
 | 
			
		||||
                <LoopIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Tooltip title="Download disk image">
 | 
			
		||||
              <IconButton onClick={() => downloadDiskImage(params.row)}>
 | 
			
		||||
                <DownloadIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Tooltip title="Delete disk image">
 | 
			
		||||
              <IconButton onClick={() => deleteDiskImage(params.row)}>
 | 
			
		||||
                <DeleteIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
          </>
 | 
			
		||||
        );
 | 
			
		||||
      width: 55,
 | 
			
		||||
      cellClassName: "actions",
 | 
			
		||||
      editable: false,
 | 
			
		||||
      getActions: (params) => {
 | 
			
		||||
        return [
 | 
			
		||||
          <DiskImageActionMenu
 | 
			
		||||
            key="menu"
 | 
			
		||||
            diskImage={params.row}
 | 
			
		||||
            onDownload={downloadDiskImage}
 | 
			
		||||
            onConvert={convertDiskImage}
 | 
			
		||||
            onDelete={deleteDiskImage}
 | 
			
		||||
          />,
 | 
			
		||||
        ];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
@@ -327,7 +326,92 @@ function DiskImageList(p: {
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {/* The table itself */}
 | 
			
		||||
      <DataGrid getRowId={(c) => c.file_name} rows={p.list} columns={columns} />
 | 
			
		||||
      <DataGrid<DiskImage>
 | 
			
		||||
        getRowId={(c) => c.file_name}
 | 
			
		||||
        rows={p.list}
 | 
			
		||||
        columns={columns}
 | 
			
		||||
        processRowUpdate={async (n, o) => {
 | 
			
		||||
          try {
 | 
			
		||||
            await DiskImageApi.Rename(o, n.file_name);
 | 
			
		||||
            return n;
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            console.error("Failed to rename disk image!", e);
 | 
			
		||||
            alert(`Failed to rename disk image! ${e}`);
 | 
			
		||||
            throw e;
 | 
			
		||||
          } finally {
 | 
			
		||||
            p.onReload();
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function DiskImageActionMenu(p: {
 | 
			
		||||
  diskImage: DiskImage;
 | 
			
		||||
  onDownload: (d: DiskImage) => void;
 | 
			
		||||
  onConvert: (d: DiskImage) => void;
 | 
			
		||||
  onDelete: (d: DiskImage) => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
 | 
			
		||||
  const open = Boolean(anchorEl);
 | 
			
		||||
  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
 | 
			
		||||
    setAnchorEl(event.currentTarget);
 | 
			
		||||
  };
 | 
			
		||||
  const handleClose = () => {
 | 
			
		||||
    setAnchorEl(null);
 | 
			
		||||
  };
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <IconButton
 | 
			
		||||
        aria-label="Actions"
 | 
			
		||||
        aria-haspopup="true"
 | 
			
		||||
        onClick={handleClick}
 | 
			
		||||
      >
 | 
			
		||||
        <MoreVertIcon />
 | 
			
		||||
      </IconButton>
 | 
			
		||||
      <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
 | 
			
		||||
        {/* Download disk image */}
 | 
			
		||||
        <MenuItem
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            handleClose();
 | 
			
		||||
            p.onDownload(p.diskImage);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ListItemIcon>
 | 
			
		||||
            <DownloadIcon />
 | 
			
		||||
          </ListItemIcon>
 | 
			
		||||
          <ListItemText secondary={"Download disk image"}>
 | 
			
		||||
            Download
 | 
			
		||||
          </ListItemText>
 | 
			
		||||
        </MenuItem>
 | 
			
		||||
 | 
			
		||||
        {/* Convert disk image */}
 | 
			
		||||
        <MenuItem
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            handleClose();
 | 
			
		||||
            p.onConvert(p.diskImage);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ListItemIcon>
 | 
			
		||||
            <LoopIcon />
 | 
			
		||||
          </ListItemIcon>
 | 
			
		||||
          <ListItemText secondary={"Convert disk image"}>Convert</ListItemText>
 | 
			
		||||
        </MenuItem>
 | 
			
		||||
 | 
			
		||||
        {/* Delete disk image */}
 | 
			
		||||
        <MenuItem
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            handleClose();
 | 
			
		||||
            p.onDelete(p.diskImage);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ListItemIcon>
 | 
			
		||||
            <DeleteIcon color="error" />
 | 
			
		||||
          </ListItemIcon>
 | 
			
		||||
          <ListItemText secondary={"Delete disk image"}>Delete</ListItemText>
 | 
			
		||||
        </MenuItem>
 | 
			
		||||
      </Menu>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -707,6 +707,11 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
              right={{ verb: "POST", path: "/api/disk_images/*/convert" }}
 | 
			
		||||
              label="Convert disk images"
 | 
			
		||||
            />
 | 
			
		||||
            <RouteRight
 | 
			
		||||
              {...p}
 | 
			
		||||
              right={{ verb: "POST", path: "/api/disk_images/*/rename" }}
 | 
			
		||||
              label="Rename disk images"
 | 
			
		||||
            />
 | 
			
		||||
            <RouteRight
 | 
			
		||||
              {...p}
 | 
			
		||||
              right={{ verb: "DELETE", path: "/api/disk_images/*" }}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user