Skip to content

Commit 3ce377d

Browse files
authored
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>
1 parent ad502b9 commit 3ce377d

File tree

3 files changed

+74
-132
lines changed

3 files changed

+74
-132
lines changed

portal-ui/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"@mui/styles": "^5.12.0",
1414
"@mui/x-date-pickers": "^5.0.20",
1515
"@reduxjs/toolkit": "^1.9.5",
16-
"@types/streamsaver": "^2.0.1",
1716
"@uiw/react-textarea-code-editor": "^2.1.1",
1817
"kbar": "^0.1.0-beta.39",
1918
"local-storage-fallback": "^4.1.1",
@@ -33,7 +32,6 @@
3332
"react-window": "^1.8.9",
3433
"react-window-infinite-loader": "^1.0.9",
3534
"recharts": "^2.6.2",
36-
"streamsaver": "^2.0.6",
3735
"styled-components": "^5.3.11",
3836
"superagent": "^8.0.8",
3937
"tinycolor2": "^1.6.0",

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts

Lines changed: 74 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import { BucketObjectItem } from "./ListObjects/types";
1818
import { encodeURLString } from "../../../../../common/utils";
1919
import { removeTrace } from "../../../ObjectBrowser/transferManager";
20-
import streamSaver from "streamsaver";
2120
import store from "../../../../../store";
2221
import { PermissionResource } from "api/consoleApi";
2322

