From 84d47a4c36c1aad1be6462dbe5565b86ff22e23a Mon Sep 17 00:00:00 2001 From: vkovtun <viktor.kovtun@id.ethz.ch> Date: Mon, 22 Apr 2024 19:22:57 +0200 Subject: [PATCH] BIS-753: Implemented file download using the modern File System Access API to avoid loading a whole file into the memory. Does not work in Firefox. --- .../database/data-browser/DataBrowser.jsx | 28 ++++++--- .../data-browser/DataBrowserController.js | 58 +++++++++++++------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/ui-admin/src/js/components/database/data-browser/DataBrowser.jsx b/ui-admin/src/js/components/database/data-browser/DataBrowser.jsx index 06c3328b4b0..5b994811c73 100644 --- a/ui-admin/src/js/components/database/data-browser/DataBrowser.jsx +++ b/ui-admin/src/js/components/database/data-browser/DataBrowser.jsx @@ -12,6 +12,7 @@ import InfoPanel from '@src/js/components/database/data-browser/InfoPanel.jsx' import DataBrowserController from '@src/js/components/database/data-browser/DataBrowserController.js' import messages from '@src/js/common/messages.js' import InfoBar from '@src/js/components/database/data-browser/InfoBar.jsx' +import LoadingDialog from "@src/js/components/common/loading/LoadingDialog.jsx"; const styles = theme => ({ columnFlexContainer: { @@ -258,7 +259,9 @@ class DataBrowser extends React.Component { showInfo: false, path: '/', freeSpace: -1, - totalSpace: -1 + totalSpace: -1, + loading: false, + progress: 0 } this.zip = new JSZip() } @@ -334,14 +337,17 @@ class DataBrowser extends React.Component { async downloadFile(file) { try { - this.setState({ loading: true }) - const blob = await this.fileToBlob(file) - this.downloadBlob(blob, file.name) + this.setState({ loading: true, progress: 0 }) + await this.controller.downloadAndSaveFile(file, this.updateProgress) } finally { - this.setState({ loading: false }) + this.setState({ loading: false, progress: 0 }) } } + updateProgress(progress) { + this.setState({ progress }) + } + downloadBlob(blob, fileName) { const link = document.createElement('a') link.href = window.URL.createObjectURL(blob) @@ -439,10 +445,12 @@ class DataBrowser extends React.Component { showInfo, path, freeSpace, - totalSpace + totalSpace, + loading, + progress } = this.state - return ( + return ([ <div className={[classes.boundary, classes.columnFlexContainer].join(' ')} > @@ -573,8 +581,10 @@ class DataBrowser extends React.Component { /> )} </div> - </div> - ) + </div>, + <LoadingDialog key='data-browser-loaging-dialog' variant='determinate' + value={progress} loading={loading} /> + ]) } } diff --git a/ui-admin/src/js/components/database/data-browser/DataBrowserController.js b/ui-admin/src/js/components/database/data-browser/DataBrowserController.js index ba17aa0134c..a96e866681c 100644 --- a/ui-admin/src/js/components/database/data-browser/DataBrowserController.js +++ b/ui-admin/src/js/components/database/data-browser/DataBrowserController.js @@ -231,30 +231,52 @@ export default class DataBrowserController extends ComponentController { return dataArray } - async downloadFile(file) { - // Check if StreamSaver's service worker is correctly set up - if (!navigator.serviceWorker.controller) { - const registration = await navigator.serviceWorker.register('/sw.js'); // Path to your service worker file - await navigator.serviceWorker.ready; // Wait for the service worker to be ready - } + async downloadAndSaveFile(file, onProgressUpdate) { + const fileHandle = await window.showSaveFilePicker() + const writable = await fileHandle.createWritable() - const streamSaver = window.streamSaver - streamSaver.mitm = 'https://cdn.jsdelivr.net/npm/streamsaver@2/mitm.html' - const fileStream = streamSaver.createWriteStream(file.name); - const writer = fileStream.getWriter(); + try { + let offset = 0; - let offset = 0; + const size = file.size; + while (offset < size) { + const chunk = await this._download(file, offset) + await writable.write(chunk) + offset += CHUNK_SIZE - while (offset < file.size) { - const chunk = await this._download(file, offset) - const buffer = await chunk.arrayBuffer() - await writer.write(new Uint8Array(buffer)) - offset += CHUNK_SIZE + const progress = Math.round((offset / size) * 100) + onProgressUpdate(Math.min(progress, 100)) + } + } finally { + onProgressUpdate(100) + await writable.close() } - - writer.close() } + // async downloadFile(file) { + // // Check if StreamSaver's service worker is correctly set up + // if (!navigator.serviceWorker.controller) { + // const registration = await navigator.serviceWorker.register('/sw.js'); // Path to your service worker file + // await navigator.serviceWorker.ready; // Wait for the service worker to be ready + // } + // + // const streamSaver = window.streamSaver + // streamSaver.mitm = 'https://cdn.jsdelivr.net/npm/streamsaver@2/mitm.html' + // const fileStream = streamSaver.createWriteStream(file.name); + // const writer = fileStream.getWriter(); + // + // let offset = 0; + // + // while (offset < file.size) { + // const chunk = await this._download(file, offset) + // const buffer = await chunk.arrayBuffer() + // await writer.write(new Uint8Array(buffer)) + // offset += CHUNK_SIZE + // } + // + // writer.close() + // } + async _download(file, offset) { const limit = Math.min(CHUNK_SIZE, file.size - offset) return await openbis.read(this.owner, file.path, offset, limit) -- GitLab