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