From 6c01e6eaaf7a73b3519058d2fb0606c5d690e53e Mon Sep 17 00:00:00 2001
From: Jakub Kuczys <me@jacken.men>
Date: Tue, 21 Mar 2023 14:39:54 +0100
Subject: [PATCH] Accept `.blob` files when importing Actual export (#785)

I'm not sure if this is something you want but it was a simple change so
I figured I might as well contribute it. This PR allows the user to
upload `.blob` files that they may have gotten from server's
`user-files/` folder. This can be useful if the user didn't export the
file but has server backups.

---------

Co-authored-by: Jed Fox <git@jedfox.com>
---
 packages/loot-core/src/server/cloud-storage.js   |  9 +++++++--
 packages/loot-core/src/server/main.js            | 16 ++++++++++++----
 packages/loot-core/src/shared/errors.js          |  1 +
 .../src/components/manager/ImportActual.js       |  8 +++++++-
 upcoming-release-notes/785.md                    |  6 ++++++
 5 files changed, 33 insertions(+), 7 deletions(-)
 create mode 100644 upcoming-release-notes/785.md

diff --git a/packages/loot-core/src/server/cloud-storage.js b/packages/loot-core/src/server/cloud-storage.js
index fd5a9a5cd..d37d97530 100644
--- a/packages/loot-core/src/server/cloud-storage.js
+++ b/packages/loot-core/src/server/cloud-storage.js
@@ -161,8 +161,13 @@ export async function exportBuffer() {
 }
 
 export async function importBuffer(fileData, buffer) {
-  let zipped = new AdmZip(buffer);
-  let entries = zipped.getEntries();
+  let zipped, entries;
+  try {
+    zipped = new AdmZip(buffer);
+    entries = zipped.getEntries();
+  } catch (err) {
+    throw FileDownloadError('not-zip-file');
+  }
   let dbEntry = entries.find(e => e.entryName.includes('db.sqlite'));
   let metaEntry = entries.find(e => e.entryName.includes('metadata.json'));
 
diff --git a/packages/loot-core/src/server/main.js b/packages/loot-core/src/server/main.js
index d81348c10..eed06087f 100644
--- a/packages/loot-core/src/server/main.js
+++ b/packages/loot-core/src/server/main.js
@@ -2108,10 +2108,18 @@ handlers['import-budget'] = async function ({ filepath, type }) {
         // duplicate some of the workflow
         await handlers['close-budget']();
 
-        let { id } = await cloudStorage.importBuffer(
-          { cloudFileId: null, groupId: null },
-          buffer,
-        );
+        let id;
+        try {
+          ({ id } = await cloudStorage.importBuffer(
+            { cloudFileId: null, groupId: null },
+            buffer,
+          ));
+        } catch (e) {
+          if (e.type === 'FileDownloadError') {
+            return { error: e.reason };
+          }
+          throw e;
+        }
 
         // We never want to load cached data from imported files, so
         // delete the cache
diff --git a/packages/loot-core/src/shared/errors.js b/packages/loot-core/src/shared/errors.js
index 3b20c0519..fc0a8cd19 100644
--- a/packages/loot-core/src/shared/errors.js
+++ b/packages/loot-core/src/shared/errors.js
@@ -28,6 +28,7 @@ export function getDownloadError({ reason, meta, fileName }) {
     case 'network':
     case 'download-failure':
       return 'Downloading the file failed. Check your network connection.';
+    case 'not-zip-file':
     case 'invalid-zip-file':
     case 'invalid-meta-file':
       return 'Downloaded file is invalid, sorry! Contact help@actualbudget.com for support.';
diff --git a/packages/loot-design/src/components/manager/ImportActual.js b/packages/loot-design/src/components/manager/ImportActual.js
index f541b061f..50cb6da77 100644
--- a/packages/loot-design/src/components/manager/ImportActual.js
+++ b/packages/loot-design/src/components/manager/ImportActual.js
@@ -12,6 +12,12 @@ function getErrorMessage(error) {
       return 'Unable to parse file. Please select a JSON file exported from nYNAB.';
     case 'not-ynab5':
       return 'This file is not valid. Please select a JSON file exported from nYNAB.';
+    case 'not-zip-file':
+      return 'This file is not valid. Please select an unencrypted archive of Actual data.';
+    case 'invalid-zip-file':
+      return 'This archive is not a valid Actual export file.';
+    case 'invalid-metadata-file':
+      return 'The metadata file in the given archive is corrupted.';
     default:
       return 'An unknown error occurred while importing. Sorry! We have been notified of this issue.';
   }
@@ -25,7 +31,7 @@ function Import({ modalProps, availableImports }) {
   async function onImport() {
     const res = await window.Actual.openFileDialog({
       properties: ['openFile'],
-      filters: [{ name: 'actual', extensions: ['zip'] }],
+      filters: [{ name: 'actual', extensions: ['zip', 'blob'] }],
     });
     if (res) {
       setImporting(true);
diff --git a/upcoming-release-notes/785.md b/upcoming-release-notes/785.md
new file mode 100644
index 000000000..534b88ee0
--- /dev/null
+++ b/upcoming-release-notes/785.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [Jackenmen]
+---
+
+Allow importing `.blob` files from actual-server
-- 
GitLab