Can download ISO files
This commit is contained in:
		@@ -6,7 +6,8 @@ interface RequestParams {
 | 
			
		||||
  allowFail?: boolean;
 | 
			
		||||
  jsonData?: any;
 | 
			
		||||
  formData?: FormData;
 | 
			
		||||
  progress?: (progress: number) => void;
 | 
			
		||||
  upProgress?: (progress: number) => void;
 | 
			
		||||
  downProgress?: (e: { progress: number; total: number }) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface APIResponse {
 | 
			
		||||
@@ -62,11 +63,11 @@ export class APIClient {
 | 
			
		||||
    let status: number;
 | 
			
		||||
 | 
			
		||||
    // Make the request with XMLHttpRequest
 | 
			
		||||
    if (args.progress) {
 | 
			
		||||
    if (args.upProgress) {
 | 
			
		||||
      const res: XMLHttpRequest = await new Promise((resolve, reject) => {
 | 
			
		||||
        const xhr = new XMLHttpRequest();
 | 
			
		||||
        xhr.upload.addEventListener("progress", (e) =>
 | 
			
		||||
          args.progress!(e.loaded / e.total)
 | 
			
		||||
          args.upProgress!(e.loaded / e.total)
 | 
			
		||||
        );
 | 
			
		||||
        xhr.addEventListener("load", () => resolve(xhr));
 | 
			
		||||
        xhr.addEventListener("error", () =>
 | 
			
		||||
@@ -104,6 +105,48 @@ export class APIClient {
 | 
			
		||||
      // Process response
 | 
			
		||||
      if (res.headers.get("content-type") === "application/json")
 | 
			
		||||
        data = await res.json();
 | 
			
		||||
      // Binary file
 | 
			
		||||
      else if (res.body !== null && args.downProgress) {
 | 
			
		||||
        // Track download progress
 | 
			
		||||
        const contentEncoding = res.headers.get("content-encoding");
 | 
			
		||||
        const contentLength = contentEncoding
 | 
			
		||||
          ? null
 | 
			
		||||
          : res.headers.get("content-length");
 | 
			
		||||
 | 
			
		||||
        const total = parseInt(contentLength ?? "0", 10);
 | 
			
		||||
        let loaded = 0;
 | 
			
		||||
 | 
			
		||||
        const resInt = new Response(
 | 
			
		||||
          new ReadableStream({
 | 
			
		||||
            start(controller) {
 | 
			
		||||
              const reader = res.body!.getReader();
 | 
			
		||||
 | 
			
		||||
              const read = async () => {
 | 
			
		||||
                try {
 | 
			
		||||
                  const ret = await reader.read();
 | 
			
		||||
                  if (ret.done) {
 | 
			
		||||
                    controller.close();
 | 
			
		||||
                    return;
 | 
			
		||||
                  }
 | 
			
		||||
                  loaded += ret.value.byteLength;
 | 
			
		||||
                  args.downProgress!({ progress: loaded, total });
 | 
			
		||||
                  controller.enqueue(ret.value);
 | 
			
		||||
                  read();
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                  console.error(e);
 | 
			
		||||
                  controller.error(e);
 | 
			
		||||
                }
 | 
			
		||||
              };
 | 
			
		||||
 | 
			
		||||
              read();
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        data = await resInt.blob();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Do not track progress
 | 
			
		||||
      else data = await res.blob();
 | 
			
		||||
 | 
			
		||||
      status = res.status;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ export class IsoFilesApi {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      uri: "/iso/upload",
 | 
			
		||||
      formData: fd,
 | 
			
		||||
      progress: progress,
 | 
			
		||||
      upProgress: progress,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +47,24 @@ export class IsoFilesApi {
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Download an ISO file
 | 
			
		||||
   */
 | 
			
		||||
  static async Download(
 | 
			
		||||
    file: IsoFile,
 | 
			
		||||
    progress: (p: number) => void
 | 
			
		||||
  ): Promise<Blob> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "GET",
 | 
			
		||||
        uri: `/iso/${file.filename}`,
 | 
			
		||||
        downProgress(e) {
 | 
			
		||||
          progress(Math.floor(100 * (e.progress / e.total)));
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete iso file
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
import {
 | 
			
		||||
  Alert,
 | 
			
		||||
  Button,
 | 
			
		||||
  CircularProgress,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  LinearProgress,
 | 
			
		||||
  TextField,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import DownloadIcon from "@mui/icons-material/Download";
 | 
			
		||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
 | 
			
		||||
import { filesize } from "filesize";
 | 
			
		||||
import { MuiFileInput } from "mui-file-input";
 | 
			
		||||
@@ -19,6 +23,7 @@ import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { VirtWebPaper } from "../widgets/VirtWebPaper";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
import { downloadBlob } from "../utils/FilesUtils";
 | 
			
		||||
 | 
			
		||||
export function IsoFilesRoute(): React.ReactElement {
 | 
			
		||||
  const [list, setList] = React.useState<IsoFile[] | undefined>();
 | 
			
		||||
@@ -185,6 +190,23 @@ function IsoFilesList(p: {
 | 
			
		||||
  const loadingMessage = useLoadingMessage();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [dlProgress, setDlProgress] = React.useState<undefined | number>();
 | 
			
		||||
 | 
			
		||||
  const downloadIso = async (entry: IsoFile) => {
 | 
			
		||||
    setDlProgress(0);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const blob = await IsoFilesApi.Download(entry, setDlProgress);
 | 
			
		||||
 | 
			
		||||
      await downloadBlob(blob, entry.filename);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Failed to download iso file!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setDlProgress(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const deleteIso = async (entry: IsoFile) => {
 | 
			
		||||
    if (
 | 
			
		||||
      !(await confirm(
 | 
			
		||||
@@ -227,13 +249,20 @@ function IsoFilesList(p: {
 | 
			
		||||
    {
 | 
			
		||||
      field: "actions",
 | 
			
		||||
      headerName: "",
 | 
			
		||||
      width: 70,
 | 
			
		||||
      width: 120,
 | 
			
		||||
      renderCell(params) {
 | 
			
		||||
        return (
 | 
			
		||||
          <>
 | 
			
		||||
            <IconButton onClick={() => deleteIso(params.row)}>
 | 
			
		||||
              <DeleteIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Tooltip title="Download file">
 | 
			
		||||
              <IconButton onClick={() => downloadIso(params.row)}>
 | 
			
		||||
                <DownloadIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Tooltip title="Delete file">
 | 
			
		||||
              <IconButton onClick={() => deleteIso(params.row)}>
 | 
			
		||||
                <DeleteIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
          </>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
@@ -241,13 +270,40 @@ function IsoFilesList(p: {
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebPaper label="Files list">
 | 
			
		||||
      <DataGrid
 | 
			
		||||
        getRowId={(c) => c.filename}
 | 
			
		||||
        rows={p.list}
 | 
			
		||||
        columns={columns}
 | 
			
		||||
        autoHeight={true}
 | 
			
		||||
      />
 | 
			
		||||
    </VirtWebPaper>
 | 
			
		||||
    <>
 | 
			
		||||
      <VirtWebPaper label="Files list">
 | 
			
		||||
        {/* Download notification */}
 | 
			
		||||
        {dlProgress !== undefined && (
 | 
			
		||||
          <Alert severity="info">
 | 
			
		||||
            <div
 | 
			
		||||
              style={{
 | 
			
		||||
                display: "flex",
 | 
			
		||||
                flexDirection: "row",
 | 
			
		||||
                alignItems: "center",
 | 
			
		||||
                overflow: "hidden",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Typography variant="body1">
 | 
			
		||||
                Downloading... {dlProgress}%
 | 
			
		||||
              </Typography>
 | 
			
		||||
              <CircularProgress
 | 
			
		||||
                variant="determinate"
 | 
			
		||||
                size={"1.5rem"}
 | 
			
		||||
                style={{ marginLeft: "10px" }}
 | 
			
		||||
                value={dlProgress}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Alert>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {/* Files list table */}
 | 
			
		||||
        <DataGrid
 | 
			
		||||
          getRowId={(c) => c.filename}
 | 
			
		||||
          rows={p.list}
 | 
			
		||||
          columns={columns}
 | 
			
		||||
          autoHeight={true}
 | 
			
		||||
        />
 | 
			
		||||
      </VirtWebPaper>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								virtweb_frontend/src/utils/FilesUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								virtweb_frontend/src/utils/FilesUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
export async function downloadBlob(blob: Blob, filename: string) {
 | 
			
		||||
  const url = URL.createObjectURL(blob);
 | 
			
		||||
 | 
			
		||||
  const link = document.createElement("a");
 | 
			
		||||
  link.href = url;
 | 
			
		||||
  link.target = "_blank";
 | 
			
		||||
  link.rel = "noopener";
 | 
			
		||||
  link.download = filename;
 | 
			
		||||
  link.click();
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user