From ae10f2287b9101f9d48395a0a60905440258d560 Mon Sep 17 00:00:00 2001
From: Eugen Ciur <eugen@papermerge.com>
Date: Sat, 29 Jan 2022 06:07:11 +0100
Subject: [PATCH] add download archives functionality

---
 app/components/commander/action_buttons.hbs |   2 +
 app/components/commander/action_buttons.js  |   7 ++
 app/components/commander/index.hbs          |   1 +
 app/components/commander/index.js           |   5 +
 app/services/requests.js                    | 119 +++++++++++---------
 app/utils/index.js                          |  45 +++++++-
 6 files changed, 126 insertions(+), 53 deletions(-)

diff --git a/app/components/commander/action_buttons.hbs b/app/components/commander/action_buttons.hbs
index 99b100f..e4bf903 100644
--- a/app/components/commander/action_buttons.hbs
+++ b/app/components/commander/action_buttons.hbs
@@ -9,6 +9,7 @@
     </button>
     <button
       class="btn btn-success"
+      {{on "click" this.onDownloadNodes}}
       type="button">
         <i class="fa fa-download"></i>
       Download
@@ -23,6 +24,7 @@
   {{else if this.multiple_nodes_selected}}
     <button
       class="btn btn-success"
+      {{on "click" this.onDownloadNodes}}
       type="button">
         <i class="fa fa-download"></i>
       Download
diff --git a/app/components/commander/action_buttons.js b/app/components/commander/action_buttons.js
index 2b773e1..d0ac8f0 100644
--- a/app/components/commander/action_buttons.js
+++ b/app/components/commander/action_buttons.js
@@ -25,4 +25,11 @@ export default class ActionButtonsComponent extends Component {
       this.args.selectedNodes[0]
     );
   }
+
+  @action
+  onDownloadNodes() {
+    this.args.onDownloadNodes(
+      this.args.selectedNodes
+    );
+  }
 }
diff --git a/app/components/commander/index.hbs b/app/components/commander/index.hbs
index 7f6366a..ec5b188 100644
--- a/app/components/commander/index.hbs
+++ b/app/components/commander/index.hbs
@@ -8,6 +8,7 @@
       @openRenameModal={{this.openRenameModal}}
       @openConfirmDeletionModal={{this.openConfirmDeletionModal}}
       @onCreateDocumentModel={{this.onCreateDocumentModel}}
+      @onDownloadNodes={{this.onDownloadNodes}}
       @selectedNodes={{this.selected_nodes}}
       @lang={{this.lang}}
       @node={{@node}} />
diff --git a/app/components/commander/index.js b/app/components/commander/index.js
index 0c5cb66..bcf6270 100644
--- a/app/components/commander/index.js
+++ b/app/components/commander/index.js
@@ -178,6 +178,11 @@ export default class CommanderComponent extends Component {
     this.__new_record = new_record; // workaround of ember bug
   }
 
+  @action
+  onDownloadNodes(selected_nodes) {
+    this.requests.downloadNodes(selected_nodes);
+  }
+
   @action
   onViewModeChange(new_view_mode) {
     this.view_mode = new_view_mode;
diff --git a/app/services/requests.js b/app/services/requests.js
index c3825ce..a370cbef 100644
--- a/app/services/requests.js
+++ b/app/services/requests.js
@@ -2,7 +2,11 @@ import Service from '@ember/service';
 // eslint-disable-next-line ember/no-computed-properties-in-native-classes
 import { computed } from '@ember/object';
 import { service } from '@ember/service';
-import { base_url } from 'papermerge/utils';
+import {
+  base_url,
+  insert_blob,
+  extract_file_name
+} from 'papermerge/utils';
 
 
 export default class Requests extends Service {
@@ -28,44 +32,47 @@ export default class Requests extends Service {
     });
   }
 
