From 603e840893258c9602eeaf880f08430cf7e4a395 Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Fri, 8 Jun 2018 18:29:16 +0200 Subject: [PATCH 01/17] browser platform support + definition files --- package.json | 9 ++- plugin.xml | 11 ++++ src/browser/README.md | 9 +++ src/browser/ZipProxy.js | 116 ++++++++++++++++++++++++++++++++++ src/browser/ZipProxy.ts | 136 ++++++++++++++++++++++++++++++++++++++++ types/index.d.ts | 24 +++++++ 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 src/browser/README.md create mode 100644 src/browser/ZipProxy.js create mode 100644 src/browser/ZipProxy.ts create mode 100644 types/index.d.ts diff --git a/package.json b/package.json index 94919f65..797ebe13 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "id": "cordova-plugin-unzip", "platforms": [ "android", - "ios" + "ios", + "browser" ] }, "repository": { @@ -14,9 +15,13 @@ "url": "https://github.com/MobileChromeApps/cordova-plugin-zip.git" }, "keywords": [ + "cordova", + "zip", + "unzip", "ecosystem:cordova", "cordova-android", - "cordova-ios" + "cordova-ios", + "cordova-browser" ], "engines": [ { diff --git a/plugin.xml b/plugin.xml index 746ea4fe..5352bb55 100644 --- a/plugin.xml +++ b/plugin.xml @@ -15,6 +15,17 @@ + + + + + + + + + + + diff --git a/src/browser/README.md b/src/browser/README.md new file mode 100644 index 00000000..67211837 --- /dev/null +++ b/src/browser/README.md @@ -0,0 +1,9 @@ +# Cordova ZIP plugin - browser endpoint + + +## Build +``` +tsc ZipProxy.ts --target ES6 +``` + +Some harmless errors show up about missing Cordova File Plugin definitions. Be careful, other errors might not be harmless :) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js new file mode 100644 index 00000000..be95173a --- /dev/null +++ b/src/browser/ZipProxy.js @@ -0,0 +1,116 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +function getFileSystem() { + return __awaiter(this, void 0, void 0, function* () { + const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; + const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; + console.debug(`zip plugin - requestFileSystem=${requestFileSystem} - storageInfo=${storageInfo}`); + // request storage quota + const requestedBytes = (this.nbMegaBytes * 1000000 /* ? x 1Mo */); + let grantedBytes = 0; + if (storageInfo != null) { + grantedBytes = yield new Promise((resolve, reject) => { + storageInfo.requestQuota(requestedBytes, resolve, reject); + }); + } + console.debug('granted bytes: ' + grantedBytes); + // request file system + if (!requestFileSystem) { + throw new Error('cannot access filesystem API'); + } + const fileSystem = yield new Promise((resolve, reject) => { + requestFileSystem(window.PERSISTENT, grantedBytes, resolve, reject); + }); + console.debug('FileSystem ready: ' + fileSystem.name); + return fileSystem; + }); +} +// file:///synchros/2018-06-08/12H44-46.472-DEMO%202014-4/up.zip +function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) { + return __awaiter(this, void 0, void 0, function* () { + try { + if (!zip) { + throw new Error('zip.js not available, please import it: https://gildas-lormeau.github.io/zip.js'); + } + console.info(`unzipping ${zipFilePath} to ${outputDirectoryPath}`); + const fileSystem = yield getFileSystem(); + console.debug(`retrieving output directory: ${outputDirectoryPath}`); + const outputDirectoryEntry = yield new Promise((resolve, reject) => { + fileSystem.root.getDirectory(outputDirectoryPath, { create: true, exclusive: false }, resolve, reject); + }); + console.debug(`output directory entry: ${outputDirectoryEntry}`); + const zipEntry = yield new Promise((resolve, reject) => { + fileSystem.root.getFile(zipFilePath, {}, resolve, reject); + }); + const zipBlob = yield new Promise((resolve, reject) => { + zipEntry.file(resolve, reject); + }); + console.info(`open reader on zip: ${zipFilePath}`); + zip.createReader(new zip.BlobReader(zipBlob), (zipReader) => { + console.debug(`reader opened on zip: ${zipFilePath}`); + zipReader.getEntries((zipEntries) => __awaiter(this, void 0, void 0, function* () { + console.debug(`entries read: ${zipFilePath}`); + successCallback({ + loaded: 0, + total: zipEntries.length + }); + try { + let i = 0; + for (const entry of zipEntries) { + console.debug(`extracting ${entry.filename} to ${outputDirectoryPath}`); + let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; + if (isDirectory) { + console.debug('add directory: ' + entry.filename); + yield new Promise((resolve, reject) => { + outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); + }); + } + else { + console.debug('adding file (get file): ' + entry.filename); + const targetFileEntry = yield new Promise((resolve, reject) => { + outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); + }); + console.debug('adding file (write file): ' + entry.filename); + yield new Promise((resolve, reject) => { + entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { + console.debug(`${entry.filename}: ${progress} / ${total}`); + }); + }); + console.debug('added file: ' + entry.filename); + } + successCallback({ + loaded: ++i, + total: zipEntries.length + }); + } + console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + successCallback({ + total: zipEntries.length + }); + } + catch (e) { + console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + errorCallback(e); + } + })); + }, errorCallback); + } + catch (e) { + console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + errorCallback(e); + } + }); +} +module.exports = { + unzip: function (successCallback, errorCallback, args) { + const [zipFilePath, outputDirectoryPath] = args; + unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); + } +}; +require("cordova/exec/proxy").add("Zip", module.exports); diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts new file mode 100644 index 00000000..35b3d8f6 --- /dev/null +++ b/src/browser/ZipProxy.ts @@ -0,0 +1,136 @@ + +async function getFileSystem(): Promise { + const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; + const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; + + console.debug(`zip plugin - requestFileSystem=${requestFileSystem} - storageInfo=${storageInfo}`); + + // request storage quota + const requestedBytes: number = (this.nbMegaBytes * 1000000 /* ? x 1Mo */); + let grantedBytes: number = 0; + if (storageInfo != null) { + grantedBytes = await new Promise((resolve, reject) => { + storageInfo.requestQuota(requestedBytes, resolve, reject); + }); + } + console.debug('granted bytes: ' + grantedBytes); + + // request file system + if (!requestFileSystem) { + throw new Error('cannot access filesystem API'); + } + const fileSystem: FileSystem = await new Promise((resolve, reject) => { + requestFileSystem(window.PERSISTENT, grantedBytes, resolve, reject); + }); + console.debug('FileSystem ready: ' + fileSystem.name); + + return fileSystem; +} + +// file:///synchros/2018-06-08/12H44-46.472-DEMO%202014-4/up.zip +async function unzip( + zipFilePath: string, + outputDirectoryPath: string, + successCallback: (event: { + loaded?: number, + total: number + }) => void, + errorCallback) { + + try { + + if (!zip) { + throw new Error('zip.js not available, please import it: https://gildas-lormeau.github.io/zip.js'); + } + + console.info(`unzipping ${zipFilePath} to ${outputDirectoryPath}`); + + const fileSystem: FileSystem = await getFileSystem(); + + console.debug(`retrieving output directory: ${outputDirectoryPath}`); + const outputDirectoryEntry: DirectoryEntry = await new Promise((resolve, reject) => { + fileSystem.root.getDirectory(outputDirectoryPath, { create: true, exclusive: false }, resolve, reject); + }); + + console.debug(`output directory entry: ${outputDirectoryEntry}`); + + const zipEntry: FileEntry = await new Promise((resolve, reject) => { + fileSystem.root.getFile(zipFilePath, {}, resolve, reject); + }); + + const zipBlob: Blob = await new Promise((resolve, reject) => { + zipEntry.file(resolve, reject); + }); + + console.info(`open reader on zip: ${zipFilePath}`); + zip.createReader(new zip.BlobReader(zipBlob), (zipReader) => { + + console.debug(`reader opened on zip: ${zipFilePath}`); + zipReader.getEntries(async (zipEntries) => { + + console.debug(`entries read: ${zipFilePath}`); + + successCallback({ + loaded: 0, + total: zipEntries.length + }); + + try { + + let i = 0; + for (const entry of zipEntries) { + console.debug(`extracting ${entry.filename} to ${outputDirectoryPath}`); + let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; + + if (isDirectory) { + console.debug('add directory: ' + entry.filename); + await new Promise((resolve, reject) => { + outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); + }); + } else { + console.debug('adding file (get file): ' + entry.filename); + const targetFileEntry = await new Promise((resolve, reject) => { + outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); + }); + console.debug('adding file (write file): ' + entry.filename); + await new Promise((resolve, reject) => { + entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { + console.debug(`${entry.filename}: ${progress} / ${total}`); + }); + }); + console.debug('added file: ' + entry.filename); + } + + successCallback({ + loaded: ++i, + total: zipEntries.length + }); + } + + console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + successCallback({ + total: zipEntries.length + }); + } catch (e) { + console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + errorCallback(e); + } + }); + }, errorCallback); + + } catch (e) { + console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + errorCallback(e); + } +} + +declare var module; +declare var require; + +module.exports = { + unzip: function (successCallback, errorCallback, args) { + const [zipFilePath, outputDirectoryPath] = args; + unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); + } +}; +require("cordova/exec/proxy").add("Zip", module.exports); diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..d72333a0 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,24 @@ +// Type definitions for Apache Cordova Zip plugin +// Project: https://github.com/MobileChromeApps/cordova-plugin-zip +// Definitions by: lgrignon + +declare const enum CordovaZipPluginUnzipResult { + Success = 0, + Failure = -1 +} + +interface CordovaZipPluginUnzipProgressEvent { + /** + * Total zip size in Bytes + */ + total: number; + /** + * Loaded zip size in Bytes + */ + loaded: number; +} + +interface CordovaZipPlugin { + unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void); + unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void, progressCallback: (event: CordovaZipPluginUnzipProgressEvent) => void); +} From 9d8bce3d80e210f3e7ea0cb84f969c3a4d4850db Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Fri, 8 Jun 2018 22:55:38 +0200 Subject: [PATCH 02/17] close reader --- src/browser/ZipProxy.js | 9 ++++++--- src/browser/ZipProxy.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index be95173a..d92f05f5 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -89,13 +89,16 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) total: zipEntries.length }); } - console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); - successCallback({ - total: zipEntries.length + zipReader.close(() => { + console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + successCallback({ + total: zipEntries.length + }); }); } catch (e) { console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + zipReader.close(); errorCallback(e); } })); diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index 35b3d8f6..586bd874 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -107,12 +107,16 @@ async function unzip( }); } - console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); - successCallback({ - total: zipEntries.length + zipReader.close(() => { + console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + successCallback({ + total: zipEntries.length + }); }); + } catch (e) { console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + zipReader.close(); errorCallback(e); } }); From 7d653453a58f1e7ca19d9ff5d9a647045a170f33 Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Mon, 11 Jun 2018 16:25:58 +0200 Subject: [PATCH 03/17] types definition fix --- types/index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/index.d.ts b/types/index.d.ts index d72333a0..842d31e7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -22,3 +22,5 @@ interface CordovaZipPlugin { unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void); unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void, progressCallback: (event: CordovaZipPluginUnzipProgressEvent) => void); } + +declare var zip: CordovaZipPlugin; \ No newline at end of file From 3a4ca99a83f091e0721c36f29f5555bcc61abd3f Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Tue, 12 Jun 2018 12:16:35 +0200 Subject: [PATCH 04/17] try to resolve input zip from temp fs --- src/browser/ZipProxy.js | 106 ++++++++++++++++++++++++++---------- src/browser/ZipProxy.ts | 117 +++++++++++++++++++++++++++++----------- 2 files changed, 163 insertions(+), 60 deletions(-) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index d92f05f5..52af2fc3 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -6,13 +6,54 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -function getFileSystem() { +var FileErrors; +(function (FileErrors) { + FileErrors[FileErrors["TypeMismatchError"] = 11] = "TypeMismatchError"; + FileErrors[FileErrors["NotFoundError"] = 1] = "NotFoundError"; +})(FileErrors || (FileErrors = {})); +function isFileError(error, requestedError) { + if (error.name && error.name == FileErrors[requestedError]) { + return true; + } + if (error.code && error.code == requestedError) { + return true; + } + return false; +} +function getFileEntry(path, parentDirectory) { + return new Promise((resolve, reject) => { + parentDirectory.getFile(path, {}, resolve, reject); + }); +} +function exists(path, parentDirectory) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield getFileEntry(path, parentDirectory); + return true; + } + catch (error) { + if (isFileError(error, FileErrors.TypeMismatchError)) { + return true; + } + if (isFileError(error, FileErrors.NotFoundError)) { + return false; + } + throw error; + } + }); +} +var FileSystemType; +(function (FileSystemType) { + FileSystemType[FileSystemType["TEMPORARY"] = window.TEMPORARY] = "TEMPORARY"; + FileSystemType[FileSystemType["PERSISTENT"] = window.PERSISTENT] = "PERSISTENT"; +})(FileSystemType || (FileSystemType = {})); +function getFileSystem(type = FileSystemType.PERSISTENT) { return __awaiter(this, void 0, void 0, function* () { const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; console.debug(`zip plugin - requestFileSystem=${requestFileSystem} - storageInfo=${storageInfo}`); // request storage quota - const requestedBytes = (this.nbMegaBytes * 1000000 /* ? x 1Mo */); + const requestedBytes = (1000 * 1000000 /* ? x 1Mo */); let grantedBytes = 0; if (storageInfo != null) { grantedBytes = yield new Promise((resolve, reject) => { @@ -25,13 +66,37 @@ function getFileSystem() { throw new Error('cannot access filesystem API'); } const fileSystem = yield new Promise((resolve, reject) => { - requestFileSystem(window.PERSISTENT, grantedBytes, resolve, reject); + requestFileSystem(type, grantedBytes, resolve, reject); }); console.debug('FileSystem ready: ' + fileSystem.name); return fileSystem; }); } -// file:///synchros/2018-06-08/12H44-46.472-DEMO%202014-4/up.zip +function unzipEntry(entry, outputDirectoryEntry) { + return __awaiter(this, void 0, void 0, function* () { + console.debug(`extracting ${entry.filename} to ${outputDirectoryEntry.fullPath}`); + let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; + if (isDirectory) { + console.debug('add directory: ' + entry.filename); + yield new Promise((resolve, reject) => { + outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); + }); + } + else { + console.debug('adding file (get file): ' + entry.filename); + const targetFileEntry = yield new Promise((resolve, reject) => { + outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); + }); + console.debug('adding file (write file): ' + entry.filename); + yield new Promise((resolve, reject) => { + entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { + console.debug(`${entry.filename}: ${progress} / ${total}`); + }); + }); + console.debug('added file: ' + entry.filename); + } + }); +} function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) { return __awaiter(this, void 0, void 0, function* () { try { @@ -45,9 +110,14 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) fileSystem.root.getDirectory(outputDirectoryPath, { create: true, exclusive: false }, resolve, reject); }); console.debug(`output directory entry: ${outputDirectoryEntry}`); - const zipEntry = yield new Promise((resolve, reject) => { - fileSystem.root.getFile(zipFilePath, {}, resolve, reject); - }); + let zipEntry; + if (yield exists(zipFilePath, fileSystem.root)) { + zipEntry = yield getFileEntry(zipFilePath, fileSystem.root); + } + else { + const tempFileSystem = yield getFileSystem(FileSystemType.TEMPORARY); + zipEntry = yield getFileEntry(zipFilePath, tempFileSystem.root); + } const zipBlob = yield new Promise((resolve, reject) => { zipEntry.file(resolve, reject); }); @@ -63,27 +133,7 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) try { let i = 0; for (const entry of zipEntries) { - console.debug(`extracting ${entry.filename} to ${outputDirectoryPath}`); - let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; - if (isDirectory) { - console.debug('add directory: ' + entry.filename); - yield new Promise((resolve, reject) => { - outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); - }); - } - else { - console.debug('adding file (get file): ' + entry.filename); - const targetFileEntry = yield new Promise((resolve, reject) => { - outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); - }); - console.debug('adding file (write file): ' + entry.filename); - yield new Promise((resolve, reject) => { - entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { - console.debug(`${entry.filename}: ${progress} / ${total}`); - }); - }); - console.debug('added file: ' + entry.filename); - } + yield unzipEntry(entry, outputDirectoryEntry); successCallback({ loaded: ++i, total: zipEntries.length diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index 586bd874..b89bde2c 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -1,12 +1,57 @@ -async function getFileSystem(): Promise { +enum FileErrors { + TypeMismatchError = 11, + NotFoundError = 1 +} + +function isFileError(error: any, requestedError: FileErrors): boolean { + if (error.name && error.name == FileErrors[requestedError]) { + return true; + } + + if (error.code && error.code == requestedError) { + return true; + } + + return false; +} + +function getFileEntry(path: string, parentDirectory: DirectoryEntry): Promise { + return new Promise((resolve, reject) => { + parentDirectory.getFile(path, {}, resolve, reject); + }); +} + +async function exists(path: string, parentDirectory: DirectoryEntry): Promise { + try { + await getFileEntry(path, parentDirectory); + return true; + } catch (error) { + if (isFileError(error, FileErrors.TypeMismatchError)) { + return true; + } + + if (isFileError(error, FileErrors.NotFoundError)) { + return false; + } + + throw error; + } +} + +enum FileSystemType { + TEMPORARY = window.TEMPORARY, + PERSISTENT = window.PERSISTENT +} + +async function getFileSystem(type: FileSystemType = FileSystemType.PERSISTENT): Promise { const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; console.debug(`zip plugin - requestFileSystem=${requestFileSystem} - storageInfo=${storageInfo}`); // request storage quota - const requestedBytes: number = (this.nbMegaBytes * 1000000 /* ? x 1Mo */); + const requestedBytes: number = (1000 * 1000000 /* ? x 1Mo */); let grantedBytes: number = 0; if (storageInfo != null) { grantedBytes = await new Promise((resolve, reject) => { @@ -20,21 +65,45 @@ async function getFileSystem(): Promise { throw new Error('cannot access filesystem API'); } const fileSystem: FileSystem = await new Promise((resolve, reject) => { - requestFileSystem(window.PERSISTENT, grantedBytes, resolve, reject); + requestFileSystem(type, grantedBytes, resolve, reject); }); console.debug('FileSystem ready: ' + fileSystem.name); return fileSystem; } -// file:///synchros/2018-06-08/12H44-46.472-DEMO%202014-4/up.zip +async function unzipEntry(entry: zip.Entry, outputDirectoryEntry: DirectoryEntry) { + console.debug(`extracting ${entry.filename} to ${outputDirectoryEntry.fullPath}`); + let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; + + if (isDirectory) { + console.debug('add directory: ' + entry.filename); + await new Promise((resolve, reject) => { + outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); + }); + } else { + console.debug('adding file (get file): ' + entry.filename); + const targetFileEntry = await new Promise((resolve, reject) => { + outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); + }); + console.debug('adding file (write file): ' + entry.filename); + await new Promise((resolve, reject) => { + entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { + console.debug(`${entry.filename}: ${progress} / ${total}`); + }); + }); + console.debug('added file: ' + entry.filename); + } +} + +interface SuccessCallback { + (event: { loaded?: number, total: number }): void; +} + async function unzip( zipFilePath: string, outputDirectoryPath: string, - successCallback: (event: { - loaded?: number, - total: number - }) => void, + successCallback: SuccessCallback, errorCallback) { try { @@ -54,9 +123,13 @@ async function unzip( console.debug(`output directory entry: ${outputDirectoryEntry}`); - const zipEntry: FileEntry = await new Promise((resolve, reject) => { - fileSystem.root.getFile(zipFilePath, {}, resolve, reject); - }); + let zipEntry: FileEntry; + if (await exists(zipFilePath, fileSystem.root)) { + zipEntry = await getFileEntry(zipFilePath, fileSystem.root); + } else { + const tempFileSystem: FileSystem = await getFileSystem(FileSystemType.TEMPORARY); + zipEntry = await getFileEntry(zipFilePath, tempFileSystem.root); + } const zipBlob: Blob = await new Promise((resolve, reject) => { zipEntry.file(resolve, reject); @@ -79,27 +152,7 @@ async function unzip( let i = 0; for (const entry of zipEntries) { - console.debug(`extracting ${entry.filename} to ${outputDirectoryPath}`); - let isDirectory = entry.filename.charAt(entry.filename.length - 1) == '/'; - - if (isDirectory) { - console.debug('add directory: ' + entry.filename); - await new Promise((resolve, reject) => { - outputDirectoryEntry.getDirectory(entry.filename, { create: true }, resolve, reject); - }); - } else { - console.debug('adding file (get file): ' + entry.filename); - const targetFileEntry = await new Promise((resolve, reject) => { - outputDirectoryEntry.getFile(entry.filename, { create: true, exclusive: false }, resolve, reject); - }); - console.debug('adding file (write file): ' + entry.filename); - await new Promise((resolve, reject) => { - entry.getData(new zip.FileWriter(targetFileEntry), resolve, (progress, total) => { - console.debug(`${entry.filename}: ${progress} / ${total}`); - }); - }); - console.debug('added file: ' + entry.filename); - } + await unzipEntry(entry, outputDirectoryEntry); successCallback({ loaded: ++i, From 42071282166c660f384ff06cff004c1ffdd78f2a Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Tue, 12 Jun 2018 15:06:14 +0200 Subject: [PATCH 05/17] sync configures zip.js to avoid loading workers --- src/browser/ZipProxy.js | 1 + src/browser/ZipProxy.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index 52af2fc3..ff8403ea 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -6,6 +6,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +zip.useWebWorkers = false; var FileErrors; (function (FileErrors) { FileErrors[FileErrors["TypeMismatchError"] = 11] = "TypeMismatchError"; diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index b89bde2c..f36136e3 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -1,4 +1,6 @@ +zip.useWebWorkers = false; + enum FileErrors { TypeMismatchError = 11, NotFoundError = 1 From 1fa3453440dc25a4aa0f8404b12addd1e0159f72 Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Tue, 12 Jun 2018 15:13:31 +0200 Subject: [PATCH 06/17] sync configures zip.js to avoid loading workers --- src/browser/ZipProxy.js | 2 +- src/browser/ZipProxy.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index ff8403ea..29692baa 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -6,7 +6,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -zip.useWebWorkers = false; var FileErrors; (function (FileErrors) { FileErrors[FileErrors["TypeMismatchError"] = 11] = "TypeMismatchError"; @@ -164,6 +163,7 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) module.exports = { unzip: function (successCallback, errorCallback, args) { const [zipFilePath, outputDirectoryPath] = args; + zip.useWebWorkers = false; unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); } }; diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index f36136e3..68d2e976 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -1,6 +1,4 @@ -zip.useWebWorkers = false; - enum FileErrors { TypeMismatchError = 11, NotFoundError = 1 @@ -189,6 +187,9 @@ declare var require; module.exports = { unzip: function (successCallback, errorCallback, args) { const [zipFilePath, outputDirectoryPath] = args; + + zip.useWebWorkers = false; + unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); } }; From 27519d93ff71c18b0e831ba6488380f2200fdeb1 Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Tue, 12 Jun 2018 17:25:32 +0200 Subject: [PATCH 07/17] error message from plugin returned to js --- types/index.d.ts | 11 +++++++++-- zip.js | 12 ++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 842d31e7..8dc74a21 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -19,8 +19,15 @@ interface CordovaZipPluginUnzipProgressEvent { } interface CordovaZipPlugin { - unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void); - unzip(sourceZip: string, destinationDir: string, onSuccess: (status: CordovaZipPluginUnzipResult) => void, progressCallback: (event: CordovaZipPluginUnzipProgressEvent) => void); + unzip( + sourceZip: string, + destinationDir: string, + onSuccess: (status: CordovaZipPluginUnzipResult, errorMessage?: string) => void); + unzip( + sourceZip: string, + destinationDir: string, + onSuccess: (status: CordovaZipPluginUnzipResult, errorMessage?: string) => void, + progressCallback: (event: CordovaZipPluginUnzipProgressEvent) => void); } declare var zip: CordovaZipPlugin; \ No newline at end of file diff --git a/zip.js b/zip.js index a3316588..cc8ed868 100644 --- a/zip.js +++ b/zip.js @@ -2,14 +2,14 @@ var exec = cordova.require('cordova/exec'); function newProgressEvent(result) { var event = { - loaded: result.loaded, - total: result.total + loaded: result.loaded, + total: result.total }; return event; } -exports.unzip = function(fileName, outputDirectory, callback, progressCallback) { - var win = function(result) { +exports.unzip = function (fileName, outputDirectory, callback, progressCallback) { + var win = function (result) { if (result && typeof result.loaded != "undefined") { if (progressCallback) { return progressCallback(newProgressEvent(result)); @@ -18,9 +18,9 @@ exports.unzip = function(fileName, outputDirectory, callback, progressCallback) callback(0); } }; - var fail = function(result) { + var fail = function (result) { if (callback) { - callback(-1); + callback(-1, result); } }; exec(win, fail, 'Zip', 'unzip', [fileName, outputDirectory]); From d29e320fa59a84f5871d7fa8d2c1bb35b56a1316 Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Wed, 13 Jun 2018 16:57:18 +0200 Subject: [PATCH 08/17] doc --- README.md | 31 ++++++++++++++--- package.json | 74 ++++++++++++++++++++--------------------- plugin.xml | 2 +- src/browser/ZipProxy.ts | 40 ++++++++++++---------- zip.js | 21 ++++++++++-- 5 files changed, 107 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 1968adb9..2914cc06 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ A Cordova plugin to unzip files in Android and iOS. cordova plugin add cordova-plugin-zip ## Usage - +```javascript zip.unzip(, , , []); - +``` Both source and destination arguments can be URLs obtained from the HTML File interface or absolute paths to files on the device. @@ -19,16 +19,39 @@ success, or -1 on failure. The progressCallback argument is optional and will be executed whenever a new ZipEntry has been extracted. E.g.: - +```javascript var progressCallback = function(progressEvent) { $( "#progressbar" ).progressbar("value", Math.round((progressEvent.loaded / progressEvent.total) * 100)); }; - +``` The values `loaded` and `total` are the number of compressed bytes processed and total. Total is the file size of the zip file. +## Example +```typescript +const downZipUrl: string = downZipFileEntry.toInternalURL(); +const downUnzipDirectoryUrl: string = downUnzipDir.toInternalURL(); +zip.unzip( + downZipUrl, + downUnzipDirectoryUrl, + (result: CordovaZipPluginUnzipResult, errorMessage: string) => { + if (result == CordovaZipPluginUnzipResult.Success) { + resolve(); + } else { + this.log.error(errorMessage, 'an error occurred during unzip'); + reject('an error occurred during unzip: ' + errorMessage); + } + }, + event => onProgress(event.loaded, event.total)); +``` + ## Release Notes +### 3.2.0 +* Browser platform support +* Updated doc +* Pass error message to upper layer + ### 3.1.0 (Feb 23, 2016) * Updated SSZipArchive (ios lib) to 1.1 diff --git a/package.json b/package.json index 797ebe13..a2e61cee 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,38 @@ { - "name": "cordova-plugin-zip", - "version": "3.1.0", - "description": "Unzips zip files", - "cordova": { - "id": "cordova-plugin-unzip", - "platforms": [ - "android", - "ios", - "browser" - ] - }, - "repository": { - "type": "git", - "url": "https://github.com/MobileChromeApps/cordova-plugin-zip.git" - }, - "keywords": [ - "cordova", - "zip", - "unzip", - "ecosystem:cordova", - "cordova-android", - "cordova-ios", - "cordova-browser" - ], - "engines": [ - { - "name": "cordova", - "version": ">=3.3.0" - } - ], - "author": "", - "license": "BSD", - "bugs": { - "url": "https://github.com/MobileChromeApps/zip/issues" - }, - "homepage": "https://github.com/MobileChromeApps/zip" -} + "name": "cordova-plugin-zip", + "version": "3.2.0", + "description": "Unzips zip files", + "cordova": { + "id": "cordova-plugin-unzip", + "platforms": [ + "android", + "ios", + "browser" + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/MobileChromeApps/cordova-plugin-zip.git" + }, + "keywords": [ + "cordova", + "zip", + "unzip", + "ecosystem:cordova", + "cordova-android", + "cordova-ios", + "cordova-browser" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.3.0" + } + ], + "author": "", + "license": "BSD", + "bugs": { + "url": "https://github.com/MobileChromeApps/zip/issues" + }, + "homepage": "https://github.com/MobileChromeApps/zip" +} \ No newline at end of file diff --git a/plugin.xml b/plugin.xml index 5352bb55..45c4ede3 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="3.2.0"> diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index 68d2e976..3146cc4f 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -101,8 +101,8 @@ interface SuccessCallback { } async function unzip( - zipFilePath: string, - outputDirectoryPath: string, + zipFileUrl: string, + outputDirectoryUrl: string, successCallback: SuccessCallback, errorCallback) { @@ -112,36 +112,42 @@ async function unzip( throw new Error('zip.js not available, please import it: https://gildas-lormeau.github.io/zip.js'); } - console.info(`unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.info(`unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); const fileSystem: FileSystem = await getFileSystem(); - console.debug(`retrieving output directory: ${outputDirectoryPath}`); + console.debug(`retrieving output directory: ${outputDirectoryUrl}`); const outputDirectoryEntry: DirectoryEntry = await new Promise((resolve, reject) => { - fileSystem.root.getDirectory(outputDirectoryPath, { create: true, exclusive: false }, resolve, reject); + fileSystem.root.getDirectory(outputDirectoryUrl, { create: true, exclusive: false }, resolve, reject); }); console.debug(`output directory entry: ${outputDirectoryEntry}`); - let zipEntry: FileEntry; - if (await exists(zipFilePath, fileSystem.root)) { - zipEntry = await getFileEntry(zipFilePath, fileSystem.root); + console.debug(`retrieving zip file: ${zipEntry}`); + new Promise((resolve, reject) => { + fileSystem.root.getDirectory(outputDirectoryUrl, { create: true, exclusive: false }, resolve, reject); + }); + console.debug(`zip file entry: ${zipEntry}`); + window.resolveLocalFileSystemURL() + let zipEntry: FileEntry = await ; + if (await exists(zipFileUrl, fileSystem.root)) { + zipEntry = await getFileEntry(zipFileUrl, fileSystem.root); } else { const tempFileSystem: FileSystem = await getFileSystem(FileSystemType.TEMPORARY); - zipEntry = await getFileEntry(zipFilePath, tempFileSystem.root); + zipEntry = await getFileEntry(zipFileUrl, tempFileSystem.root); } const zipBlob: Blob = await new Promise((resolve, reject) => { zipEntry.file(resolve, reject); }); - console.info(`open reader on zip: ${zipFilePath}`); + console.info(`open reader on zip: ${zipFileUrl}`); zip.createReader(new zip.BlobReader(zipBlob), (zipReader) => { - console.debug(`reader opened on zip: ${zipFilePath}`); + console.debug(`reader opened on zip: ${zipFileUrl}`); zipReader.getEntries(async (zipEntries) => { - console.debug(`entries read: ${zipFilePath}`); + console.debug(`entries read: ${zipFileUrl}`); successCallback({ loaded: 0, @@ -161,14 +167,14 @@ async function unzip( } zipReader.close(() => { - console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + console.info(`unzip OK from ${zipFileUrl} to ${outputDirectoryUrl}`); successCallback({ total: zipEntries.length }); }); } catch (e) { - console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.error(e, `error while unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); zipReader.close(); errorCallback(e); } @@ -176,7 +182,7 @@ async function unzip( }, errorCallback); } catch (e) { - console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.error(e, `error while unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); errorCallback(e); } } @@ -186,11 +192,11 @@ declare var require; module.exports = { unzip: function (successCallback, errorCallback, args) { - const [zipFilePath, outputDirectoryPath] = args; + const [zipFileUrl, outputDirectoryUrl] = args; zip.useWebWorkers = false; - unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); + unzip(zipFileUrl, outputDirectoryUrl, successCallback, errorCallback); } }; require("cordova/exec/proxy").add("Zip", module.exports); diff --git a/zip.js b/zip.js index cc8ed868..cd41af62 100644 --- a/zip.js +++ b/zip.js @@ -8,7 +8,24 @@ function newProgressEvent(result) { return event; } -exports.unzip = function (fileName, outputDirectory, callback, progressCallback) { +/** + * Example: + * const downZipUrl: string = downZipFileEntry.toInternalURL(); + const downUnzipDirectoryUrl: string = downUnzipDir.toInternalURL(); + zip.unzip( + downZipUrl, + downUnzipDirectoryUrl, + (result: CordovaZipPluginUnzipResult, errorMessage: string) => { + if (result == CordovaZipPluginUnzipResult.Success) { + resolve(); + } else { + this.log.error(errorMessage, 'an error occurred during unzip'); + reject('an error occurred during unzip: ' + errorMessage); + } + }, + event => onProgress(event.loaded, event.total)); + */ +exports.unzip = function (fileNameUrl, outputDirectoryUrl, callback, progressCallback) { var win = function (result) { if (result && typeof result.loaded != "undefined") { if (progressCallback) { @@ -23,5 +40,5 @@ exports.unzip = function (fileName, outputDirectory, callback, progressCallback) callback(-1, result); } }; - exec(win, fail, 'Zip', 'unzip', [fileName, outputDirectory]); + exec(win, fail, 'Zip', 'unzip', [fileNameUrl, outputDirectoryUrl]); }; \ No newline at end of file From 8ab87144f9ef86e36a4dbcca3219bae546ac3b3f Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Fri, 15 Jun 2018 11:48:05 +0200 Subject: [PATCH 09/17] resolve cdvfile scheme urls --- src/browser/ZipProxy.js | 79 +++++++++++++++++++++++++++++------------ src/browser/ZipProxy.ts | 64 ++++++++++++++++++++++++--------- 2 files changed, 105 insertions(+), 38 deletions(-) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index 29692baa..19241023 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -25,6 +25,43 @@ function getFileEntry(path, parentDirectory) { parentDirectory.getFile(path, {}, resolve, reject); }); } +function resolveOrCreateDirectoryEntry(entryUrl) { + return resolveOrCreateEntry(entryUrl, true); +} +function resolveOrCreateFileEntry(entryUrl) { + return resolveOrCreateEntry(entryUrl, false); +} +function resolveOrCreateEntry(entryUrl, directory) { + return __awaiter(this, void 0, void 0, function* () { + let entry; + try { + entry = (yield new Promise((resolve, reject) => { + window.resolveLocalFileSystemURL(entryUrl, resolve, reject); + })); + } + catch (e) { + console.error(e); + console.error(`cannot resolve directory entry at url ${entryUrl}`); + const fileSystem = yield (entryUrl.indexOf('/temporary/') != -1 ? getFileSystem(FileSystemType.TEMPORARY) : getFileSystem()); + let path = entryUrl; + if (entryUrl.indexOf('/temporary/') != -1) { + path = entryUrl.substring(entryUrl.indexOf('/temporary/') + '/temporary/'.length - 1); + } + else if (entryUrl.indexOf('/persistent/') != -1) { + path = entryUrl.substring(entryUrl.indexOf('/persistent/') + '/persistent/'.length - 1); + } + entry = yield new Promise((resolve, reject) => { + if (directory) { + fileSystem.root.getDirectory(path, { create: true, exclusive: false }, resolve, reject); + } + else { + fileSystem.root.getFile(path, { create: true, exclusive: true }, resolve, reject); + } + }); + } + return entry; + }); +} function exists(path, parentDirectory) { return __awaiter(this, void 0, void 0, function* () { try { @@ -47,8 +84,12 @@ var FileSystemType; FileSystemType[FileSystemType["TEMPORARY"] = window.TEMPORARY] = "TEMPORARY"; FileSystemType[FileSystemType["PERSISTENT"] = window.PERSISTENT] = "PERSISTENT"; })(FileSystemType || (FileSystemType = {})); +const fileSystemsCache = {}; function getFileSystem(type = FileSystemType.PERSISTENT) { return __awaiter(this, void 0, void 0, function* () { + if (fileSystemsCache[type]) { + return fileSystemsCache[type]; + } const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; console.debug(`zip plugin - requestFileSystem=${requestFileSystem} - storageInfo=${storageInfo}`); @@ -69,6 +110,7 @@ function getFileSystem(type = FileSystemType.PERSISTENT) { requestFileSystem(type, grantedBytes, resolve, reject); }); console.debug('FileSystem ready: ' + fileSystem.name); + fileSystemsCache[type] = fileSystem; return fileSystem; }); } @@ -97,35 +139,28 @@ function unzipEntry(entry, outputDirectoryEntry) { } }); } -function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) { +function unzip(zipFileUrl, outputDirectoryUrl, successCallback, errorCallback) { return __awaiter(this, void 0, void 0, function* () { try { if (!zip) { throw new Error('zip.js not available, please import it: https://gildas-lormeau.github.io/zip.js'); } - console.info(`unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.info(`unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); const fileSystem = yield getFileSystem(); - console.debug(`retrieving output directory: ${outputDirectoryPath}`); - const outputDirectoryEntry = yield new Promise((resolve, reject) => { - fileSystem.root.getDirectory(outputDirectoryPath, { create: true, exclusive: false }, resolve, reject); - }); + console.debug(`retrieving output directory: ${outputDirectoryUrl}`); + const outputDirectoryEntry = yield resolveOrCreateDirectoryEntry(outputDirectoryUrl); console.debug(`output directory entry: ${outputDirectoryEntry}`); - let zipEntry; - if (yield exists(zipFilePath, fileSystem.root)) { - zipEntry = yield getFileEntry(zipFilePath, fileSystem.root); - } - else { - const tempFileSystem = yield getFileSystem(FileSystemType.TEMPORARY); - zipEntry = yield getFileEntry(zipFilePath, tempFileSystem.root); - } + console.debug(`retrieving zip file: ${zipFileUrl}`); + let zipEntry = yield resolveOrCreateFileEntry(zipFileUrl); + console.debug(`zip file entry: ${zipEntry}`); const zipBlob = yield new Promise((resolve, reject) => { zipEntry.file(resolve, reject); }); - console.info(`open reader on zip: ${zipFilePath}`); + console.info(`open reader on zip: ${zipFileUrl}`); zip.createReader(new zip.BlobReader(zipBlob), (zipReader) => { - console.debug(`reader opened on zip: ${zipFilePath}`); + console.debug(`reader opened on zip: ${zipFileUrl}`); zipReader.getEntries((zipEntries) => __awaiter(this, void 0, void 0, function* () { - console.debug(`entries read: ${zipFilePath}`); + console.debug(`entries read: ${zipFileUrl}`); successCallback({ loaded: 0, total: zipEntries.length @@ -140,14 +175,14 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) }); } zipReader.close(() => { - console.info(`unzip OK from ${zipFilePath} to ${outputDirectoryPath}`); + console.info(`unzip OK from ${zipFileUrl} to ${outputDirectoryUrl}`); successCallback({ total: zipEntries.length }); }); } catch (e) { - console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.error(e, `error while unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); zipReader.close(); errorCallback(e); } @@ -155,16 +190,16 @@ function unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback) }, errorCallback); } catch (e) { - console.error(e, `error while unzipping ${zipFilePath} to ${outputDirectoryPath}`); + console.error(e, `error while unzipping ${zipFileUrl} to ${outputDirectoryUrl}`); errorCallback(e); } }); } module.exports = { unzip: function (successCallback, errorCallback, args) { - const [zipFilePath, outputDirectoryPath] = args; + const [zipFileUrl, outputDirectoryUrl] = args; zip.useWebWorkers = false; - unzip(zipFilePath, outputDirectoryPath, successCallback, errorCallback); + unzip(zipFileUrl, outputDirectoryUrl, successCallback, errorCallback); } }; require("cordova/exec/proxy").add("Zip", module.exports); diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index 3146cc4f..d66a2c06 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -22,6 +22,44 @@ function getFileEntry(path: string, parentDirectory: DirectoryEntry): Promise { + return resolveOrCreateEntry(entryUrl, true) as Promise; +} + +function resolveOrCreateFileEntry(entryUrl: string): Promise { + return resolveOrCreateEntry(entryUrl, false) as Promise; +} + +async function resolveOrCreateEntry(entryUrl: string, directory: boolean): Promise { + let entry: DirectoryEntry | FileEntry; + try { + entry = await new Promise((resolve, reject) => { + window.resolveLocalFileSystemURL(entryUrl, resolve, reject); + }) as DirectoryEntry | FileEntry; + } catch (e) { + console.error(e); + console.error(`cannot resolve directory entry at url ${entryUrl}`); + + const fileSystem: FileSystem = await (entryUrl.indexOf('/temporary/') != -1 ? getFileSystem(FileSystemType.TEMPORARY) : getFileSystem()); + let path: string = entryUrl; + if (entryUrl.indexOf('/temporary/') != -1) { + path = entryUrl.substring(entryUrl.indexOf('/temporary/') + '/temporary/'.length - 1); + } else if (entryUrl.indexOf('/persistent/') != -1) { + path = entryUrl.substring(entryUrl.indexOf('/persistent/') + '/persistent/'.length - 1); + } + + entry = await new Promise((resolve, reject) => { + if (directory) { + fileSystem.root.getDirectory(path, { create: true, exclusive: false }, resolve, reject); + } else { + fileSystem.root.getFile(path, { create: true, exclusive: true }, resolve, reject); + } + }); + } + + return entry; +} + async function exists(path: string, parentDirectory: DirectoryEntry): Promise { try { await getFileEntry(path, parentDirectory); @@ -44,7 +82,12 @@ enum FileSystemType { PERSISTENT = window.PERSISTENT } +const fileSystemsCache: { [type: number]: FileSystem } = {}; async function getFileSystem(type: FileSystemType = FileSystemType.PERSISTENT): Promise { + if (fileSystemsCache[type]) { + return fileSystemsCache[type]; + } + const requestFileSystem = window['webkitRequestFileSystem'] || window.requestFileSystem; const storageInfo = navigator['webkitPersistentStorage'] || window['storageInfo']; @@ -69,6 +112,8 @@ async function getFileSystem(type: FileSystemType = FileSystemType.PERSISTENT): }); console.debug('FileSystem ready: ' + fileSystem.name); + fileSystemsCache[type] = fileSystem; + return fileSystem; } @@ -117,25 +162,12 @@ async function unzip( const fileSystem: FileSystem = await getFileSystem(); console.debug(`retrieving output directory: ${outputDirectoryUrl}`); - const outputDirectoryEntry: DirectoryEntry = await new Promise((resolve, reject) => { - fileSystem.root.getDirectory(outputDirectoryUrl, { create: true, exclusive: false }, resolve, reject); - }); - + const outputDirectoryEntry: DirectoryEntry = await resolveOrCreateDirectoryEntry(outputDirectoryUrl); console.debug(`output directory entry: ${outputDirectoryEntry}`); - console.debug(`retrieving zip file: ${zipEntry}`); - new Promise((resolve, reject) => { - fileSystem.root.getDirectory(outputDirectoryUrl, { create: true, exclusive: false }, resolve, reject); - }); + console.debug(`retrieving zip file: ${zipFileUrl}`); + let zipEntry: FileEntry = await resolveOrCreateFileEntry(zipFileUrl); console.debug(`zip file entry: ${zipEntry}`); - window.resolveLocalFileSystemURL() - let zipEntry: FileEntry = await ; - if (await exists(zipFileUrl, fileSystem.root)) { - zipEntry = await getFileEntry(zipFileUrl, fileSystem.root); - } else { - const tempFileSystem: FileSystem = await getFileSystem(FileSystemType.TEMPORARY); - zipEntry = await getFileEntry(zipFileUrl, tempFileSystem.root); - } const zipBlob: Blob = await new Promise((resolve, reject) => { zipEntry.file(resolve, reject); From 74a0169bb6520d2a318fdbf1b79bcaf82f8e9daf Mon Sep 17 00:00:00 2001 From: Louis Grignon Date: Fri, 15 Jun 2018 18:35:24 +0200 Subject: [PATCH 10/17] fixed progress --- src/browser/README.md | 2 +- src/browser/ZipProxy.js | 13 +- src/browser/ZipProxy.ts | 18 +- src/browser/cordova-plugin-file.d.ts | 378 +++++++++++++++++++++++++++ src/browser/zip.js.d.ts | 94 +++++++ 5 files changed, 487 insertions(+), 18 deletions(-) create mode 100644 src/browser/cordova-plugin-file.d.ts create mode 100644 src/browser/zip.js.d.ts diff --git a/src/browser/README.md b/src/browser/README.md index 67211837..948a9ecd 100644 --- a/src/browser/README.md +++ b/src/browser/README.md @@ -3,7 +3,7 @@ ## Build ``` -tsc ZipProxy.ts --target ES6 +tsc cordova-plugin-file.d.ts zip.js.d.ts ZipProxy.ts --target ES6 ``` Some harmless errors show up about missing Cordova File Plugin definitions. Be careful, other errors might not be harmless :) diff --git a/src/browser/ZipProxy.js b/src/browser/ZipProxy.js index 19241023..baca2a49 100644 --- a/src/browser/ZipProxy.js +++ b/src/browser/ZipProxy.js @@ -141,6 +141,9 @@ function unzipEntry(entry, outputDirectoryEntry) { } function unzip(zipFileUrl, outputDirectoryUrl, successCallback, errorCallback) { return __awaiter(this, void 0, void 0, function* () { + function onProgress(loaded, total) { + successCallback({ loaded, total }, { keepCallback: true }); + } try { if (!zip) { throw new Error('zip.js not available, please import it: https://gildas-lormeau.github.io/zip.js'); @@ -161,18 +164,12 @@ function unzip(zipFileUrl, outputDirectoryUrl, successCallback, errorCallback) { console.debug(`reader opened on zip: ${zipFileUrl}`); zipReader.getEntries((zipEntries) => __awaiter(this, void 0, void 0, function* () { console.debug(`entries read: ${zipFileUrl}`); - successCallback({ - loaded: 0, - total: zipEntries.length - }); + onProgress(0, zipEntries.length); try { let i = 0; for (const entry of zipEntries) { yield unzipEntry(entry, outputDirectoryEntry); - successCallback({ - loaded: ++i, - total: zipEntries.length - }); + onProgress(++i, zipEntries.length); } zipReader.close(() => { console.info(`unzip OK from ${zipFileUrl} to ${outputDirectoryUrl}`); diff --git a/src/browser/ZipProxy.ts b/src/browser/ZipProxy.ts index d66a2c06..ff04c8ff 100644 --- a/src/browser/ZipProxy.ts +++ b/src/browser/ZipProxy.ts @@ -142,7 +142,7 @@ async function unzipEntry(entry: zip.Entry, outputDirectoryEntry: DirectoryEntry } interface SuccessCallback { - (event: { loaded?: number, total: number }): void; + (event: { loaded?: number, total: number }, options?): void; } async function unzip( @@ -151,6 +151,12 @@ async function unzip( successCallback: SuccessCallback, errorCallback) { + function onProgress(loaded: number, total: number) { + successCallback( + { loaded, total }, + { keepCallback: true }); + } + try { if (!zip) { @@ -181,10 +187,7 @@ async function unzip( console.debug(`entries read: ${zipFileUrl}`); - successCallback({ - loaded: 0, - total: zipEntries.length - }); + onProgress(0, zipEntries.length); try { @@ -192,10 +195,7 @@ async function unzip( for (const entry of zipEntries) { await unzipEntry(entry, outputDirectoryEntry); - successCallback({ - loaded: ++i, - total: zipEntries.length - }); + onProgress(++i, zipEntries.length); } zipReader.close(() => { diff --git a/src/browser/cordova-plugin-file.d.ts b/src/browser/cordova-plugin-file.d.ts new file mode 100644 index 00000000..c748e3da --- /dev/null +++ b/src/browser/cordova-plugin-file.d.ts @@ -0,0 +1,378 @@ +// Type definitions for Apache Cordova File System plugin +// Project: https://github.com/apache/cordova-plugin-file +// Definitions by: Microsoft Open Technologies Inc +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// +// Copyright (c) Microsoft Open Technologies, Inc. +// Licensed under the MIT license. + +interface Window { + /** + * Requests a filesystem in which to store application data. + * @param type Whether the filesystem requested should be persistent, as defined above. Use one of TEMPORARY or PERSISTENT. + * @param size This is an indicator of how much storage space, in bytes, the application expects to need. + * @param successCallback The callback that is called when the user agent provides a filesystem. + * @param errorCallback A callback that is called when errors happen, or when the request to obtain the filesystem is denied. + */ + requestFileSystem( + type: LocalFileSystem, + size: number, + successCallback: (fileSystem: FileSystem) => void, + errorCallback?: (fileError: FileError) => void): void; + /** + * Look up file system Entry referred to by local URL. + * @param string url URL referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URL + * @param errorCallback invoked if error occurs retrieving file system entry + */ + resolveLocalFileSystemURL(url: string, + successCallback: (entry: Entry) => void, + errorCallback?: (error: FileError) => void): void; + /** + * Look up file system Entry referred to by local URI. + * @param string uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ + resolveLocalFileSystemURI(uri: string, + successCallback: (entry: Entry) => void, + errorCallback?: (error: FileError) => void): void; + TEMPORARY: number; + PERSISTENT: number; +} + +/** This interface represents a file system. */ +interface FileSystem { + /* The name of the file system, unique across the list of exposed file systems. */ + name: string; + /** The root directory of the file system. */ + root: DirectoryEntry; +} + +/** + * An abstract interface representing entries in a file system, + * each of which may be a File or DirectoryEntry. + */ +interface Entry { + /** Entry is a file. */ + isFile: boolean; + /** Entry is a directory. */ + isDirectory: boolean; + /** The name of the entry, excluding the path leading to it. */ + name: string; + /** The full absolute path from the root to the entry. */ + fullPath: string; + /** The file system on which the entry resides. */ + fileSystem: FileSystem; + nativeURL: string; + /** + * Look up metadata about this entry. + * @param successCallback A callback that is called with the time of the last modification. + * @param errorCallback A callback that is called when errors happen. + */ + getMetadata( + successCallback: (metadata: Metadata) => void, + errorCallback?: (error: FileError) => void): void; + /** + * Move an entry to a different location on the file system. It is an error to try to: + * move a directory inside itself or to any child at any depth;move an entry into its parent if a name different from its current one isn't provided; + * move a file to a path occupied by a directory; + * move a directory to a path occupied by a file; + * move any element to a path occupied by a directory which is not empty. + * A move of a file on top of an existing file must attempt to delete and replace that file. + * A move of a directory on top of an existing empty directory must attempt to delete and replace that directory. + * @param parent The directory to which to move the entry. + * @param newName The new name of the entry. Defaults to the Entry's current name if unspecified. + * @param successCallback A callback that is called with the Entry for the new location. + * @param errorCallback A callback that is called when errors happen. + */ + moveTo(parent: DirectoryEntry, + newName?: string, + successCallback?: (entry: Entry) => void, + errorCallback?: (error: FileError) => void): void; + /** + * Copy an entry to a different location on the file system. It is an error to try to: + * copy a directory inside itself or to any child at any depth; + * copy an entry into its parent if a name different from its current one isn't provided; + * copy a file to a path occupied by a directory; + * copy a directory to a path occupied by a file; + * copy any element to a path occupied by a directory which is not empty. + * A copy of a file on top of an existing file must attempt to delete and replace that file. + * A copy of a directory on top of an existing empty directory must attempt to delete and replace that directory. + * Directory copies are always recursive--that is, they copy all contents of the directory. + * @param parent The directory to which to move the entry. + * @param newName The new name of the entry. Defaults to the Entry's current name if unspecified. + * @param successCallback A callback that is called with the Entry for the new object. + * @param errorCallback A callback that is called when errors happen. + */ + copyTo(parent: DirectoryEntry, + newName?: string, + successCallback?: (entry: Entry) => void, + errorCallback?: (error: FileError) => void): void; + /** + * Returns a URL that can be used as the src attribute of a