@@ -50,146 +49,101 @@ export const download = (
5049
if (versionID) {
5150
path = path.concat(`&version_id=${versionID}`);
5251
}
53-
return new DownloadHelper(
54-
path,
55-
id,
56-
anonymousMode,
57-
fileSize,
58-
progressCallback,
59-
completeCallback,
60-
errorCallback,
61-
abortCallback,
62-
toastCallback,
52+
53+
// If file is greater than 50GiB then we force browser download, if not then we use HTTP Request for Object Manager
54+
if (fileSize > 53687091200) {
55+
return new BrowserDownload(path, id, completeCallback, toastCallback);
56+
}
57+
58+
let req = new XMLHttpRequest();
59+
req.open("GET", path, true);
60+
if (anonymousMode) {
61+
req.setRequestHeader("X-Anonymous", "1");
62+
}
63+
req.addEventListener(
64+
"progress",
65+
function (evt) {
66+
let percentComplete = Math.round((evt.loaded / fileSize) * 100);
67+
68+
if (progressCallback) {
69+
progressCallback(percentComplete);
70+
}
71+
},
72+
false,
6373
);
74+
75+
req.responseType = "blob";
76+
req.onreadystatechange = () => {
77+
if (req.readyState === 4) {
78+
if (req.status === 200) {
79+
const rspHeader = req.getResponseHeader("Content-Disposition");
80+
81+
let filename = "download";
82+
if (rspHeader) {
83+
let rspHeaderDecoded = decodeURIComponent(rspHeader);
84+
filename = rspHeaderDecoded.split('"')[1];
85+
}
86+
87+
if (completeCallback) {
88+
completeCallback();
89+
}
90+
91+
removeTrace(id);
92+
93+
var link = document.createElement("a");
94+
link.href = window.URL.createObjectURL(req.response);
95+
link.download = filename;
96+
document.body.appendChild(link);
97+
link.click();
98+
document.body.removeChild(link);
99+
} else {
100+
if (req.getResponseHeader("Content-Type") === "application/json") {
101+
const rspBody: { detailedMessage?: string } = JSON.parse(
102+
req.response,
103+
);
104+
if (rspBody.detailedMessage) {
105+
errorCallback(rspBody.detailedMessage);
106+
return;
107+
}
108+
}
109+
errorCallback(`Unexpected response status code (${req.status}).`);
110+
}
111+
}
112+
};
113+
req.onerror = () => {
114+
if (errorCallback) {
115+
errorCallback("A network error occurred.");
116+
}
117+
};
118+
req.onabort = () => {
119+
if (abortCallback) {
120+
abortCallback();
121+
}
122+
};
123+
124+
return req;
64125
};
65126

66-
class DownloadHelper {
67-
aborter: AbortController;
127+
class BrowserDownload {
68128
path: string;
69129
id: string;
70-
filename: string = "";
71-
anonymousMode: boolean;
72-
fileSize: number = 0;
73-
writer: any = null;
74-
progressCallback: (progress: number) => void;
75130
completeCallback: () => void;
76-
errorCallback: (msg: string) => void;
77-
abortCallback: () => void;
78131
toastCallback: () => void;
79132

80133
constructor(
81134
path: string,
82135
id: string,
83-
anonymousMode: boolean,
84-
fileSize: number,
85-
progressCallback: (progress: number) => void,
86136
completeCallback: () => void,
87-
errorCallback: (msg: string) => void,
88-
abortCallback: () => void,
89137
toastCallback: () => void,
90138
) {
91-
this.aborter = new AbortController();
92139
this.path = path;
93140
this.id = id;
94-
this.anonymousMode = anonymousMode;
95-
this.fileSize = fileSize;
96-
this.progressCallback = progressCallback;
97141
this.completeCallback = completeCallback;
98-
this.errorCallback = errorCallback;
99-
this.abortCallback = abortCallback;
100142
this.toastCallback = toastCallback;
101143
}
102144

103-
abort(): void {
104-
this.aborter.abort();
105-
this.abortCallback();
106-
if (this.writer) {
107-
this.writer.abort();
108-
}
109-
}
110-
111145
send(): void {
112-
let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
113-
if (isSafari) {
114-
this.toastCallback();
115-
this.downloadByBrowser();
116-
} else if (!this.fileSize) {
117-
this.downloadByBrowser();
118-
} else {
119-
this.download({
120-
url: this.path,
121-
chunkSize: 1024 * 1024 * 1024 * 1.5,
122-
});
123-
}
124-
}
125-
126-
async getRangeContent(url: string, start: number, end: number) {
127-
const info = this.getRequestInfo(start, end);
128-
const response = await fetch(url, info);
129-
if (response.ok && response.body) {
130-
if (!this.filename) {
131-
this.filename = this.getFilename(response);
132-
}
133-
if (!this.writer) {
134-
this.writer = streamSaver.createWriteStream(this.filename).getWriter();
135-
}
136-
const reader = response.body.getReader();
137-
let done, value;
138-
while (!done) {
139-
({ value, done } = await reader.read());
140-
if (done) {
141-
break;
142-
}
143-
await this.writer.write(value);
144-
}
145-
} else {
146-
throw new Error(`Unexpected response status code (${response.status}).`);
147-
}
148-
}
149-
150-
getRequestInfo(start: number, end: number) {
151-
const info: RequestInit = {
152-
signal: this.aborter.signal,
153-
headers: { range: `bytes=${start}-${end}` },
154-
};
155-
if (this.anonymousMode) {
156-
info.headers = { ...info.headers, "X-Anonymous": "1" };
157-
}
158-
return info;
159-
}
160-
161-
getFilename(response: Response) {
162-
const rspHeader = response.headers.get("Content-Disposition");
163-
if (rspHeader) {
164-
let rspHeaderDecoded = decodeURIComponent(rspHeader);
165-
return rspHeaderDecoded.split('"')[1];
166-
}
167-
return "download";
168-
}
169-
170-
async download({ url, chunkSize }: any) {
171-
const numberOfChunks = Math.ceil(this.fileSize / chunkSize);
172-
this.progressCallback(0);
173-
try {
174-
for (let i = 0; i < numberOfChunks; i++) {
175-
let start = i * chunkSize;
176-
let end =
177-
i + 1 === numberOfChunks
178-
? this.fileSize - 1
179-
: (i + 1) * chunkSize - 1;
180-
await this.getRangeContent(url, start, end);
181-
let percentComplete = Math.round(((i + 1) / numberOfChunks) * 100);
182-
this.progressCallback(percentComplete);
183-
}
184-
this.writer.close();
185-
this.completeCallback();
186-
removeTrace(this.id);
187-
} catch (e: any) {
188-
this.errorCallback(e.message);
189-
}
190-
}
191-
192-
downloadByBrowser() {
146+
this.toastCallback();
193147
const link = document.createElement("a");
194148
link.href = this.path;
195149
document.body.appendChild(link);

portal-ui/yarn.lock

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,11 +2782,6 @@
27822782
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
27832783
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
27842784

2785-
"@types/streamsaver@^2.0.1":
2786-
version "2.0.1"
2787-
resolved "https://registry.yarnpkg.com/@types/streamsaver/-/streamsaver-2.0.1.tgz#fa5e5b891d1b282be3078c232a30ee004b8e0be0"
2788-
integrity sha512-I49NtT8w6syBI3Zg3ixCyygTHoTVMY0z2TMRcTgccdIsVd2MwlKk7ITLHLsJtgchUHcOd7QEARG9h0ifcA6l2Q==
2789-
27902785
"@types/styled-components@^5.1.25":
27912786
version "5.1.26"
27922787
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af"
@@ -11115,11 +11110,6 @@ statuses@2.0.1:
1111511110
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
1111611111
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
1111711112

11118-
streamsaver@^2.0.6:
11119-
version "2.0.6"
11120-
resolved "https://registry.yarnpkg.com/streamsaver/-/streamsaver-2.0.6.tgz#869d2347dd70191e0ac888d52296956a8cba2ed9"
11121-
integrity sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==
11122-
1112311113
strict-uri-encode@^2.0.0:
1112411114
version "2.0.0"
1112511115
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"

0 commit comments

Comments
 (0)