Skip to content

Commit

Permalink
Complete saving after Cordova Activity relaunch
Browse files Browse the repository at this point in the history
  • Loading branch information
Amphiluke committed Sep 26, 2021
1 parent 904af3b commit adc7083
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 29 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -10,6 +10,7 @@
"keywords": [
"ecosystem:cordova",
"cordova-android",
"file",
"save",
"dialog"
],
Expand Down
6 changes: 4 additions & 2 deletions plugin.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-save-dialog" version="0.1.0">
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-save-dialog" version="0.1.1">
<name>Save Dialog</name>
<description>Cordova plugin for Android to display the native Save dialog and store a file in the selected location</description>
<license>MIT</license>
<keywords>cordova,save,dialog</keywords>
<js-module src="www/save-dialog.js" name="SaveDialog">
<js-module src="www/SaveDialog.js" name="SaveDialog">
<clobbers target="cordova.plugins.saveDialog" />
</js-module>
<js-module src="www/BlobKeeper.js" name="BlobKeeper">
</js-module>

<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
Expand Down
61 changes: 61 additions & 0 deletions www/BlobKeeper.js
Original file line number Diff line number Diff line change
@@ -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)
};
61 changes: 61 additions & 0 deletions www/SaveDialog.js
Original file line number Diff line number Diff line change
@@ -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);
26 changes: 0 additions & 26 deletions www/save-dialog.js

This file was deleted.

0 comments on commit adc7083

Please sign in to comment.