From adc7083d2eed918920e8c0fb083dad577d5f0314 Mon Sep 17 00:00:00 2001 From: Amphiluke Date: Sun, 26 Sep 2021 21:36:41 +0700 Subject: [PATCH] Complete saving after Cordova Activity relaunch --- README.md | 1 + package.json | 3 ++- plugin.xml | 6 +++-- www/BlobKeeper.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++ www/SaveDialog.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++ www/save-dialog.js | 26 -------------------- 6 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 www/BlobKeeper.js create mode 100644 www/SaveDialog.js delete mode 100644 www/save-dialog.js diff --git a/README.md b/README.md index db65022..a18e44b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ try { let response = await fetch(`https://avatars.dicebear.com/api/avataaars/${Math.random()}.svg`); let blob = await response.blob(); await cordova.plugins.saveDialog.saveFile(blob, "random-avatar.svg"); + console.info("The file has been successfully saved"); } catch (e) { console.error(e); } diff --git a/package.json b/package.json index 891ccc9..c7c4d50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-save-dialog", - "version": "0.1.0", + "version": "0.1.1", "description": "Cordova plugin for Android to display the native Save dialog and store a file in the selected location", "main": "index.js", "repository": { @@ -10,6 +10,7 @@ "keywords": [ "ecosystem:cordova", "cordova-android", + "file", "save", "dialog" ], diff --git a/plugin.xml b/plugin.xml index 340c889..0516cfd 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,12 +1,14 @@ - + Save Dialog Cordova plugin for Android to display the native Save dialog and store a file in the selected location MIT cordova,save,dialog - + + + diff --git a/www/BlobKeeper.js b/www/BlobKeeper.js new file mode 100644 index 0000000..63b304a --- /dev/null +++ b/www/BlobKeeper.js @@ -0,0 +1,61 @@ +let moduleMapper = require("cordova/modulemapper"); +let indexedDB = moduleMapper.getOriginalSymbol(window, "indexedDB") || window.indexedDB; + +function openDB() { + if (!indexedDB) { + return Promise.reject("indexedDB is not supported"); + } + if (openDB._db) { + return Promise.resolve(openDB._db); + } + return new Promise((resolve, reject) => { + let openRequest = indexedDB.open("CordovaPluginSaveDialog"); + openRequest.onupgradeneeded = () => { + openRequest.result.createObjectStore("blobs"); + }; + openRequest.onsuccess = () => { + openDB._db = openRequest.result; + resolve(openDB._db); + }; + openRequest.onerror = () => { + openDB._db = null; + reject(openRequest.error); + }; + }); +} + +function writeBlob(blob) { + return openDB().then(db => new Promise((resolve, reject) => { + let objectStore = db.transaction(["blobs"], "readwrite").objectStore("blobs"); + let putRequest = blob ? objectStore.put(blob, 0) : objectStore.clear(); + putRequest.onsuccess = () => { + resolve(); + }; + putRequest.onerror = () => { + reject(putRequest.error); + }; + })); +} + +function getBlob() { + return openDB().then(db => new Promise((resolve, reject) => { + let objectStore = db.transaction(["blobs"], "readonly").objectStore("blobs"); + let getRequest = objectStore.get(0); + getRequest.onsuccess = () => { + resolve(getRequest.result); + }; + getRequest.onerror = () => { + reject(getRequest.error); + }; + })); +} + +function warn(reason) { + console.warn("[SaveDialog]", reason); +} + +module.exports = { + keep: blob => writeBlob(blob).catch(warn), + get: () => getBlob().catch(warn), + clear: () => writeBlob(null).catch(warn) +}; diff --git a/www/SaveDialog.js b/www/SaveDialog.js new file mode 100644 index 0000000..05b2bde --- /dev/null +++ b/www/SaveDialog.js @@ -0,0 +1,61 @@ +let exec = require("cordova/exec"); +let {keep: keepBlob, get: getBlob, clear: clearBlob} = require("./BlobKeeper"); +let moduleMapper = require("cordova/modulemapper"); +let FileReader = moduleMapper.getOriginalSymbol(window, "FileReader") || window.FileReader; + +let locateFile = (type, name) => new Promise((resolve, reject) => { + exec(resolve, reject, "SaveDialog", "locateFile", [type || "application/octet-stream", name]); +}); + +let saveFile = (uri, blob) => new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = () => { + exec(resolve, reject, "SaveDialog", "saveFile", [uri, reader.result]); + }; + reader.onerror = () => { + reject(reader.error); + }; + reader.onabort = () => { + reject("Blob reading has been aborted"); + }; + reader.readAsArrayBuffer(blob); +}); + +module.exports = { + saveFile(blob, name = "") { + if (window.cordova.platformId !== "android") { + return Promise.reject("Unsupported platform"); + } + return keepBlob(blob) // see the “resume” event handler below + .then(() => locateFile(blob.type, name)) + .then(uri => saveFile(uri, blob)) + .then(() => { + clearBlob(); + }) + .catch(reason => { + clearBlob(); + return Promise.reject(reason); + }); + } +}; + +// If Android OS has destroyed the Cordova Activity in background, try to complete the Save operation +// using the URI passed in the payload of the “resume” event and the blob stored by the BlobKeeper. +// https://cordova.apache.org/docs/en/10.x/guide/platforms/android/plugin.html#launching-other-activities +document.addEventListener("resume", ({pendingResult = {}}) => { + if (pendingResult.pluginServiceName !== "SaveDialog") { + return; + } + if (pendingResult.pluginStatus !== "OK" || !pendingResult.result) { + clearBlob(); + return; + } + getBlob().then(blob => { + if (blob instanceof Blob) { + saveFile(pendingResult.result, blob).catch(reason => { + console.warn("[SaveDialog]", reason); + }); + } + clearBlob(); + }); +}, false); diff --git a/www/save-dialog.js b/www/save-dialog.js deleted file mode 100644 index e57c2e5..0000000 --- a/www/save-dialog.js +++ /dev/null @@ -1,26 +0,0 @@ -let exec = require("cordova/exec"); -let moduleMapper = require("cordova/modulemapper"); -let FileReader = moduleMapper.getOriginalSymbol(window, "FileReader") || window.FileReader; - -module.exports = { - saveFile(blob, name = "") { - if (window.cordova.platformId !== "android") { - return Promise.reject("Unsupported platform"); - } - return new Promise((resolve, reject) => { - exec(resolve, reject, "SaveDialog", "locateFile", [blob.type || "application/octet-stream", name]); - }).then(uri => new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.onload = () => { - exec(resolve, reject, "SaveDialog", "saveFile", [uri, reader.result]); - }; - reader.onerror = () => { - reject(reader.error); - }; - reader.onabort = () => { - reject("Blob reading has been aborted"); - }; - reader.readAsArrayBuffer(blob); - })); - } -};