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"))
 | 
					    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
 | 
					/// Delete a disk image
 | 
				
			||||||
pub async fn delete(p: web::Path<DiskFilePath>) -> HttpResult {
 | 
					pub async fn delete(p: web::Path<DiskFilePath>) -> HttpResult {
 | 
				
			||||||
    if !files_utils::check_file_name(&p.filename) {
 | 
					    if !files_utils::check_file_name(&p.filename) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -352,6 +352,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                "/api/disk_images/{filename}/convert",
 | 
					                "/api/disk_images/{filename}/convert",
 | 
				
			||||||
                web::post().to(disk_images_controller::convert),
 | 
					                web::post().to(disk_images_controller::convert),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/disk_images/{filename}/rename",
 | 
				
			||||||
 | 
					                web::post().to(disk_images_controller::rename),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/disk_images/{filename}",
 | 
					                "/api/disk_images/{filename}",
 | 
				
			||||||
                web::delete().to(disk_images_controller::delete),
 | 
					                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
 | 
					   * Delete disk image file
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
import DownloadIcon from "@mui/icons-material/Download";
 | 
					import DownloadIcon from "@mui/icons-material/Download";
 | 
				
			||||||
import LoopIcon from "@mui/icons-material/Loop";
 | 
					import LoopIcon from "@mui/icons-material/Loop";
 | 
				
			||||||
 | 
					import MoreVertIcon from "@mui/icons-material/MoreVert";
 | 
				
			||||||
import RefreshIcon from "@mui/icons-material/Refresh";
 | 
					import RefreshIcon from "@mui/icons-material/Refresh";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Alert,
 | 
					  Alert,
 | 
				
			||||||
@@ -8,6 +9,10 @@ import {
 | 
				
			|||||||
  CircularProgress,
 | 
					  CircularProgress,
 | 
				
			||||||
  IconButton,
 | 
					  IconButton,
 | 
				
			||||||
  LinearProgress,
 | 
					  LinearProgress,
 | 
				
			||||||
 | 
					  ListItemIcon,
 | 
				
			||||||
 | 
					  ListItemText,
 | 
				
			||||||
 | 
					  Menu,
 | 
				
			||||||
 | 
					  MenuItem,
 | 
				
			||||||
  Tooltip,
 | 
					  Tooltip,
 | 
				
			||||||
  Typography,
 | 
					  Typography,
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
@@ -164,15 +169,11 @@ function DiskImageList(p: {
 | 
				
			|||||||
  const confirm = useConfirm();
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
  const loadingMessage = useLoadingMessage();
 | 
					  const loadingMessage = useLoadingMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [dlProgress, setDlProgress] = React.useState<undefined | number>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [currConversion, setCurrConversion] = React.useState<
 | 
					  const [currConversion, setCurrConversion] = React.useState<
 | 
				
			||||||
    DiskImage | undefined
 | 
					    DiskImage | undefined
 | 
				
			||||||
  >();
 | 
					  >();
 | 
				
			||||||
  const [dlProgress, setDlProgress] = React.useState<undefined | number>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Convert disk image file
 | 
					 | 
				
			||||||
  const convertDiskImage = (entry: DiskImage) => {
 | 
					 | 
				
			||||||
    setCurrConversion(entry);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Download disk image file
 | 
					  // Download disk image file
 | 
				
			||||||
  const downloadDiskImage = async (entry: DiskImage) => {
 | 
					  const downloadDiskImage = async (entry: DiskImage) => {
 | 
				
			||||||
@@ -190,6 +191,11 @@ function DiskImageList(p: {
 | 
				
			|||||||
    setDlProgress(undefined);
 | 
					    setDlProgress(undefined);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Convert disk image file
 | 
				
			||||||
 | 
					  const convertDiskImage = (entry: DiskImage) => {
 | 
				
			||||||
 | 
					    setCurrConversion(entry);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Delete disk image
 | 
					  // Delete disk image
 | 
				
			||||||
  const deleteDiskImage = async (entry: DiskImage) => {
 | 
					  const deleteDiskImage = async (entry: DiskImage) => {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
@@ -221,7 +227,7 @@ function DiskImageList(p: {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const columns: GridColDef<(typeof p.list)[number]>[] = [
 | 
					  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",
 | 
					      field: "format",
 | 
				
			||||||
      headerName: "Format",
 | 
					      headerName: "Format",
 | 
				
			||||||
@@ -260,28 +266,21 @@ function DiskImageList(p: {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      field: "actions",
 | 
					      field: "actions",
 | 
				
			||||||
 | 
					      type: "actions",
 | 
				
			||||||
      headerName: "",
 | 
					      headerName: "",
 | 
				
			||||||
      width: 140,
 | 
					      width: 55,
 | 
				
			||||||
      renderCell(params) {
 | 
					      cellClassName: "actions",
 | 
				
			||||||
        return (
 | 
					      editable: false,
 | 
				
			||||||
          <>
 | 
					      getActions: (params) => {
 | 
				
			||||||
            <Tooltip title="Convert disk image">
 | 
					        return [
 | 
				
			||||||
              <IconButton onClick={() => { convertDiskImage(params.row); }}>
 | 
					          <DiskImageActionMenu
 | 
				
			||||||
                <LoopIcon />
 | 
					            key="menu"
 | 
				
			||||||
              </IconButton>
 | 
					            diskImage={params.row}
 | 
				
			||||||
            </Tooltip>
 | 
					            onDownload={downloadDiskImage}
 | 
				
			||||||
            <Tooltip title="Download disk image">
 | 
					            onConvert={convertDiskImage}
 | 
				
			||||||
              <IconButton onClick={() => downloadDiskImage(params.row)}>
 | 
					            onDelete={deleteDiskImage}
 | 
				
			||||||
                <DownloadIcon />
 | 
					          />,
 | 
				
			||||||
              </IconButton>
 | 
					        ];
 | 
				
			||||||
            </Tooltip>
 | 
					 | 
				
			||||||
            <Tooltip title="Delete disk image">
 | 
					 | 
				
			||||||
              <IconButton onClick={() => deleteDiskImage(params.row)}>
 | 
					 | 
				
			||||||
                <DeleteIcon />
 | 
					 | 
				
			||||||
              </IconButton>
 | 
					 | 
				
			||||||
            </Tooltip>
 | 
					 | 
				
			||||||
          </>
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
@@ -327,7 +326,92 @@ function DiskImageList(p: {
 | 
				
			|||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* The table itself */}
 | 
					      {/* 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" }}
 | 
					              right={{ verb: "POST", path: "/api/disk_images/*/convert" }}
 | 
				
			||||||
              label="Convert disk images"
 | 
					              label="Convert disk images"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					            <RouteRight
 | 
				
			||||||
 | 
					              {...p}
 | 
				
			||||||
 | 
					              right={{ verb: "POST", path: "/api/disk_images/*/rename" }}
 | 
				
			||||||
 | 
					              label="Rename disk images"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
            <RouteRight
 | 
					            <RouteRight
 | 
				
			||||||
              {...p}
 | 
					              {...p}
 | 
				
			||||||
              right={{ verb: "DELETE", path: "/api/disk_images/*" }}
 | 
					              right={{ verb: "DELETE", path: "/api/disk_images/*" }}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user