Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions portal-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
194 changes: 74 additions & 120 deletions portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);
Expand Down
10 changes: 0 additions & 10 deletions portal-ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down