+  /**
+  *  `document_version` contains following attributes:
+  *    id
+  *    number
+  *    file_name
+  *    lang
+  *    pages
+  *    size
+  *    page_count
+  *    short_description
+  *
+  *  attributes which correspond to server side (or client side) DocumentVersion model
+  */
   async downloadDocumentVersion(document_version) {
-    /*
-      `document_version` contains following attributes:
-        id
-        number
-        file_name
-        lang
-        pages
-        size
-        page_count
-        short_description
-
-      attributes which correspond to server side (or client side) DocumentVersion model
-    */
-    let url, headers_copy = {};
+    let response, blob;
 
-    url = `${base_url()}/document-versions/${document_version.id}/download/`;
-    Object.assign(headers_copy, this.headers);
-     //headers_copy['Access-Control-Allow-Origin'] = ENV.APP.HOST;
+    response = await this._get(`/document-versions/${document_version.id}/download/`);
 
-    return fetch(url, {
-      method: 'GET',
-      headers: headers_copy
-    }).then(
-      response => response.blob()
-    ).then((blob) => {
-      let url = window.URL.createObjectURL(blob);
-      let a = document.createElement('a');
-
-      a.href = url;
-      a.download = document_version.file_name;
-      // we need to append the element to the dom -> otherwise it will not
-      // work in firefox
-      document.body.appendChild(a);
-      a.click();
-      //afterwards we remove the element again
-      a.remove();
-    });
+    blob = await response.blob();
+    insert_blob(
+      document_version.file_name,
+      blob
+    );
+  }
+
+  async downloadNodes(selected_nodes) {
+    let params_arr,
+      params_str,
+      response,
+      blob,
+      file_name;
+
+    params_arr = selected_nodes.map(node => `node_ids=${node.id}`);
+    params_str = params_arr.join('&');
+
+    response = await this._get('/nodes/download/', params_str);
+
+    file_name = extract_file_name(response, 'fallback.zip');
+    blob = await response.blob();
+
+    insert_blob(file_name, blob);
   }
 
   async nodesMove(data) {
@@ -84,29 +91,20 @@ export default class Requests extends Service {
   }
 
   async search(query) {
-    let url;
-
-    url = `${base_url()}/search/?q=${query}`;
-
-    return fetch(url, {
-      method: 'GET',
-      headers: this.headers
-    });
+    return this._get('/search/', `q=${query}`);
   }
 
   async preferences({section_name}={}) {
-    let url;
+    let params = {};
 
     if (section_name) {
-      url = `${base_url()}/preferences/?section=${section_name}`
-    } else {
-      url = `${base_url()}/preferences/`;
+      params = {'section': section_name};
     }
 
-    return fetch(url, {
-      method: 'GET',
-      headers: this.headers
-    });
+    return this._get(
+      '/preferences/',
+      new URLSearchParams(params).toString()
+    );
   }
 
   async preferencesUpdate(data) {
@@ -143,6 +141,23 @@ export default class Requests extends Service {
     });
   }
 
+  async _get(url, params_str) {
+    let url_with_base,
+      headers_copy = {};
+
+    if (params_str) {
+     url_with_base = `${base_url()}${url}?${params_str}`;
+    } else {
+      url_with_base = `${base_url()}${url}`;
+    }
+    Object.assign(headers_copy, this.headers);
+
+    return fetch(url_with_base, {
+      method: 'GET',
+      headers: headers_copy,
+    });
+  }
+
   @computed('session.{data.authenticated.token,isAuthenticated}')
   get headers() {
     let _headers = {},
diff --git a/app/utils/index.js b/app/utils/index.js
index c86c1f5..6a23321 100644
--- a/app/utils/index.js
+++ b/app/utils/index.js
@@ -113,9 +113,52 @@ function ws_base_url() {
   return `${ENV.APP.WS_HOST}/${ENV.APP.WS_NAMESPACE}`;
 }
 
+/**
+ * Extracts file name from a response with accessible Content-Disposition header
+ */
+function extract_file_name(response, fallback) {
+  let file_name = fallback,
+    content_disp,
+    match;
+
+  content_disp = response.headers.get('content-disposition');
+
+  if (content_disp) {
+    match = content_disp.match('filename=(.*)$');
+    if (match) {
+      file_name = match[1];
+    }
+  } else {
+    console.warn('Could not read content disposition header');
+    console.warn('Returning default file name');
+  }
+
+  return file_name;
+}
+
+/**
+ * Insert a blob data into DOM and prompt use to download it
+ */
+function insert_blob(file_name, blob) {
+  let url, a;
+
+  url = window.URL.createObjectURL(blob);
+  a = document.createElement('a');
+  a.href = url;
+  a.download = file_name;
+  // we need to append the element to the dom -> otherwise it will not
+  // work in firefox
+  document.body.appendChild(a);
+  a.click();
+  //afterwards we remove the element again
+  a.remove();
+}
+
 export {
   group_perms_by_model,
   are_sets_equal,
   base_url,
-  ws_base_url
+  ws_base_url,
+  insert_blob,
+  extract_file_name
 };
-- 
GitLab