Changed Download Handler Behavior (#2964)

- Updated Handler for files with size of 49GB or less
- Used Browser Handler tor size of more than 50GB

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-07-26 22:08:35 -06:00
committed by GitHub
parent ad502b9f18
commit 3ce377dbd1
3 changed files with 74 additions and 132 deletions

View File

@@ -13,7 +13,6 @@
"@mui/styles": "^5.12.0",
"@mui/x-date-pickers": "^5.0.20",
"@reduxjs/toolkit": "^1.9.5",
"@types/streamsaver": "^2.0.1",
"@uiw/react-textarea-code-editor": "^2.1.1",
"kbar": "^0.1.0-beta.39",
"local-storage-fallback": "^4.1.1",
@@ -33,7 +32,6 @@
"react-window": "^1.8.9",
"react-window-infinite-loader": "^1.0.9",
"recharts": "^2.6.2",
"streamsaver": "^2.0.6",
"styled-components": "^5.3.11",
"superagent": "^8.0.8",
"tinycolor2": "^1.6.0",

View File

@@ -17,7 +17,6 @@
import { BucketObjectItem } from "./ListObjects/types";
import { encodeURLString } from "../../../../../common/utils";
import { removeTrace } from "../../../ObjectBrowser/transferManager";
import streamSaver from "streamsaver";
import store from "../../../../../store";
import { PermissionResource } from "api/consoleApi";
@@ -50,146 +49,101 @@ export const download = (
if (versionID) {
path = path.concat(`&version_id=${versionID}`);
}
return new DownloadHelper(
path,
id,
anonymousMode,
fileSize,
progressCallback,
completeCallback,
errorCallback,
abortCallback,
toastCallback,
// If file is greater than 50GiB then we force browser download, if not then we use HTTP Request for Object Manager
if (fileSize > 53687091200) {
return new BrowserDownload(path, id, completeCallback, toastCallback);
}
let req = new XMLHttpRequest();
req.open("GET", path, true);
if (anonymousMode) {
req.setRequestHeader("X-Anonymous", "1");
}
req.addEventListener(
"progress",
function (evt) {
let percentComplete = Math.round((evt.loaded / fileSize) * 100);
if (progressCallback) {
progressCallback(percentComplete);
}
},
false,
);
req.responseType = "blob";
req.onreadystatechange = () => {
if (req.readyState === 4) {
if (req.status === 200) {
const rspHeader = req.getResponseHeader("Content-Disposition");
let filename = "download";
if (rspHeader) {
let rspHeaderDecoded = decodeURIComponent(rspHeader);
filename = rspHeaderDecoded.split('"')[1];
}
if (completeCallback) {
completeCallback();
}
removeTrace(id);
var link = document.createElement("a");
link.href = window.URL.createObjectURL(req.response);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
if (req.getResponseHeader("Content-Type") === "application/json") {
const rspBody: { detailedMessage?: string } = JSON.parse(
req.response,
);
if (rspBody.detailedMessage) {
errorCallback(rspBody.detailedMessage);
return;
}
}
errorCallback(`Unexpected response status code (${req.status}).`);
}
}
};
req.onerror = () => {
if (errorCallback) {
errorCallback("A network error occurred.");
}
};
req.onabort = () => {
if (abortCallback) {
abortCallback();
}
};
return req;
};
class DownloadHelper {
aborter: AbortController;
class BrowserDownload {
path: string;
id: string;
filename: string = "";
anonymousMode: boolean;
fileSize: number = 0;
writer: any = null;
progressCallback: (progress: number) => void;
completeCallback: () => void;
errorCallback: (msg: string) => void;
abortCallback: () => void;
toastCallback: () => void;
constructor(
path: string,
id: string,
anonymousMode: boolean,
fileSize: number,
progressCallback: (progress: number) => void,
completeCallback: () => void,
errorCallback: (msg: string) => void,
abortCallback: () => void,
toastCallback: () => void,
) {
this.aborter = new AbortController();
this.path = path;
this.id = id;
this.anonymousMode = anonymousMode;
this.fileSize = fileSize;
this.progressCallback = progressCallback;
this.completeCallback = completeCallback;
this.errorCallback = errorCallback;
this.abortCallback = abortCallback;
this.toastCallback = toastCallback;
}
abort(): void {
this.aborter.abort();
this.abortCallback();
if (this.writer) {
this.writer.abort();
}
}
send(): void {
let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari) {
this.toastCallback();
this.downloadByBrowser();
} else if (!this.fileSize) {
this.downloadByBrowser();
} else {
this.download({
url: this.path,
chunkSize: 1024 * 1024 * 1024 * 1.5,
});
}
}
async getRangeContent(url: string, start: number, end: number) {
const info = this.getRequestInfo(start, end);
const response = await fetch(url, info);
if (response.ok && response.body) {
if (!this.filename) {
this.filename = this.getFilename(response);
}
if (!this.writer) {
this.writer = streamSaver.createWriteStream(this.filename).getWriter();
}
const reader = response.body.getReader();
let done, value;
while (!done) {
({ value, done } = await reader.read());
if (done) {
break;
}
await this.writer.write(value);
}
} else {
throw new Error(`Unexpected response status code (${response.status}).`);
}
}
getRequestInfo(start: number, end: number) {
const info: RequestInit = {
signal: this.aborter.signal,
headers: { range: `bytes=${start}-${end}` },
};
if (this.anonymousMode) {
info.headers = { ...info.headers, "X-Anonymous": "1" };
}
return info;
}
getFilename(response: Response) {
const rspHeader = response.headers.get("Content-Disposition");
if (rspHeader) {
let rspHeaderDecoded = decodeURIComponent(rspHeader);
return rspHeaderDecoded.split('"')[1];
}
return "download";
}
async download({ url, chunkSize }: any) {
const numberOfChunks = Math.ceil(this.fileSize / chunkSize);
this.progressCallback(0);
try {
for (let i = 0; i < numberOfChunks; i++) {
let start = i * chunkSize;
let end =
i + 1 === numberOfChunks
? this.fileSize - 1
: (i + 1) * chunkSize - 1;
await this.getRangeContent(url, start, end);
let percentComplete = Math.round(((i + 1) / numberOfChunks) * 100);
this.progressCallback(percentComplete);
}
this.writer.close();
this.completeCallback();
removeTrace(this.id);
} catch (e: any) {
this.errorCallback(e.message);
}
}
downloadByBrowser() {
this.toastCallback();
const link = document.createElement("a");
link.href = this.path;
document.body.appendChild(link);

View File

@@ -2782,11 +2782,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/streamsaver@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/streamsaver/-/streamsaver-2.0.1.tgz#fa5e5b891d1b282be3078c232a30ee004b8e0be0"
integrity sha512-I49NtT8w6syBI3Zg3ixCyygTHoTVMY0z2TMRcTgccdIsVd2MwlKk7ITLHLsJtgchUHcOd7QEARG9h0ifcA6l2Q==
"@types/styled-components@^5.1.25":
version "5.1.26"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af"
@@ -11115,11 +11110,6 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
streamsaver@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/streamsaver/-/streamsaver-2.0.6.tgz#869d2347dd70191e0ac888d52296956a8cba2ed9"
integrity sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"