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