From b1233b41253026da530eeec8dbe4e66c1fea9248 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 14:19:50 +1000 Subject: [PATCH 01/46] Files for electron platform --- plugin.xml | 17 + src/electron/FileProxy.js | 1058 ++++++++++++++++++++++++++++++++++++ www/electron/FileSystem.js | 30 + 3 files changed, 1105 insertions(+) create mode 100644 src/electron/FileProxy.js create mode 100644 www/electron/FileSystem.js diff --git a/plugin.xml b/plugin.xml index 01bff31f6..44be39173 100644 --- a/plugin.xml +++ b/plugin.xml @@ -237,6 +237,7 @@ to config.xml in order for the application to find previously stored files. + @@ -257,4 +258,20 @@ to config.xml in order for the application to find previously stored files. + + + + + + + + + + + + + + + + diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js new file mode 100644 index 000000000..a2f99d9c2 --- /dev/null +++ b/src/electron/FileProxy.js @@ -0,0 +1,1058 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +(function () { + /* global require, exports, module */ + /* global FILESYSTEM_PREFIX */ + /* global FileReader */ + /* global atob, btoa, Blob */ + + /* Heavily based on https://github.com/ebidel/idb.filesystem.js */ + + // For chrome we don't need to implement proxy methods + // All functionality can be accessed natively. + if (require('./isChrome')()) { + var pathsPrefix = { + // Read-only directory where the application is installed. + applicationDirectory: location.origin + '/', // eslint-disable-line no-undef + // Where to put app-specific data files. + dataDirectory: 'filesystem:file:///persistent/', + // Cached files that should survive app restarts. + // Apps should not rely on the OS to delete files in here. + cacheDirectory: 'filesystem:file:///temporary/' + }; + + exports.requestAllPaths = function (successCallback) { + successCallback(pathsPrefix); + }; + + require('cordova/exec/proxy').add('File', module.exports); + return; + } + + var LocalFileSystem = require('./LocalFileSystem'); + var FileSystem = require('./FileSystem'); + var FileEntry = require('./FileEntry'); + var FileError = require('./FileError'); + var DirectoryEntry = require('./DirectoryEntry'); + var File = require('./File'); + + (function (exports, global) { + var indexedDB = global.indexedDB || global.mozIndexedDB; + if (!indexedDB) { + throw 'Firefox OS File plugin: indexedDB not supported'; + } + + var fs_ = null; + + var idb_ = {}; + idb_.db = null; + var FILE_STORE_ = 'entries'; + + var DIR_SEPARATOR = '/'; + + var pathsPrefix = { + // Read-only directory where the application is installed. + applicationDirectory: location.origin + '/', // eslint-disable-line no-undef + // Where to put app-specific data files. + dataDirectory: 'file:///persistent/', + // Cached files that should survive app restarts. + // Apps should not rely on the OS to delete files in here. + cacheDirectory: 'file:///temporary/' + }; + + var unicodeLastChar = 65535; + + /** * Exported functionality ***/ + + exports.requestFileSystem = function (successCallback, errorCallback, args) { + var type = args[0]; + // Size is ignored since IDB filesystem size depends + // on browser implementation and can't be set up by user + var size = args[1]; // eslint-disable-line no-unused-vars + + if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent'; + var storageName = (location.protocol + location.host).replace(/:/g, '_'); // eslint-disable-line no-undef + + var root = new DirectoryEntry('', DIR_SEPARATOR); + fs_ = new FileSystem(name, root); + + idb_.open(storageName, function () { + successCallback(fs_); + }, errorCallback); + }; + + // Overridden by Android, BlackBerry 10 and iOS to populate fsMap + require('./fileSystems').getFs = function (name, callback) { + callback(new FileSystem(name, fs_.root)); + }; + + // list a directory's contents (files and folders). + exports.readEntries = function (successCallback, errorCallback, args) { + var fullPath = args[0]; + + if (typeof successCallback !== 'function') { + throw Error('Expected successCallback argument.'); + } + + var path = resolveToFullPath_(fullPath); + + exports.getDirectory(function () { + idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function (entries) { + successCallback(entries); + }, errorCallback); + }, function () { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + }, [path.storagePath, path.fullPath, {create: false}]); + }; + + exports.getFile = function (successCallback, errorCallback, args) { + var fullPath = args[0]; + var path = args[1]; + var options = args[2] || {}; + + // Create an absolute path if we were handed a relative one. + path = resolveToFullPath_(fullPath, path); + + idb_.get(path.storagePath, function (fileEntry) { + if (options.create === true && options.exclusive === true && fileEntry) { + // If create and exclusive are both true, and the path already exists, + // getFile must fail. + + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + } else if (options.create === true && !fileEntry) { + // If create is true, the path doesn't exist, and no other error occurs, + // getFile must create it as a zero-length file and return a corresponding + // FileEntry. + var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); + + newFileEntry.file_ = new MyFile({ + size: 0, + name: newFileEntry.name, + lastModifiedDate: new Date(), + storagePath: path.storagePath + }); + + idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback); + } else if (options.create === true && fileEntry) { + if (fileEntry.isFile) { + // Overwrite file, delete then create new. + idb_['delete'](path.storagePath, function () { + var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); + + newFileEntry.file_ = new MyFile({ + size: 0, + name: newFileEntry.name, + lastModifiedDate: new Date(), + storagePath: path.storagePath + }); + + idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback); + }, errorCallback); + } else { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + } + } else if ((!options.create || options.create === false) && !fileEntry) { + // If create is not true and the path doesn't exist, getFile must fail. + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + } else if ((!options.create || options.create === false) && fileEntry && + fileEntry.isDirectory) { + // If create is not true and the path exists, but is a directory, getFile + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); + } + } else { + // Otherwise, if no other error occurs, getFile must return a FileEntry + // corresponding to path. + + successCallback(fileEntryFromIdbEntry(fileEntry)); + } + }, errorCallback); + }; + + exports.getFileMetadata = function (successCallback, errorCallback, args) { + var fullPath = args[0]; + + exports.getFile(function (fileEntry) { + successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate, + fileEntry.file_.size)); + }, errorCallback, [fullPath, null]); + }; + + exports.getMetadata = function (successCallback, errorCallback, args) { + exports.getFile(function (fileEntry) { + successCallback( + { + modificationTime: fileEntry.file_.lastModifiedDate, + size: fileEntry.file_.lastModifiedDate + }); + }, errorCallback, args); + }; + + exports.setMetadata = function (successCallback, errorCallback, args) { + var fullPath = args[0]; + var metadataObject = args[1]; + + exports.getFile(function (fileEntry) { + fileEntry.file_.lastModifiedDate = metadataObject.modificationTime; + idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback); + }, errorCallback, [fullPath, null]); + }; + + exports.write = function (successCallback, errorCallback, args) { + var fileName = args[0]; + var data = args[1]; + var position = args[2]; + var isBinary = args[3]; // eslint-disable-line no-unused-vars + + if (!data) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + if (typeof data === 'string' || data instanceof String) { + data = new Blob([data]); // eslint-disable-line no-undef + } + + exports.getFile(function (fileEntry) { + var blob_ = fileEntry.file_.blob_; + + if (!blob_) { + blob_ = new Blob([data], {type: data.type}); // eslint-disable-line no-undef + } else { + // Calc the head and tail fragments + var head = blob_.slice(0, position); + var tail = blob_.slice(position + (data.size || data.byteLength)); + + // Calc the padding + var padding = position - head.size; + if (padding < 0) { + padding = 0; + } + + // Do the "write". In fact, a full overwrite of the Blob. + blob_ = new Blob([head, new Uint8Array(padding), data, tail], // eslint-disable-line no-undef + {type: data.type}); + } + + // Set the blob we're writing on this file entry so we can recall it later. + fileEntry.file_.blob_ = blob_; + fileEntry.file_.lastModifiedDate = new Date() || null; + fileEntry.file_.size = blob_.size; + fileEntry.file_.name = blob_.name; + fileEntry.file_.type = blob_.type; + + idb_.put(fileEntry, fileEntry.file_.storagePath, function () { + successCallback(data.size || data.byteLength); + }, errorCallback); + }, errorCallback, [fileName, null]); + }; + + exports.readAsText = function (successCallback, errorCallback, args) { + var fileName = args[0]; + var enc = args[1]; + var startPos = args[2]; + var endPos = args[3]; + + readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback); + }; + + exports.readAsDataURL = function (successCallback, errorCallback, args) { + var fileName = args[0]; + var startPos = args[1]; + var endPos = args[2]; + + readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback); + }; + + exports.readAsBinaryString = function (successCallback, errorCallback, args) { + var fileName = args[0]; + var startPos = args[1]; + var endPos = args[2]; + + readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback); + }; + + exports.readAsArrayBuffer = function (successCallback, errorCallback, args) { + var fileName = args[0]; + var startPos = args[1]; + var endPos = args[2]; + + readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback); + }; + + exports.removeRecursively = exports.remove = function (successCallback, errorCallback, args) { + if (typeof successCallback !== 'function') { + throw Error('Expected successCallback argument.'); + } + + var fullPath = resolveToFullPath_(args[0]).storagePath; + if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) { + errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR); + return; + } + + function deleteEntry (isDirectory) { + // TODO: This doesn't protect against directories that have content in it. + // Should throw an error instead if the dirEntry is not empty. + idb_['delete'](fullPath, function () { + successCallback(); + }, function () { + if (errorCallback) { errorCallback(); } + }, isDirectory); + } + + // We need to to understand what we are deleting: + exports.getDirectory(function (entry) { + deleteEntry(entry.isDirectory); + }, function () { + // DirectoryEntry was already deleted or entry is FileEntry + deleteEntry(false); + }, [fullPath, null, {create: false}]); + }; + + exports.getDirectory = function (successCallback, errorCallback, args) { + var fullPath = args[0]; + var path = args[1]; + var options = args[2]; + + // Create an absolute path if we were handed a relative one. + path = resolveToFullPath_(fullPath, path); + + idb_.get(path.storagePath, function (folderEntry) { + if (!options) { + options = {}; + } + + if (options.create === true && options.exclusive === true && folderEntry) { + // If create and exclusive are both true, and the path already exists, + // getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + // There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's + // so we are shielding from it with returns. + return; + } + + if (options.create === true && !folderEntry) { + // If create is true, the path doesn't exist, and no other error occurs, + // getDirectory must create it as a zero-length file and return a corresponding + // MyDirectoryEntry. + var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); + + idb_.put(dirEntry, path.storagePath, successCallback, errorCallback); + return; + } + + if (options.create === true && folderEntry) { + + if (folderEntry.isDirectory) { + // IDB won't save methods, so we need re-create the MyDirectoryEntry. + successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem)); + } else { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + } + return; + } + + if ((!options.create || options.create === false) && !folderEntry) { + // Handle root special. It should always exist. + if (path.fullPath === DIR_SEPARATOR) { + successCallback(fs_.root); + return; + } + + // If create is not true and the path doesn't exist, getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + + return; + } + if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) { + // If create is not true and the path exists, but is a file, getDirectory + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); + } + return; + } + + // Otherwise, if no other error occurs, getDirectory must return a + // MyDirectoryEntry corresponding to path. + + // IDB won't' save methods, so we need re-create MyDirectoryEntry. + successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem)); + }, errorCallback); + }; + + exports.getParent = function (successCallback, errorCallback, args) { + if (typeof successCallback !== 'function') { + throw Error('Expected successCallback argument.'); + } + + var fullPath = args[0]; + // fullPath is like this: + // file:///persistent/path/to/file or + // file:///persistent/path/to/directory/ + + if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory || + fullPath === pathsPrefix.dataDirectory) { + successCallback(fs_.root); + return; + } + + // To delete all slashes at the end + while (fullPath[fullPath.length - 1] === '/') { + fullPath = fullPath.substr(0, fullPath.length - 1); + } + + var pathArr = fullPath.split(DIR_SEPARATOR); + pathArr.pop(); + var parentName = pathArr.pop(); + var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR; + + // To get parent of root files + var joined = path + parentName + DIR_SEPARATOR;// is like this: file:///persistent/ + if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) { + exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]); + return; + } + + exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]); + }; + + exports.copyTo = function (successCallback, errorCallback, args) { + var srcPath = args[0]; + var parentFullPath = args[1]; + var name = args[2]; + + if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + + return; + } + + // Read src file + exports.getFile(function (srcFileEntry) { + + var path = resolveToFullPath_(parentFullPath); + // Check directory + exports.getDirectory(function () { + + // Create dest file + exports.getFile(function (dstFileEntry) { + + exports.write(function () { + successCallback(dstFileEntry); + }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]); + + }, errorCallback, [parentFullPath, name, {create: true}]); + + }, function () { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } }, + [path.storagePath, null, {create: false}]); + + }, errorCallback, [srcPath, null]); + }; + + exports.moveTo = function (successCallback, errorCallback, args) { + var srcPath = args[0]; + // parentFullPath and name parameters is ignored because + // args is being passed downstream to exports.copyTo method + var parentFullPath = args[1]; // eslint-disable-line + var name = args[2]; // eslint-disable-line + + exports.copyTo(function (fileEntry) { + + exports.remove(function () { + successCallback(fileEntry); + }, errorCallback, [srcPath]); + + }, errorCallback, args); + }; + + exports.resolveLocalFileSystemURI = function (successCallback, errorCallback, args) { + var path = args[0]; + + // Ignore parameters + if (path.indexOf('?') !== -1) { + path = String(path).split('?')[0]; + } + + // support for encodeURI + if (/\%5/g.test(path) || /\%20/g.test(path)) { // eslint-disable-line no-useless-escape + path = decodeURI(path); + } + + if (path.trim()[0] === '/') { + if (errorCallback) { + errorCallback(FileError.ENCODING_ERR); + } + return; + } + + // support for cdvfile + if (path.trim().substr(0, 7) === 'cdvfile') { + if (path.indexOf('cdvfile://localhost') === -1) { + if (errorCallback) { + errorCallback(FileError.ENCODING_ERR); + } + return; + } + + var indexPersistent = path.indexOf('persistent'); + var indexTemporary = path.indexOf('temporary'); + + // cdvfile://localhost/persistent/path/to/file + if (indexPersistent !== -1) { + path = 'file:///persistent' + path.substr(indexPersistent + 10); + } else if (indexTemporary !== -1) { + path = 'file:///temporary' + path.substr(indexTemporary + 9); + } else { + if (errorCallback) { + errorCallback(FileError.ENCODING_ERR); + } + return; + } + } + + // to avoid path form of '///path/to/file' + function handlePathSlashes (path) { + var cutIndex = 0; + for (var i = 0; i < path.length - 1; i++) { + if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) { + cutIndex = i + 1; + } else break; + } + + return path.substr(cutIndex); + } + + // Handle localhost containing paths (see specs ) + if (path.indexOf('file://localhost/') === 0) { + path = path.replace('file://localhost/', 'file:///'); + } + + if (path.indexOf(pathsPrefix.dataDirectory) === 0) { + path = path.substring(pathsPrefix.dataDirectory.length - 1); + path = handlePathSlashes(path); + + exports.requestFileSystem(function () { + exports.getFile(successCallback, function () { + exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path, + {create: false}]); + }, [pathsPrefix.dataDirectory, path, {create: false}]); + }, errorCallback, [LocalFileSystem.PERSISTENT]); + } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) { + path = path.substring(pathsPrefix.cacheDirectory.length - 1); + path = handlePathSlashes(path); + + exports.requestFileSystem(function () { + exports.getFile(successCallback, function () { + exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path, + {create: false}]); + }, [pathsPrefix.cacheDirectory, path, {create: false}]); + }, errorCallback, [LocalFileSystem.TEMPORARY]); + } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) { + path = path.substring(pathsPrefix.applicationDirectory.length); + // TODO: need to cut out redundant slashes? + + var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef + xhr.open('GET', path, true); + xhr.onreadystatechange = function () { + if (xhr.status === 200 && xhr.readyState === 4) { + exports.requestFileSystem(function (fs) { + fs.name = location.hostname; // eslint-disable-line no-undef + + // TODO: need to call exports.getFile(...) to handle errors correct + fs.root.getFile(path, {create: true}, writeFile, errorCallback); + }, errorCallback, [LocalFileSystem.PERSISTENT]); + } + }; + + xhr.onerror = function () { + if (errorCallback) { + errorCallback(FileError.NOT_READABLE_ERR); + } + }; + + xhr.send(); + } else { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + } + + function writeFile (entry) { + entry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function (evt) { + if (!evt.target.error) { + entry.filesystemName = location.hostname; // eslint-disable-line no-undef + successCallback(entry); + } + }; + fileWriter.onerror = function () { + if (errorCallback) { + errorCallback(FileError.NOT_READABLE_ERR); + } + }; + fileWriter.write(new Blob([xhr.response])); // eslint-disable-line no-undef + }, errorCallback); // eslint-disable-line no-undef + } + }; + + exports.requestAllPaths = function (successCallback) { + successCallback(pathsPrefix); + }; + + /** * Helpers ***/ + + /** + * Interface to wrap the native File interface. + * + * This interface is necessary for creating zero-length (empty) files, + * something the Filesystem API allows you to do. Unfortunately, File's + * constructor cannot be called directly, making it impossible to instantiate + * an empty File in JS. + * + * @param {Object} opts Initial values. + * @constructor + */ + function MyFile (opts) { + var blob_ = new Blob(); // eslint-disable-line no-undef + + this.size = opts.size || 0; + this.name = opts.name || ''; + this.type = opts.type || ''; + this.lastModifiedDate = opts.lastModifiedDate || null; + this.storagePath = opts.storagePath || ''; + + // Need some black magic to correct the object's size/name/type based on the + // blob that is saved. + Object.defineProperty(this, 'blob_', { + enumerable: true, + get: function () { + return blob_; + }, + set: function (val) { + blob_ = val; + this.size = blob_.size; + this.name = blob_.name; + this.type = blob_.type; + this.lastModifiedDate = blob_.lastModifiedDate; + }.bind(this) + }); + } + + MyFile.prototype.constructor = MyFile; + + var MyFileHelper = { + toJson: function (myFile, success) { + /* + Safari private browse mode cannot store Blob object to indexeddb. + Then use pure json object instead of Blob object. + */ + var fr = new FileReader(); + fr.onload = function (ev) { + var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(fr.result))); + success({ + opt: { + size: myFile.size, + name: myFile.name, + type: myFile.type, + lastModifiedDate: myFile.lastModifiedDate, + storagePath: myFile.storagePath + }, + base64: base64 + }); + }; + fr.readAsArrayBuffer(myFile.blob_); + }, + setBase64: function (myFile, base64) { + if (base64) { + var arrayBuffer = (new Uint8Array( + [].map.call(atob(base64), function (c) { return c.charCodeAt(0); }) + )).buffer; + + myFile.blob_ = new Blob([arrayBuffer], { type: myFile.type }); + } else { + myFile.blob_ = new Blob(); + } + } + }; + + // When saving an entry, the fullPath should always lead with a slash and never + // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute + // one. This method ensures path is legit! + function resolveToFullPath_ (cwdFullPath, path) { + path = path || ''; + var fullPath = path; + var prefix = ''; + + cwdFullPath = cwdFullPath || DIR_SEPARATOR; + if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) { + prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length)); + cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length)); + } + + var relativePath = path[0] !== DIR_SEPARATOR; + if (relativePath) { + fullPath = cwdFullPath; + if (cwdFullPath !== DIR_SEPARATOR) { + fullPath += DIR_SEPARATOR + path; + } else { + fullPath += path; + } + } + + // Remove doubled separator substrings + var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g'); + fullPath = fullPath.replace(re, DIR_SEPARATOR); + + // Adjust '..'s by removing parent directories when '..' flows in path. + var parts = fullPath.split(DIR_SEPARATOR); + for (var i = 0; i < parts.length; ++i) { + var part = parts[i]; + if (part === '..') { + parts[i - 1] = ''; + parts[i] = ''; + } + } + fullPath = parts.filter(function (el) { + return el; + }).join(DIR_SEPARATOR); + + // Add back in leading slash. + if (fullPath[0] !== DIR_SEPARATOR) { + fullPath = DIR_SEPARATOR + fullPath; + } + + // Replace './' by current dir. ('./one/./two' -> one/two) + fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR); + + // Replace '//' with '/'. + fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR); + + // Replace '/.' with '/'. + fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR); + + // Remove '/' if it appears on the end. + if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && + fullPath !== DIR_SEPARATOR) { + fullPath = fullPath.substring(0, fullPath.length - 1); + } + + var storagePath = prefix + fullPath; + storagePath = decodeURI(storagePath); + fullPath = decodeURI(fullPath); + + return { + storagePath: storagePath, + fullPath: fullPath, + fileName: fullPath.split(DIR_SEPARATOR).pop(), + fsName: prefix.split(DIR_SEPARATOR).pop() + }; + } + + function fileEntryFromIdbEntry (fileEntry) { + // IDB won't save methods, so we need re-create the FileEntry. + var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem); + clonedFileEntry.file_ = fileEntry.file_; + + return clonedFileEntry; + } + + function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) { + exports.getFile(function (fileEntry) { + var fileReader = new FileReader(); // eslint-disable-line no-undef + var blob = fileEntry.file_.blob_.slice(startPos, endPos); + + fileReader.onload = function (e) { + successCallback(e.target.result); + }; + + fileReader.onerror = errorCallback; + + switch (what) { + case 'text': + fileReader.readAsText(blob, encoding); + break; + case 'dataURL': + fileReader.readAsDataURL(blob); + break; + case 'arrayBuffer': + fileReader.readAsArrayBuffer(blob); + break; + case 'binaryString': + fileReader.readAsBinaryString(blob); + break; + } + + }, errorCallback, [fullPath, null]); + } + + /** * Core logic to handle IDB operations ***/ + + idb_.open = function (dbName, successCallback, errorCallback) { + var self = this; + + // TODO: FF 12.0a1 isn't liking a db name with : in it. + var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version */); + + request.onerror = errorCallback || onError; + + request.onupgradeneeded = function (e) { + // First open was called or higher db version was used. + + // console.log('onupgradeneeded: oldVersion:' + e.oldVersion, + // 'newVersion:' + e.newVersion); + + self.db = e.target.result; + self.db.onerror = onError; + + if (!self.db.objectStoreNames.contains(FILE_STORE_)) { + self.db.createObjectStore(FILE_STORE_/*, {keyPath: 'id', autoIncrement: true} */); + } + }; + + request.onsuccess = function (e) { + self.db = e.target.result; + self.db.onerror = onError; + successCallback(e); + }; + + request.onblocked = errorCallback || onError; + }; + + idb_.close = function () { + this.db.close(); + this.db = null; + }; + + idb_.get = function (fullPath, successCallback, errorCallback) { + if (!this.db) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + var tx = this.db.transaction([FILE_STORE_], 'readonly'); + + var request = tx.objectStore(FILE_STORE_).get(fullPath); + + tx.onabort = errorCallback || onError; + tx.oncomplete = function () { + var entry = request.result; + if (entry && entry.file_json) { + /* + Safari private browse mode cannot store Blob object to indexeddb. + Then use pure json object instead of Blob object. + */ + entry.file_ = new MyFile(entry.file_json.opt); + MyFileHelper.setBase64(entry.file_, entry.file_json.base64); + delete entry.file_json; + } + successCallback(entry); + }; + }; + + idb_.getAllEntries = function (fullPath, storagePath, successCallback, errorCallback) { + if (!this.db) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + var results = []; + + if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) { + storagePath = storagePath.substring(0, storagePath.length - 1); + } + + var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ', + storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar)); + + var tx = this.db.transaction([FILE_STORE_], 'readonly'); + tx.onabort = errorCallback || onError; + tx.oncomplete = function () { + results = results.filter(function (val) { + var pathWithoutSlash = val.fullPath; + + if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) { + pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1); + } + + var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length; + var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length; + + /* Input fullPath parameter equals '//' for root folder */ + /* Entries in root folder has valPartsLen equals 2 (see below) */ + if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && fullPath.trim().length === 2) { + fullPathPartsLen = 1; + } else if (fullPath[fullPath.length - 1] === DIR_SEPARATOR) { + fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length; + } else { + fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length; + } + + if (valPartsLen === fullPathPartsLen + 1) { + // If this a subfolder and entry is a direct child, include it in + // the results. Otherwise, it's not an entry of this folder. + return val; + } else return false; + }); + + successCallback(results); + }; + + var request = tx.objectStore(FILE_STORE_).openCursor(range); + + request.onsuccess = function (e) { + var cursor = e.target.result; + if (cursor) { + var val = cursor.value; + + results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem)); + cursor['continue'](); + } + }; + }; + + idb_['delete'] = function (fullPath, successCallback, errorCallback, isDirectory) { + if (!idb_.db) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + var tx = this.db.transaction([FILE_STORE_], 'readwrite'); + tx.oncomplete = successCallback; + tx.onabort = errorCallback || onError; + tx.oncomplete = function () { + if (isDirectory) { + // We delete nested files and folders after deleting parent folder + // We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange + fullPath = fullPath + DIR_SEPARATOR; + + // Range contains all entries in the form fullPath where + // symbol in the range from ' ' to symbol which has code `unicodeLastChar` + var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar)); + + var newTx = this.db.transaction([FILE_STORE_], 'readwrite'); + newTx.oncomplete = successCallback; + newTx.onabort = errorCallback || onError; + newTx.objectStore(FILE_STORE_)['delete'](range); + } else { + successCallback(); + } + }; + tx.objectStore(FILE_STORE_)['delete'](fullPath); + }; + + idb_.put = function (entry, storagePath, successCallback, errorCallback, retry) { + if (!this.db) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; + } + + var tx = this.db.transaction([FILE_STORE_], 'readwrite'); + tx.onabort = errorCallback || onError; + tx.oncomplete = function () { + // TODO: Error is thrown if we pass the request event back instead. + successCallback(entry); + }; + + try { + tx.objectStore(FILE_STORE_).put(entry, storagePath); + } catch (e) { + if (e.name === 'DataCloneError') { + tx.oncomplete = null; + /* + Safari private browse mode cannot store Blob object to indexeddb. + Then use pure json object instead of Blob object. + */ + + var successCallback2 = function (entry) { + entry.file_ = new MyFile(entry.file_json.opt); + delete entry.file_json; + successCallback(entry); + }; + + if (!retry) { + if (entry.file_ && entry.file_ instanceof MyFile && entry.file_.blob_) { + MyFileHelper.toJson(entry.file_, function (json) { + entry.file_json = json; + delete entry.file_; + idb_.put(entry, storagePath, successCallback2, errorCallback, true); + }); + return; + } + } + } + throw e; + } + }; + + // Global error handler. Errors bubble from request, to transaction, to db. + function onError (e) { + switch (e.target.errorCode) { + case 12: + console.log('Error - Attempt to open db with a lower version than the ' + + 'current one.'); + break; + default: + console.log('errorCode: ' + e.target.errorCode); + } + + console.log(e, e.code, e.message); + } + + })(module.exports, window); + + require('cordova/exec/proxy').add('File', module.exports); +})(); diff --git a/www/electron/FileSystem.js b/www/electron/FileSystem.js new file mode 100644 index 000000000..a7c1d6ca7 --- /dev/null +++ b/www/electron/FileSystem.js @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/* global FILESYSTEM_PREFIX: true, module */ + +FILESYSTEM_PREFIX = 'file:///'; + +module.exports = { + __format__: function (fullPath) { + return (FILESYSTEM_PREFIX + this.name + (fullPath[0] === '/' ? '' : '/') + FileSystem.encodeURIPath(fullPath)); // eslint-disable-line no-undef + } +}; From 959d382f5cc39adfcb854506b1fc3e8d8e73d6c8 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 15:01:43 +1000 Subject: [PATCH 02/46] Electron paths --- src/electron/FileProxy.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index a2f99d9c2..7a2a220de 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -68,14 +68,13 @@ var DIR_SEPARATOR = '/'; + // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname var pathsPrefix = { - // Read-only directory where the application is installed. - applicationDirectory: location.origin + '/', // eslint-disable-line no-undef - // Where to put app-specific data files. - dataDirectory: 'file:///persistent/', - // Cached files that should survive app restarts. - // Apps should not rely on the OS to delete files in here. - cacheDirectory: 'file:///temporary/' + applicationDirectory: app.getAppPath(), // eslint-disable-line no-undef + dataDirectory: app.getPath('userData'), // eslint-disable-line no-undef + cacheDirectory: app.getPath('cache'), // eslint-disable-line no-undef + tempDirectory: app.getPath('temp'), // eslint-disable-line no-undef + documentsDirectory: app.getPath('documents'), // eslint-disable-line no-undef }; var unicodeLastChar = 65535; From 0f9183efbbe969de09daa408a41962b81d66c5b6 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 15:13:28 +1000 Subject: [PATCH 03/46] Remove chrome check for electron --- plugin.xml | 2 +- src/electron/FileProxy.js | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/plugin.xml b/plugin.xml index 44be39173..6d9c53352 100644 --- a/plugin.xml +++ b/plugin.xml @@ -260,7 +260,7 @@ to config.xml in order for the application to find previously stored files. - + diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 7a2a220de..251553d1d 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -24,29 +24,6 @@ /* global FileReader */ /* global atob, btoa, Blob */ - /* Heavily based on https://github.com/ebidel/idb.filesystem.js */ - - // For chrome we don't need to implement proxy methods - // All functionality can be accessed natively. - if (require('./isChrome')()) { - var pathsPrefix = { - // Read-only directory where the application is installed. - applicationDirectory: location.origin + '/', // eslint-disable-line no-undef - // Where to put app-specific data files. - dataDirectory: 'filesystem:file:///persistent/', - // Cached files that should survive app restarts. - // Apps should not rely on the OS to delete files in here. - cacheDirectory: 'filesystem:file:///temporary/' - }; - - exports.requestAllPaths = function (successCallback) { - successCallback(pathsPrefix); - }; - - require('cordova/exec/proxy').add('File', module.exports); - return; - } - var LocalFileSystem = require('./LocalFileSystem'); var FileSystem = require('./FileSystem'); var FileEntry = require('./FileEntry'); From 540cfaa3daa7c6ceea76d248adde12771988e8f3 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 15:31:08 +1000 Subject: [PATCH 04/46] Fix electron paths --- plugin.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugin.xml b/plugin.xml index 6d9c53352..c17288047 100644 --- a/plugin.xml +++ b/plugin.xml @@ -260,7 +260,7 @@ to config.xml in order for the application to find previously stored files. - + @@ -268,10 +268,6 @@ to config.xml in order for the application to find previously stored files. - - - - From 75423fa956c345e9c4c1aa4347825c315e0cc971 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 16:09:12 +1000 Subject: [PATCH 05/46] Try to load electron via require --- plugin.xml | 4 ++++ src/electron/FileProxy.js | 1 + 2 files changed, 5 insertions(+) diff --git a/plugin.xml b/plugin.xml index c17288047..86ee98704 100644 --- a/plugin.xml +++ b/plugin.xml @@ -260,6 +260,10 @@ to config.xml in order for the application to find previously stored files. + + + + diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 251553d1d..ed699bd59 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -24,6 +24,7 @@ /* global FileReader */ /* global atob, btoa, Blob */ + var app = require('electron').app; var LocalFileSystem = require('./LocalFileSystem'); var FileSystem = require('./FileSystem'); var FileEntry = require('./FileEntry'); From 57774763df515332cd695050dcba255f50bd2944 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 16:33:56 +1000 Subject: [PATCH 06/46] Fix lint errors --- src/electron/FileProxy.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index ed699bd59..7ba0896ef 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -48,11 +48,11 @@ // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname var pathsPrefix = { - applicationDirectory: app.getAppPath(), // eslint-disable-line no-undef - dataDirectory: app.getPath('userData'), // eslint-disable-line no-undef - cacheDirectory: app.getPath('cache'), // eslint-disable-line no-undef - tempDirectory: app.getPath('temp'), // eslint-disable-line no-undef - documentsDirectory: app.getPath('documents'), // eslint-disable-line no-undef + applicationDirectory: app.getAppPath(), + dataDirectory: app.getPath('userData'), + cacheDirectory: app.getPath('cache'), + tempDirectory: app.getPath('temp'), + documentsDirectory: app.getPath('documents') }; var unicodeLastChar = 65535; @@ -888,7 +888,7 @@ storagePath = storagePath.substring(0, storagePath.length - 1); } - var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ', + var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ', // eslint-disable-line no-undef storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar)); var tx = this.db.transaction([FILE_STORE_], 'readonly'); @@ -956,7 +956,7 @@ // Range contains all entries in the form fullPath where // symbol in the range from ' ' to symbol which has code `unicodeLastChar` - var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar)); + var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar)); // eslint-disable-line no-undef var newTx = this.db.transaction([FILE_STORE_], 'readwrite'); newTx.oncomplete = successCallback; From c32ab7b27a6de113455df76900865a6558252aa8 Mon Sep 17 00:00:00 2001 From: zorn Date: Sun, 19 Jan 2020 19:16:44 +1000 Subject: [PATCH 07/46] Try to req electron app --- src/electron/FileProxy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 7ba0896ef..f36919e58 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -24,7 +24,8 @@ /* global FileReader */ /* global atob, btoa, Blob */ - var app = require('electron').app; + console.log(global.require('electron')); + var app = global.require('electron').app; var LocalFileSystem = require('./LocalFileSystem'); var FileSystem = require('./FileSystem'); var FileEntry = require('./FileEntry'); From 99bdb0e5f6ca465128b65a8d0e11d844dc3c7649 Mon Sep 17 00:00:00 2001 From: zorn-v Date: Wed, 22 Jan 2020 14:51:09 +1000 Subject: [PATCH 08/46] Electron data in package.json --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 56df32f4a..d218a18d6 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "platforms": [ "android", "browser", + "electron", "ios", "osx", "windows" @@ -26,6 +27,7 @@ "ecosystem:cordova", "cordova-android", "cordova-browser", + "cordova-electron", "cordova-ios", "cordova-osx", "cordova-windows" From bb2849a1335ebfac0ceea5b940158ea6dca4643b Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 22 Jan 2020 20:30:31 +1000 Subject: [PATCH 09/46] console.error if node integration is disabled --- src/electron/FileProxy.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index f36919e58..0a5369579 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -24,7 +24,15 @@ /* global FileReader */ /* global atob, btoa, Blob */ - console.log(global.require('electron')); + if (window.require === undefined) { + console.error( + 'Electron Node.js integration is disabled, you can not use cordova-file-plugin without it\n'+ + 'Check docs how to enable Node.js integration: https://cordova.apache.org/docs/en/latest/guide/platforms/electron/#quick-start' + ); + return; + } + + console.log(window.require('fs')); var app = global.require('electron').app; var LocalFileSystem = require('./LocalFileSystem'); var FileSystem = require('./FileSystem'); From 11ff7443d194791536e0ebe22ed2a6025949d0c9 Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 22 Jan 2020 20:53:33 +1000 Subject: [PATCH 10/46] Set node integration after plugin install --- plugin.xml | 4 +--- scripts/electron/addNodeIntegration.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 scripts/electron/addNodeIntegration.js diff --git a/plugin.xml b/plugin.xml index 86ee98704..101905e4e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -260,9 +260,7 @@ to config.xml in order for the application to find previously stored files. - - - + diff --git a/scripts/electron/addNodeIntegration.js b/scripts/electron/addNodeIntegration.js new file mode 100644 index 000000000..6b2e660f4 --- /dev/null +++ b/scripts/electron/addNodeIntegration.js @@ -0,0 +1,10 @@ +const fs = require('fs'); + +module.exports = ctx => { + const cfgPath = ctx.opts.projectRoot + '/platforms/electron/platform_www/cdv-electron-settings.json'; + const cfg = require(cfgPath); + cfg.browserWindow = cfg.browserWindow || {}; + cfg.browserWindow.webPreferences = cfg.browserWindow.webPreferences || {}; + cfg.browserWindow.webPreferences.nodeIntegration = true; + fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 4), 'utf8'); +} From 0d6513c3f5d8151fe76cef2689aaa4ea3e956015 Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 22 Jan 2020 21:07:08 +1000 Subject: [PATCH 11/46] Fix lint error --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 0a5369579..9c5b060dd 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -26,7 +26,7 @@ if (window.require === undefined) { console.error( - 'Electron Node.js integration is disabled, you can not use cordova-file-plugin without it\n'+ + 'Electron Node.js integration is disabled, you can not use cordova-file-plugin without it\n' + 'Check docs how to enable Node.js integration: https://cordova.apache.org/docs/en/latest/guide/platforms/electron/#quick-start' ); return; From 9f3cffa2729aae401df88f0bccd5e068d7169df5 Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 22 Jan 2020 22:45:29 +1000 Subject: [PATCH 12/46] Resolve correct system paths --- src/electron/FileProxy.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 9c5b060dd..7cff7a6f8 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -32,14 +32,15 @@ return; } - console.log(window.require('fs')); - var app = global.require('electron').app; - var LocalFileSystem = require('./LocalFileSystem'); - var FileSystem = require('./FileSystem'); - var FileEntry = require('./FileEntry'); - var FileError = require('./FileError'); - var DirectoryEntry = require('./DirectoryEntry'); - var File = require('./File'); + const fs = window.require('fs'); + const app = window.require('electron').remote.app; + + const LocalFileSystem = require('./LocalFileSystem'); + const FileSystem = require('./FileSystem'); + const FileEntry = require('./FileEntry'); + const FileError = require('./FileError'); + const DirectoryEntry = require('./DirectoryEntry'); + const File = require('./File'); (function (exports, global) { var indexedDB = global.indexedDB || global.mozIndexedDB; @@ -56,7 +57,7 @@ var DIR_SEPARATOR = '/'; // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname - var pathsPrefix = { + const pathsPrefix = { applicationDirectory: app.getAppPath(), dataDirectory: app.getPath('userData'), cacheDirectory: app.getPath('cache'), From 50a2de6793899199036a130154bef37f163ba384 Mon Sep 17 00:00:00 2001 From: zorn-v Date: Thu, 23 Jan 2020 18:50:28 +1000 Subject: [PATCH 13/46] resolveLocalFileSystemURI draft --- src/electron/FileProxy.js | 107 ++++++-------------------------------- 1 file changed, 17 insertions(+), 90 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 7cff7a6f8..1818c8b6e 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -499,25 +499,13 @@ }; exports.resolveLocalFileSystemURI = function (successCallback, errorCallback, args) { - var path = args[0]; - - // Ignore parameters - if (path.indexOf('?') !== -1) { - path = String(path).split('?')[0]; - } + const path = args[0]; // support for encodeURI if (/\%5/g.test(path) || /\%20/g.test(path)) { // eslint-disable-line no-useless-escape path = decodeURI(path); } - if (path.trim()[0] === '/') { - if (errorCallback) { - errorCallback(FileError.ENCODING_ERR); - } - return; - } - // support for cdvfile if (path.trim().substr(0, 7) === 'cdvfile') { if (path.indexOf('cdvfile://localhost') === -1) { @@ -532,9 +520,9 @@ // cdvfile://localhost/persistent/path/to/file if (indexPersistent !== -1) { - path = 'file:///persistent' + path.substr(indexPersistent + 10); + path = pathsPrefix.dataDirectory + path.substr(indexPersistent + 10); } else if (indexTemporary !== -1) { - path = 'file:///temporary' + path.substr(indexTemporary + 9); + path = pathsPrefix.tempDirectory + path.substr(indexTemporary + 9); } else { if (errorCallback) { errorCallback(FileError.ENCODING_ERR); @@ -543,89 +531,28 @@ } } - // to avoid path form of '///path/to/file' - function handlePathSlashes (path) { - var cutIndex = 0; - for (var i = 0; i < path.length - 1; i++) { - if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) { - cutIndex = i + 1; - } else break; - } - - return path.substr(cutIndex); - } - - // Handle localhost containing paths (see specs ) - if (path.indexOf('file://localhost/') === 0) { - path = path.replace('file://localhost/', 'file:///'); - } - + let fsName = 'unknown'; if (path.indexOf(pathsPrefix.dataDirectory) === 0) { - path = path.substring(pathsPrefix.dataDirectory.length - 1); - path = handlePathSlashes(path); - - exports.requestFileSystem(function () { - exports.getFile(successCallback, function () { - exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path, - {create: false}]); - }, [pathsPrefix.dataDirectory, path, {create: false}]); - }, errorCallback, [LocalFileSystem.PERSISTENT]); - } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) { - path = path.substring(pathsPrefix.cacheDirectory.length - 1); - path = handlePathSlashes(path); - - exports.requestFileSystem(function () { - exports.getFile(successCallback, function () { - exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path, - {create: false}]); - }, [pathsPrefix.cacheDirectory, path, {create: false}]); - }, errorCallback, [LocalFileSystem.TEMPORARY]); - } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) { - path = path.substring(pathsPrefix.applicationDirectory.length); - // TODO: need to cut out redundant slashes? - - var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef - xhr.open('GET', path, true); - xhr.onreadystatechange = function () { - if (xhr.status === 200 && xhr.readyState === 4) { - exports.requestFileSystem(function (fs) { - fs.name = location.hostname; // eslint-disable-line no-undef - - // TODO: need to call exports.getFile(...) to handle errors correct - fs.root.getFile(path, {create: true}, writeFile, errorCallback); - }, errorCallback, [LocalFileSystem.PERSISTENT]); - } - }; - - xhr.onerror = function () { - if (errorCallback) { - errorCallback(FileError.NOT_READABLE_ERR); - } - }; - - xhr.send(); + fsName = 'persistent'; + } else if (path.indexOf(pathsPrefix.tempDirectory) === 0) { + fsName = 'temporary'; } else { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } + return; } - function writeFile (entry) { - entry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function (evt) { - if (!evt.target.error) { - entry.filesystemName = location.hostname; // eslint-disable-line no-undef - successCallback(entry); - } - }; - fileWriter.onerror = function () { - if (errorCallback) { - errorCallback(FileError.NOT_READABLE_ERR); - } - }; - fileWriter.write(new Blob([xhr.response])); // eslint-disable-line no-undef - }, errorCallback); // eslint-disable-line no-undef + if (!fs.existsSync(path) && !fs.mkdirSync(path, {recursive: true})) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + return; } + + const nodePath = window.require('path'); + const root = new DirectoryEntry(nodePath.basename(path), path) + successCallback(new FileSystem(fsName, root)); }; exports.requestAllPaths = function (successCallback) { From a2f822bb00e60afed1f8b34803c7076d78c83837 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 23 Jan 2020 20:06:57 +1000 Subject: [PATCH 14/46] resolveLocalFileSystemURI --- src/electron/FileProxy.js | 54 ++++++++------------------------------- 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 1818c8b6e..4c610ce7b 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -69,35 +69,6 @@ /** * Exported functionality ***/ - exports.requestFileSystem = function (successCallback, errorCallback, args) { - var type = args[0]; - // Size is ignored since IDB filesystem size depends - // on browser implementation and can't be set up by user - var size = args[1]; // eslint-disable-line no-unused-vars - - if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - return; - } - - var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent'; - var storageName = (location.protocol + location.host).replace(/:/g, '_'); // eslint-disable-line no-undef - - var root = new DirectoryEntry('', DIR_SEPARATOR); - fs_ = new FileSystem(name, root); - - idb_.open(storageName, function () { - successCallback(fs_); - }, errorCallback); - }; - - // Overridden by Android, BlackBerry 10 and iOS to populate fsMap - require('./fileSystems').getFs = function (name, callback) { - callback(new FileSystem(name, fs_.root)); - }; - // list a directory's contents (files and folders). exports.readEntries = function (successCallback, errorCallback, args) { var fullPath = args[0]; @@ -499,7 +470,7 @@ }; exports.resolveLocalFileSystemURI = function (successCallback, errorCallback, args) { - const path = args[0]; + let path = args[0]; // support for encodeURI if (/\%5/g.test(path) || /\%20/g.test(path)) { // eslint-disable-line no-useless-escape @@ -531,28 +502,23 @@ } } - let fsName = 'unknown'; - if (path.indexOf(pathsPrefix.dataDirectory) === 0) { - fsName = 'persistent'; - } else if (path.indexOf(pathsPrefix.tempDirectory) === 0) { - fsName = 'temporary'; - } else { - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); - } - return; + if (path.indexOf(pathsPrefix.dataDirectory) === 0 && !fs.existsSync(pathsPrefix.dataDirectory)) { + fs.mkdirSync(pathsPrefix.dataDirectory, {recursive: true}); } - if (!fs.existsSync(path) && !fs.mkdirSync(path, {recursive: true})) { + if (!fs.existsSync(path)) { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } return; } - const nodePath = window.require('path'); - const root = new DirectoryEntry(nodePath.basename(path), path) - successCallback(new FileSystem(fsName, root)); + const baseName = window.require('path').basename(path); + if (fs.statSync(path).isDirectory()) { + successCallback(new DirectoryEntry(baseName, path)); + } else { + successCallback(new FileEntry(baseName, path)); + } }; exports.requestAllPaths = function (successCallback) { From fabf8b80e80b562ef415984ff59da2936d86ce47 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 23 Jan 2020 21:23:43 +1000 Subject: [PATCH 15/46] getDirectory --- src/electron/FileProxy.js | 111 +++++++++++++++----------------------- 1 file changed, 44 insertions(+), 67 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 4c610ce7b..2db30f3cf 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -305,81 +305,58 @@ }; exports.getDirectory = function (successCallback, errorCallback, args) { - var fullPath = args[0]; - var path = args[1]; - var options = args[2]; - - // Create an absolute path if we were handed a relative one. - path = resolveToFullPath_(fullPath, path); - - idb_.get(path.storagePath, function (folderEntry) { - if (!options) { - options = {}; - } + const path = args[0] + args[1]; + const options = args[2] || {}; + const exists = fs.existsSync(path); + const baseName = window.require('path').basename(path); - if (options.create === true && options.exclusive === true && folderEntry) { - // If create and exclusive are both true, and the path already exists, - // getDirectory must fail. - if (errorCallback) { - errorCallback(FileError.PATH_EXISTS_ERR); - } - // There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's - // so we are shielding from it with returns. - return; + if (options.create === true && options.exclusive === true && exists) { + // If create and exclusive are both true, and the path already exists, + // getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); } - - if (options.create === true && !folderEntry) { - // If create is true, the path doesn't exist, and no other error occurs, - // getDirectory must create it as a zero-length file and return a corresponding - // MyDirectoryEntry. - var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); - - idb_.put(dirEntry, path.storagePath, successCallback, errorCallback); - return; + return; + } + if (options.create === true && !exists) { + // If create is true, the path doesn't exist, and no other error occurs, + // getDirectory must create it as a zero-length file and return a corresponding + // MyDirectoryEntry. + fs.mkdir(path, err => { + if (err) throw err; + successCallback(new DirectoryEntry(baseName, path)); + }) + return; + } + if (options.create === true && exists) { + if (fs.statSync(path).isDirectory()) { + successCallback(new DirectoryEntry(baseName, path)); + } else if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); } - - if (options.create === true && folderEntry) { - - if (folderEntry.isDirectory) { - // IDB won't save methods, so we need re-create the MyDirectoryEntry. - successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem)); - } else { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - } - return; + return; + } + if (!options.create && !exists) { + // If create is not true and the path doesn't exist, getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); } - if ((!options.create || options.create === false) && !folderEntry) { - // Handle root special. It should always exist. - if (path.fullPath === DIR_SEPARATOR) { - successCallback(fs_.root); - return; - } - - // If create is not true and the path doesn't exist, getDirectory must fail. - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); - } - - return; - } - if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) { - // If create is not true and the path exists, but is a file, getDirectory - // must fail. - if (errorCallback) { - errorCallback(FileError.TYPE_MISMATCH_ERR); - } - return; + return; + } + if (!options.create && exists && fs.statSync(path).isFile()) { + // If create is not true and the path exists, but is a file, getDirectory + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); } + return; + } - // Otherwise, if no other error occurs, getDirectory must return a - // MyDirectoryEntry corresponding to path. + // Otherwise, if no other error occurs, getDirectory must return a + // DirectoryEntry corresponding to path. + successCallback(new DirectoryEntry(baseName, path)); - // IDB won't' save methods, so we need re-create MyDirectoryEntry. - successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem)); - }, errorCallback); }; exports.getParent = function (successCallback, errorCallback, args) { From efd2b0e22559dabbfeecf99a796a2d501be8b3fc Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 23 Jan 2020 23:26:28 +1000 Subject: [PATCH 16/46] getFile --- src/electron/FileProxy.js | 152 ++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 82 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 2db30f3cf..aae4a6bcf 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -91,74 +91,68 @@ }; exports.getFile = function (successCallback, errorCallback, args) { - var fullPath = args[0]; - var path = args[1]; - var options = args[2] || {}; - - // Create an absolute path if we were handed a relative one. - path = resolveToFullPath_(fullPath, path); - - idb_.get(path.storagePath, function (fileEntry) { - if (options.create === true && options.exclusive === true && fileEntry) { - // If create and exclusive are both true, and the path already exists, - // getFile must fail. - - if (errorCallback) { - errorCallback(FileError.PATH_EXISTS_ERR); - } - } else if (options.create === true && !fileEntry) { - // If create is true, the path doesn't exist, and no other error occurs, - // getFile must create it as a zero-length file and return a corresponding - // FileEntry. - var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); - - newFileEntry.file_ = new MyFile({ - size: 0, - name: newFileEntry.name, - lastModifiedDate: new Date(), - storagePath: path.storagePath - }); - - idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback); - } else if (options.create === true && fileEntry) { - if (fileEntry.isFile) { - // Overwrite file, delete then create new. - idb_['delete'](path.storagePath, function () { - var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); - - newFileEntry.file_ = new MyFile({ - size: 0, - name: newFileEntry.name, - lastModifiedDate: new Date(), - storagePath: path.storagePath - }); + const path = args[0] + args[1]; + const options = args[2] || {}; + const exists = fs.existsSync(path); + const baseName = window.require('path').basename(path); - idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback); - }, errorCallback); - } else { + function createFile() { + fs.open(path, 'w', (err, fd) => { + if (err) { if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); + errorCallback(FileError.INVALID_STATE_ERR, err); } + return; } - } else if ((!options.create || options.create === false) && !fileEntry) { - // If create is not true and the path doesn't exist, getFile must fail. - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); - } - } else if ((!options.create || options.create === false) && fileEntry && - fileEntry.isDirectory) { - // If create is not true and the path exists, but is a directory, getFile - // must fail. + fs.close(fd, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.INVALID_STATE_ERR, err); + } + return; + } + successCallback(new FileEntry(baseName, path)); + }); + }) + } + + if (options.create === true && options.exclusive === true && exists) { + // If create and exclusive are both true, and the path already exists, + // getFile must fail. + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + } else if (options.create === true && !exists) { + // If create is true, the path doesn't exist, and no other error occurs, + // getFile must create it as a zero-length file and return a corresponding + // FileEntry. + createFile(); + } else if (options.create === true && exists) { + if (fs.statSync(path).isFile()) { + // Overwrite file, delete then create new. + createFile(); + } else { if (errorCallback) { - errorCallback(FileError.TYPE_MISMATCH_ERR); + errorCallback(FileError.INVALID_MODIFICATION_ERR); } - } else { - // Otherwise, if no other error occurs, getFile must return a FileEntry - // corresponding to path. - - successCallback(fileEntryFromIdbEntry(fileEntry)); } - }, errorCallback); + } else if (!options.create && !exists) { + // If create is not true and the path doesn't exist, getFile must fail. + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + } else if (!options.create && exists && fs.statSync(path).isDirectory()) { + // If create is not true and the path exists, but is a directory, getFile + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); + } + } else { + // Otherwise, if no other error occurs, getFile must return a FileEntry + // corresponding to path. + + successCallback(new FileEntry(baseName, path)); + } }; exports.getFileMetadata = function (successCallback, errorCallback, args) { @@ -316,47 +310,41 @@ if (errorCallback) { errorCallback(FileError.PATH_EXISTS_ERR); } - return; - } - if (options.create === true && !exists) { + } else if (options.create === true && !exists) { // If create is true, the path doesn't exist, and no other error occurs, // getDirectory must create it as a zero-length file and return a corresponding // MyDirectoryEntry. - fs.mkdir(path, err => { - if (err) throw err; + fs.mkdir(path, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + return; + } successCallback(new DirectoryEntry(baseName, path)); }) - return; - } - if (options.create === true && exists) { + } else if (options.create === true && exists) { if (fs.statSync(path).isDirectory()) { successCallback(new DirectoryEntry(baseName, path)); } else if (errorCallback) { errorCallback(FileError.INVALID_MODIFICATION_ERR); } - return; - } - if (!options.create && !exists) { + } else if (!options.create && !exists) { // If create is not true and the path doesn't exist, getDirectory must fail. if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } - - return; - } - if (!options.create && exists && fs.statSync(path).isFile()) { + } else if (!options.create && exists && fs.statSync(path).isFile()) { // If create is not true and the path exists, but is a file, getDirectory // must fail. if (errorCallback) { errorCallback(FileError.TYPE_MISMATCH_ERR); } - return; + } else { + // Otherwise, if no other error occurs, getDirectory must return a + // DirectoryEntry corresponding to path. + successCallback(new DirectoryEntry(baseName, path)); } - - // Otherwise, if no other error occurs, getDirectory must return a - // DirectoryEntry corresponding to path. - successCallback(new DirectoryEntry(baseName, path)); - }; exports.getParent = function (successCallback, errorCallback, args) { From e33f72188503f53a88e584bf9337f5b315fa2d8d Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 23 Jan 2020 23:50:28 +1000 Subject: [PATCH 17/46] getFileMetadata + getMetadata --- src/electron/FileProxy.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index aae4a6bcf..6ea1e2451 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -150,28 +150,33 @@ } else { // Otherwise, if no other error occurs, getFile must return a FileEntry // corresponding to path. - successCallback(new FileEntry(baseName, path)); } }; exports.getFileMetadata = function (successCallback, errorCallback, args) { var fullPath = args[0]; - - exports.getFile(function (fileEntry) { - successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate, - fileEntry.file_.size)); - }, errorCallback, [fullPath, null]); + fs.stat(fullPath, (err, stats) => { + if (err) { + errorCallback(FileError.NOT_FOUND_ERR); + return; + } + const baseName = window.require('path').basename(path); + successCallback(new File(baseName, fullPath, '', stats.mtime, stats.size)); + }); }; exports.getMetadata = function (successCallback, errorCallback, args) { - exports.getFile(function (fileEntry) { - successCallback( - { - modificationTime: fileEntry.file_.lastModifiedDate, - size: fileEntry.file_.lastModifiedDate - }); - }, errorCallback, args); + fs.stat(args[0], (err, stats) => { + if (err) { + errorCallback(FileError.NOT_FOUND_ERR); + return; + } + successCallback({ + modificationTime: stats.mtime, + size: stats.size + }); + }); }; exports.setMetadata = function (successCallback, errorCallback, args) { From b4b22da612bbc39bc715c5c6404a65558920834a Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 23 Jan 2020 23:52:19 +1000 Subject: [PATCH 18/46] getFileMetadata fix --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 6ea1e2451..685d6e1f7 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -161,7 +161,7 @@ errorCallback(FileError.NOT_FOUND_ERR); return; } - const baseName = window.require('path').basename(path); + const baseName = window.require('path').basename(fullPath); successCallback(new File(baseName, fullPath, '', stats.mtime, stats.size)); }); }; From 665fa87a3acf728ee03e6e82ea257ba0a17630b2 Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 01:06:23 +1000 Subject: [PATCH 19/46] write --- src/electron/FileProxy.js | 63 +++++++++++++-------------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 685d6e1f7..bb8b2d5ca 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -100,14 +100,14 @@ fs.open(path, 'w', (err, fd) => { if (err) { if (errorCallback) { - errorCallback(FileError.INVALID_STATE_ERR, err); + errorCallback(FileError.INVALID_STATE_ERR); } return; } fs.close(fd, (err) => { if (err) { if (errorCallback) { - errorCallback(FileError.INVALID_STATE_ERR, err); + errorCallback(FileError.INVALID_STATE_ERR); } return; } @@ -155,7 +155,7 @@ }; exports.getFileMetadata = function (successCallback, errorCallback, args) { - var fullPath = args[0]; + const fullPath = args[0]; fs.stat(fullPath, (err, stats) => { if (err) { errorCallback(FileError.NOT_FOUND_ERR); @@ -190,10 +190,10 @@ }; exports.write = function (successCallback, errorCallback, args) { - var fileName = args[0]; - var data = args[1]; - var position = args[2]; - var isBinary = args[3]; // eslint-disable-line no-unused-vars + const fileName = args[0]; + const data = args[1]; + const position = args[2]; + const isBinary = args[3]; // eslint-disable-line no-unused-vars if (!data) { if (errorCallback) { @@ -202,42 +202,21 @@ return; } - if (typeof data === 'string' || data instanceof String) { - data = new Blob([data]); // eslint-disable-line no-undef - } - - exports.getFile(function (fileEntry) { - var blob_ = fileEntry.file_.blob_; - - if (!blob_) { - blob_ = new Blob([data], {type: data.type}); // eslint-disable-line no-undef - } else { - // Calc the head and tail fragments - var head = blob_.slice(0, position); - var tail = blob_.slice(position + (data.size || data.byteLength)); - - // Calc the padding - var padding = position - head.size; - if (padding < 0) { - padding = 0; + const buf = Buffer.from(data); + const promisify = window.require('util').promisify; + let bytesWritten = 0; + promisify(fs.open)(fileName, 'a') + .then(fd => { + return promisify(fs.write)(fd, buf, 0, buf.length, position) + .then(bw => bytesWritten = bw) + .then(() => promisify(fs.close)(fd)); + }) + .then(() => successCallback(bytesWritten)) + .catch(() => { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR) } - - // Do the "write". In fact, a full overwrite of the Blob. - blob_ = new Blob([head, new Uint8Array(padding), data, tail], // eslint-disable-line no-undef - {type: data.type}); - } - - // Set the blob we're writing on this file entry so we can recall it later. - fileEntry.file_.blob_ = blob_; - fileEntry.file_.lastModifiedDate = new Date() || null; - fileEntry.file_.size = blob_.size; - fileEntry.file_.name = blob_.name; - fileEntry.file_.type = blob_.type; - - idb_.put(fileEntry, fileEntry.file_.storagePath, function () { - successCallback(data.size || data.byteLength); - }, errorCallback); - }, errorCallback, [fileName, null]); + }); }; exports.readAsText = function (successCallback, errorCallback, args) { From db2d4ff3bca87876847a14355c36a8ecc620c122 Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 01:08:36 +1000 Subject: [PATCH 20/46] [write] fs.close in finally --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index bb8b2d5ca..6390b6319 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -209,7 +209,7 @@ .then(fd => { return promisify(fs.write)(fd, buf, 0, buf.length, position) .then(bw => bytesWritten = bw) - .then(() => promisify(fs.close)(fd)); + .finally(() => promisify(fs.close)(fd)); }) .then(() => successCallback(bytesWritten)) .catch(() => { From ce1671892d76be7e0eafca0f5c1ff240ac4171ec Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 01:49:24 +1000 Subject: [PATCH 21/46] Use nodeRequire const --- src/electron/FileProxy.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 6390b6319..b69415995 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -32,8 +32,9 @@ return; } - const fs = window.require('fs'); - const app = window.require('electron').remote.app; + const nodeRequire = global.require; + const fs = nodeRequire('fs'); + const app = nodeRequire('electron').remote.app; const LocalFileSystem = require('./LocalFileSystem'); const FileSystem = require('./FileSystem'); @@ -94,7 +95,7 @@ const path = args[0] + args[1]; const options = args[2] || {}; const exists = fs.existsSync(path); - const baseName = window.require('path').basename(path); + const baseName = nodeRequire('path').basename(path); function createFile() { fs.open(path, 'w', (err, fd) => { @@ -161,7 +162,7 @@ errorCallback(FileError.NOT_FOUND_ERR); return; } - const baseName = window.require('path').basename(fullPath); + const baseName = nodeRequire('path').basename(fullPath); successCallback(new File(baseName, fullPath, '', stats.mtime, stats.size)); }); }; @@ -203,7 +204,7 @@ } const buf = Buffer.from(data); - const promisify = window.require('util').promisify; + const promisify = nodeRequire('util').promisify; let bytesWritten = 0; promisify(fs.open)(fileName, 'a') .then(fd => { @@ -286,7 +287,7 @@ const path = args[0] + args[1]; const options = args[2] || {}; const exists = fs.existsSync(path); - const baseName = window.require('path').basename(path); + const baseName = nodeRequire('path').basename(path); if (options.create === true && options.exclusive === true && exists) { // If create and exclusive are both true, and the path already exists, @@ -462,7 +463,7 @@ return; } - const baseName = window.require('path').basename(path); + const baseName = nodeRequire('path').basename(path); if (fs.statSync(path).isDirectory()) { successCallback(new DirectoryEntry(baseName, path)); } else { From 52dbfc483d7c6acf93c5ad2428fdd920f8e9ceeb Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 02:46:00 +1000 Subject: [PATCH 22/46] readEntries --- src/electron/FileProxy.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index b69415995..ba2f63aeb 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -72,23 +72,32 @@ // list a directory's contents (files and folders). exports.readEntries = function (successCallback, errorCallback, args) { - var fullPath = args[0]; + const fullPath = args[0]; if (typeof successCallback !== 'function') { throw Error('Expected successCallback argument.'); } - var path = resolveToFullPath_(fullPath); - - exports.getDirectory(function () { - idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function (entries) { - successCallback(entries); - }, errorCallback); - }, function () { - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); + fs.readdir(fullPath, {withFileTypes: true}, (err, files) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + return; } - }, [path.storagePath, path.fullPath, {create: false}]); + const result = []; + files.forEach(d => { + result.push({ + isDirectory: d.isDirectory(), + isFile: d.isFile(), + name: d.name, + fullPath: fullPath + d.name, + filesystemName: 'temporary', + nativeURL: fullPath + d.name + }); + }); + successCallback(result); + }); }; exports.getFile = function (successCallback, errorCallback, args) { From 4393d95b14e2cda0651283ee0890106af02d5c0c Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 03:02:45 +1000 Subject: [PATCH 23/46] setMetadata --- src/electron/FileProxy.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index ba2f63aeb..d15c47a23 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -168,7 +168,9 @@ const fullPath = args[0]; fs.stat(fullPath, (err, stats) => { if (err) { - errorCallback(FileError.NOT_FOUND_ERR); + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } return; } const baseName = nodeRequire('path').basename(fullPath); @@ -179,7 +181,9 @@ exports.getMetadata = function (successCallback, errorCallback, args) { fs.stat(args[0], (err, stats) => { if (err) { - errorCallback(FileError.NOT_FOUND_ERR); + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } return; } successCallback({ @@ -193,10 +197,15 @@ var fullPath = args[0]; var metadataObject = args[1]; - exports.getFile(function (fileEntry) { - fileEntry.file_.lastModifiedDate = metadataObject.modificationTime; - idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback); - }, errorCallback, [fullPath, null]); + fs.utime(fullPath, metadataObject.modificationTime, metadataObject.modificationTime, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + return; + } + successCallback(); + } + }); }; exports.write = function (successCallback, errorCallback, args) { From dfd2ed2c348ceaf06ad1471732428fcd96cad04b Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 03:32:36 +1000 Subject: [PATCH 24/46] getParent --- src/electron/FileProxy.js | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index d15c47a23..ea91d6908 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -355,33 +355,10 @@ throw Error('Expected successCallback argument.'); } - var fullPath = args[0]; - // fullPath is like this: - // file:///persistent/path/to/file or - // file:///persistent/path/to/directory/ - - if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory || - fullPath === pathsPrefix.dataDirectory) { - successCallback(fs_.root); - return; - } - - // To delete all slashes at the end - while (fullPath[fullPath.length - 1] === '/') { - fullPath = fullPath.substr(0, fullPath.length - 1); - } - - var pathArr = fullPath.split(DIR_SEPARATOR); - pathArr.pop(); - var parentName = pathArr.pop(); - var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR; - - // To get parent of root files - var joined = path + parentName + DIR_SEPARATOR;// is like this: file:///persistent/ - if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) { - exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]); - return; - } + const nodePath = nodeRequire('path'); + const parentPath = nodePath.dirname(args[0]); + const parentName = nodePath.basename(parentPath); + const path = nodePath.dirname(parentPath) + nodePath.sep; exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]); }; From c2012549780062749b0d4a3a538bc6dc6eb10099 Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 04:06:29 +1000 Subject: [PATCH 25/46] copyTo --- src/electron/FileProxy.js | 41 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index ea91d6908..85528a2b8 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -364,38 +364,19 @@ }; exports.copyTo = function (successCallback, errorCallback, args) { - var srcPath = args[0]; - var parentFullPath = args[1]; - var name = args[2]; + const srcPath = args[0]; + const dstDir = args[1]; + const dstName = args[2]; - if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); + fs.copyFile(srcPath, dstDir + dstName, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + return; } - - return; - } - - // Read src file - exports.getFile(function (srcFileEntry) { - - var path = resolveToFullPath_(parentFullPath); - // Check directory - exports.getDirectory(function () { - - // Create dest file - exports.getFile(function (dstFileEntry) { - - exports.write(function () { - successCallback(dstFileEntry); - }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]); - - }, errorCallback, [parentFullPath, name, {create: true}]); - - }, function () { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } }, - [path.storagePath, null, {create: false}]); - - }, errorCallback, [srcPath, null]); + exports.getFile(successCallback, errorCallback, [dstDir, dstName]); + }); }; exports.moveTo = function (successCallback, errorCallback, args) { From 6ab97c15bc7899fddac26d7a768390964f7d1822 Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 04:39:32 +1000 Subject: [PATCH 26/46] remove --- src/electron/FileProxy.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 85528a2b8..2c5070a4a 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -271,10 +271,31 @@ readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback); }; - exports.removeRecursively = exports.remove = function (successCallback, errorCallback, args) { - if (typeof successCallback !== 'function') { - throw Error('Expected successCallback argument.'); - } + exports.remove = function (successCallback, errorCallback, args) { + const fullPath = args[0]; + + fs.stat(fullPath, (err, stats) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + return; + } + const rm = stats.isDirectory() ? fs.rmdir : fs.unlink; + rm(fullPath, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR); + } + return; + } + successCallback(); + }); + }); + }; + + exports.removeRecursively = function (successCallback, errorCallback, args) { + console.log('removeRecursively', args); var fullPath = resolveToFullPath_(args[0]).storagePath; if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) { From a7a7b89c528942cf9f0383587af655a3ac56caaa Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 05:10:22 +1000 Subject: [PATCH 27/46] removeRecursively draft --- src/electron/FileProxy.js | 48 ++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 2c5070a4a..0b90d9f04 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -33,6 +33,7 @@ } const nodeRequire = global.require; + const nodePath = nodeRequire('path'); const fs = nodeRequire('fs'); const app = nodeRequire('electron').remote.app; @@ -87,13 +88,17 @@ } const result = []; files.forEach(d => { + let path = fullPath + d.name; + if (d.isDirectory()) { + path += nodePath.sep; + } result.push({ isDirectory: d.isDirectory(), isFile: d.isFile(), name: d.name, - fullPath: fullPath + d.name, + fullPath: path, filesystemName: 'temporary', - nativeURL: fullPath + d.name + nativeURL: path }); }); successCallback(result); @@ -273,7 +278,7 @@ exports.remove = function (successCallback, errorCallback, args) { const fullPath = args[0]; - + console.log('remove', fullPath); fs.stat(fullPath, (err, stats) => { if (err) { if (errorCallback) { @@ -295,31 +300,17 @@ }; exports.removeRecursively = function (successCallback, errorCallback, args) { - console.log('removeRecursively', args); - - var fullPath = resolveToFullPath_(args[0]).storagePath; - if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) { - errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR); - return; - } - - function deleteEntry (isDirectory) { - // TODO: This doesn't protect against directories that have content in it. - // Should throw an error instead if the dirEntry is not empty. - idb_['delete'](fullPath, function () { - successCallback(); - }, function () { - if (errorCallback) { errorCallback(); } - }, isDirectory); - } - - // We need to to understand what we are deleting: - exports.getDirectory(function (entry) { - deleteEntry(entry.isDirectory); - }, function () { - // DirectoryEntry was already deleted or entry is FileEntry - deleteEntry(false); - }, [fullPath, null, {create: false}]); + const fullPath = args[0]; + console.log('removeRecursively', fullPath); + exports.readEntries((entries) => { + entries.forEach(entry => { + if (entry.isDirectory) { + exports.removeRecursively(successCallback, errorCallback, [entry.fullPath]); + } + exports.remove(successCallback, errorCallback, [fullPath]); + }); + successCallback(); + }, errorCallback, [fullPath]); }; exports.getDirectory = function (successCallback, errorCallback, args) { @@ -376,7 +367,6 @@ throw Error('Expected successCallback argument.'); } - const nodePath = nodeRequire('path'); const parentPath = nodePath.dirname(args[0]); const parentName = nodePath.basename(parentPath); const path = nodePath.dirname(parentPath) + nodePath.sep; From 5c6f44a9349b278f8fa2314e83975fed522616dc Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 05:56:09 +1000 Subject: [PATCH 28/46] buggy removeRecursively --- src/electron/FileProxy.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 0b90d9f04..7c995cb7d 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -278,7 +278,7 @@ exports.remove = function (successCallback, errorCallback, args) { const fullPath = args[0]; - console.log('remove', fullPath); + fs.stat(fullPath, (err, stats) => { if (err) { if (errorCallback) { @@ -301,15 +301,22 @@ exports.removeRecursively = function (successCallback, errorCallback, args) { const fullPath = args[0]; - console.log('removeRecursively', fullPath); + exports.readEntries((entries) => { + if (entries.length === 0) { + exports.remove(successCallback, errorCallback, [fullPath]); + } entries.forEach(entry => { if (entry.isDirectory) { - exports.removeRecursively(successCallback, errorCallback, [entry.fullPath]); + exports.removeRecursively(() => { + exports.remove(() => { + exports.remove(successCallback, errorCallback, [fullPath]); + }, errorCallback, [entry.fullPath]); + }, errorCallback, [entry.fullPath]); + } else { + exports.remove(successCallback, errorCallback, [entry.fullPath]); } - exports.remove(successCallback, errorCallback, [fullPath]); }); - successCallback(); }, errorCallback, [fullPath]); }; From ff399579060eac9379545a2a0612d4c9dc95c103 Mon Sep 17 00:00:00 2001 From: zorn Date: Fri, 24 Jan 2020 06:21:48 +1000 Subject: [PATCH 29/46] Use rimraf module for removeRecursively --- plugin.xml | 2 + src/electron/FileProxy.js | 24 ++- src/electron/rimraf.js | 331 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 15 deletions(-) create mode 100644 src/electron/rimraf.js diff --git a/plugin.xml b/plugin.xml index 101905e4e..df0b279a8 100644 --- a/plugin.xml +++ b/plugin.xml @@ -266,6 +266,8 @@ to config.xml in order for the application to find previously stored files. + + diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 7c995cb7d..3e38d7491 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -301,23 +301,17 @@ exports.removeRecursively = function (successCallback, errorCallback, args) { const fullPath = args[0]; + const rimraf = require('./rimraf'); - exports.readEntries((entries) => { - if (entries.length === 0) { - exports.remove(successCallback, errorCallback, [fullPath]); - } - entries.forEach(entry => { - if (entry.isDirectory) { - exports.removeRecursively(() => { - exports.remove(() => { - exports.remove(successCallback, errorCallback, [fullPath]); - }, errorCallback, [entry.fullPath]); - }, errorCallback, [entry.fullPath]); - } else { - exports.remove(successCallback, errorCallback, [entry.fullPath]); + rimraf(fullPath, {disableGlob: true}, err => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR); } - }); - }, errorCallback, [fullPath]); + return; + } + successCallback(); + }); }; exports.getDirectory = function (successCallback, errorCallback, args) { diff --git a/src/electron/rimraf.js b/src/electron/rimraf.js new file mode 100644 index 000000000..74584ff71 --- /dev/null +++ b/src/electron/rimraf.js @@ -0,0 +1,331 @@ +const path = global.require("path") +const fs = global.require("fs") +let glob = undefined +try { + glob = require("glob") +} catch (_err) { + // treat glob as optional. +} + +const defaultGlobOpts = { + nosort: true, + silent: true +} + +// for EMFILE handling +let timeout = 0 + +const isWindows = (process.platform === "win32") + +const defaults = options => { + const methods = [ + 'unlink', + 'chmod', + 'stat', + 'lstat', + 'rmdir', + 'readdir' + ] + methods.forEach(m => { + options[m] = options[m] || fs[m] + m = m + 'Sync' + options[m] = options[m] || fs[m] + }) + + options.maxBusyTries = options.maxBusyTries || 3 + options.emfileWait = options.emfileWait || 1000 + if (options.glob === false) { + options.disableGlob = true + } + if (options.disableGlob !== true && glob === undefined) { + throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + } + options.disableGlob = options.disableGlob || false + options.glob = options.glob || defaultGlobOpts +} + +const rimraf = (p, options, cb) => { + if (typeof options === 'function') { + cb = options + options = {} + } + + defaults(options) + + let busyTries = 0 + let errState = null + let n = 0 + + const next = (er) => { + errState = errState || er + if (--n === 0) + cb(errState) + } + + const afterGlob = (er, results) => { + if (er) + return cb(er) + + n = results.length + if (n === 0) + return cb() + + results.forEach(p => { + const CB = (er) => { + if (er) { + if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && + busyTries < options.maxBusyTries) { + busyTries ++ + // try again, with the same exact callback as this one. + return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) + } + + // this one won't happen if graceful-fs is used. + if (er.code === "EMFILE" && timeout < options.emfileWait) { + return setTimeout(() => rimraf_(p, options, CB), timeout ++) + } + + // already gone + if (er.code === "ENOENT") er = null + } + + timeout = 0 + next(er) + } + rimraf_(p, options, CB) + }) + } + + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) + + options.lstat(p, (er, stat) => { + if (!er) + return afterGlob(null, [p]) + + glob(p, options.glob, afterGlob) + }) + +} + +// Two possible strategies. +// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR +// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR +// +// Both result in an extra syscall when you guess wrong. However, there +// are likely far more normal files in the world than directories. This +// is based on the assumption that a the average number of files per +// directory is >= 1. +// +// If anyone ever complains about this, then I guess the strategy could +// be made configurable somehow. But until then, YAGNI. +const rimraf_ = (p, options, cb) => { + + // sunos lets the root user unlink directories, which is... weird. + // so we have to lstat here and make sure it's not a dir. + options.lstat(p, (er, st) => { + if (er && er.code === "ENOENT") + return cb(null) + + // Windows can EPERM on stat. Life is suffering. + if (er && er.code === "EPERM" && isWindows) + fixWinEPERM(p, options, er, cb) + + if (st && st.isDirectory()) + return rmdir(p, options, er, cb) + + options.unlink(p, er => { + if (er) { + if (er.code === "ENOENT") + return cb(null) + if (er.code === "EPERM") + return (isWindows) + ? fixWinEPERM(p, options, er, cb) + : rmdir(p, options, er, cb) + if (er.code === "EISDIR") + return rmdir(p, options, er, cb) + } + return cb(er) + }) + }) +} + +const fixWinEPERM = (p, options, er, cb) => { + + options.chmod(p, 0o666, er2 => { + if (er2) + cb(er2.code === "ENOENT" ? null : er) + else + options.stat(p, (er3, stats) => { + if (er3) + cb(er3.code === "ENOENT" ? null : er) + else if (stats.isDirectory()) + rmdir(p, options, er, cb) + else + options.unlink(p, cb) + }) + }) +} + +const fixWinEPERMSync = (p, options, er) => { + + try { + options.chmodSync(p, 0o666) + } catch (er2) { + if (er2.code === "ENOENT") + return + else + throw er + } + + let stats + try { + stats = options.statSync(p) + } catch (er3) { + if (er3.code === "ENOENT") + return + else + throw er + } + + if (stats.isDirectory()) + rmdirSync(p, options, er) + else + options.unlinkSync(p) +} + +const rmdir = (p, options, originalEr, cb) => { + + // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) + // if we guessed wrong, and it's not a directory, then + // raise the original error. + options.rmdir(p, er => { + if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) + rmkids(p, options, cb) + else if (er && er.code === "ENOTDIR") + cb(originalEr) + else + cb(er) + }) +} + +const rmkids = (p, options, cb) => { + + options.readdir(p, (er, files) => { + if (er) + return cb(er) + let n = files.length + if (n === 0) + return options.rmdir(p, cb) + let errState + files.forEach(f => { + rimraf(path.join(p, f), options, er => { + if (errState) + return + if (er) + return cb(errState = er) + if (--n === 0) + options.rmdir(p, cb) + }) + }) + }) +} + +// this looks simpler, and is strictly *faster*, but will +// tie up the JavaScript thread and fail on excessively +// deep directory trees. +const rimrafSync = (p, options) => { + options = options || {} + defaults(options) + + let results + + if (options.disableGlob || !glob.hasMagic(p)) { + results = [p] + } else { + try { + options.lstatSync(p) + results = [p] + } catch (er) { + results = glob.sync(p, options.glob) + } + } + + if (!results.length) + return + + for (let i = 0; i < results.length; i++) { + const p = results[i] + + let st + try { + st = options.lstatSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + + // Windows can EPERM on stat. Life is suffering. + if (er.code === "EPERM" && isWindows) + fixWinEPERMSync(p, options, er) + } + + try { + // sunos lets the root user unlink directories, which is... weird. + if (st && st.isDirectory()) + rmdirSync(p, options, null) + else + options.unlinkSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "EPERM") + return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) + if (er.code !== "EISDIR") + throw er + + rmdirSync(p, options, er) + } + } +} + +const rmdirSync = (p, options, originalEr) => { + + try { + options.rmdirSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "ENOTDIR") + throw originalEr + if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") + rmkidsSync(p, options) + } +} + +const rmkidsSync = (p, options) => { + + options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) + + // We only end up here once we got ENOTEMPTY at least once, and + // at this point, we are guaranteed to have removed all the kids. + // So, we know that it won't be ENOENT or ENOTDIR or anything else. + // try really hard to delete stuff on windows, because it has a + // PROFOUNDLY annoying habit of not closing handles promptly when + // files are deleted, resulting in spurious ENOTEMPTY errors. + const retries = isWindows ? 100 : 1 + let i = 0 + do { + let threw = true + try { + const ret = options.rmdirSync(p, options) + threw = false + return ret + } finally { + if (++i < retries && threw) + continue + } + } while (true) +} + +module.exports = rimraf +rimraf.sync = rimrafSync From 4000756d8e97fe3f628003ad416e53e01920f4ab Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 11:54:30 +1000 Subject: [PATCH 30/46] Add trailing slash to pathsPrefix --- src/electron/FileProxy.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 3e38d7491..3b422a96d 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -60,11 +60,11 @@ // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname const pathsPrefix = { - applicationDirectory: app.getAppPath(), - dataDirectory: app.getPath('userData'), - cacheDirectory: app.getPath('cache'), - tempDirectory: app.getPath('temp'), - documentsDirectory: app.getPath('documents') + applicationDirectory: app.getAppPath() + nodePath.sep, + dataDirectory: app.getPath('userData') + nodePath.sep, + cacheDirectory: app.getPath('cache') + nodePath.sep, + tempDirectory: app.getPath('temp') + nodePath.sep, + documentsDirectory: app.getPath('documents') + nodePath.sep }; var unicodeLastChar = 65535; From 0fb00c4d635f76f275f2d961895e923c4147f36f Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 18:19:43 +1000 Subject: [PATCH 31/46] readAs --- src/electron/FileProxy.js | 56 ++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 3b422a96d..63fb76f38 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -621,32 +621,40 @@ } function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) { - exports.getFile(function (fileEntry) { - var fileReader = new FileReader(); // eslint-disable-line no-undef - var blob = fileEntry.file_.blob_.slice(startPos, endPos); + const promisify = nodeRequire('util').promisify - fileReader.onload = function (e) { - successCallback(e.target.result); - }; - - fileReader.onerror = errorCallback; - - switch (what) { - case 'text': - fileReader.readAsText(blob, encoding); - break; - case 'dataURL': - fileReader.readAsDataURL(blob); - break; - case 'arrayBuffer': - fileReader.readAsArrayBuffer(blob); - break; - case 'binaryString': - fileReader.readAsBinaryString(blob); - break; + fs.open(fullPath, 'r', (err, fd) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + return; } - - }, errorCallback, [fullPath, null]); + const buf = Buffer.alloc(endPos - startPos); + promisify(fs.read)(fd, buf, 0, buf.length, startPos) + .then(() => { + switch (what) { + case 'text': + successCallback(buf.toString(encoding)); + break; + case 'dataURL': + successCallback('data:;base64,' + buf.toString('base64')); + break; + case 'arrayBuffer': + successCallback(buf); + break; + case 'binaryString': + successCallback(buf.toString('binary')); + break; + } + }) + .catch(() => promisify(fs.close)(fd)) + .catch(() => { + if (errorCallback) { + errorCallback(FileError.NOT_READABLE_ERR); + } + }); + }); } /** * Core logic to handle IDB operations ***/ From 92f45a661dc2839f4aac8fc615271cce14d7f1cc Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 18:26:15 +1000 Subject: [PATCH 32/46] Cleanup --- src/electron/FileProxy.js | 393 -------------------------------------- 1 file changed, 393 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 63fb76f38..3c889484d 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -20,9 +20,6 @@ */ (function () { /* global require, exports, module */ - /* global FILESYSTEM_PREFIX */ - /* global FileReader */ - /* global atob, btoa, Blob */ if (window.require === undefined) { console.error( @@ -45,18 +42,6 @@ const File = require('./File'); (function (exports, global) { - var indexedDB = global.indexedDB || global.mozIndexedDB; - if (!indexedDB) { - throw 'Firefox OS File plugin: indexedDB not supported'; - } - - var fs_ = null; - - var idb_ = {}; - idb_.db = null; - var FILE_STORE_ = 'entries'; - - var DIR_SEPARATOR = '/'; // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname const pathsPrefix = { @@ -67,8 +52,6 @@ documentsDirectory: app.getPath('documents') + nodePath.sep }; - var unicodeLastChar = 65535; - /** * Exported functionality ***/ // list a directory's contents (files and folders). @@ -465,161 +448,6 @@ /** * Helpers ***/ - /** - * Interface to wrap the native File interface. - * - * This interface is necessary for creating zero-length (empty) files, - * something the Filesystem API allows you to do. Unfortunately, File's - * constructor cannot be called directly, making it impossible to instantiate - * an empty File in JS. - * - * @param {Object} opts Initial values. - * @constructor - */ - function MyFile (opts) { - var blob_ = new Blob(); // eslint-disable-line no-undef - - this.size = opts.size || 0; - this.name = opts.name || ''; - this.type = opts.type || ''; - this.lastModifiedDate = opts.lastModifiedDate || null; - this.storagePath = opts.storagePath || ''; - - // Need some black magic to correct the object's size/name/type based on the - // blob that is saved. - Object.defineProperty(this, 'blob_', { - enumerable: true, - get: function () { - return blob_; - }, - set: function (val) { - blob_ = val; - this.size = blob_.size; - this.name = blob_.name; - this.type = blob_.type; - this.lastModifiedDate = blob_.lastModifiedDate; - }.bind(this) - }); - } - - MyFile.prototype.constructor = MyFile; - - var MyFileHelper = { - toJson: function (myFile, success) { - /* - Safari private browse mode cannot store Blob object to indexeddb. - Then use pure json object instead of Blob object. - */ - var fr = new FileReader(); - fr.onload = function (ev) { - var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(fr.result))); - success({ - opt: { - size: myFile.size, - name: myFile.name, - type: myFile.type, - lastModifiedDate: myFile.lastModifiedDate, - storagePath: myFile.storagePath - }, - base64: base64 - }); - }; - fr.readAsArrayBuffer(myFile.blob_); - }, - setBase64: function (myFile, base64) { - if (base64) { - var arrayBuffer = (new Uint8Array( - [].map.call(atob(base64), function (c) { return c.charCodeAt(0); }) - )).buffer; - - myFile.blob_ = new Blob([arrayBuffer], { type: myFile.type }); - } else { - myFile.blob_ = new Blob(); - } - } - }; - - // When saving an entry, the fullPath should always lead with a slash and never - // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute - // one. This method ensures path is legit! - function resolveToFullPath_ (cwdFullPath, path) { - path = path || ''; - var fullPath = path; - var prefix = ''; - - cwdFullPath = cwdFullPath || DIR_SEPARATOR; - if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) { - prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length)); - cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length)); - } - - var relativePath = path[0] !== DIR_SEPARATOR; - if (relativePath) { - fullPath = cwdFullPath; - if (cwdFullPath !== DIR_SEPARATOR) { - fullPath += DIR_SEPARATOR + path; - } else { - fullPath += path; - } - } - - // Remove doubled separator substrings - var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g'); - fullPath = fullPath.replace(re, DIR_SEPARATOR); - - // Adjust '..'s by removing parent directories when '..' flows in path. - var parts = fullPath.split(DIR_SEPARATOR); - for (var i = 0; i < parts.length; ++i) { - var part = parts[i]; - if (part === '..') { - parts[i - 1] = ''; - parts[i] = ''; - } - } - fullPath = parts.filter(function (el) { - return el; - }).join(DIR_SEPARATOR); - - // Add back in leading slash. - if (fullPath[0] !== DIR_SEPARATOR) { - fullPath = DIR_SEPARATOR + fullPath; - } - - // Replace './' by current dir. ('./one/./two' -> one/two) - fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR); - - // Replace '//' with '/'. - fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR); - - // Replace '/.' with '/'. - fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR); - - // Remove '/' if it appears on the end. - if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && - fullPath !== DIR_SEPARATOR) { - fullPath = fullPath.substring(0, fullPath.length - 1); - } - - var storagePath = prefix + fullPath; - storagePath = decodeURI(storagePath); - fullPath = decodeURI(fullPath); - - return { - storagePath: storagePath, - fullPath: fullPath, - fileName: fullPath.split(DIR_SEPARATOR).pop(), - fsName: prefix.split(DIR_SEPARATOR).pop() - }; - } - - function fileEntryFromIdbEntry (fileEntry) { - // IDB won't save methods, so we need re-create the FileEntry. - var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem); - clonedFileEntry.file_ = fileEntry.file_; - - return clonedFileEntry; - } - function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) { const promisify = nodeRequire('util').promisify @@ -657,227 +485,6 @@ }); } - /** * Core logic to handle IDB operations ***/ - - idb_.open = function (dbName, successCallback, errorCallback) { - var self = this; - - // TODO: FF 12.0a1 isn't liking a db name with : in it. - var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version */); - - request.onerror = errorCallback || onError; - - request.onupgradeneeded = function (e) { - // First open was called or higher db version was used. - - // console.log('onupgradeneeded: oldVersion:' + e.oldVersion, - // 'newVersion:' + e.newVersion); - - self.db = e.target.result; - self.db.onerror = onError; - - if (!self.db.objectStoreNames.contains(FILE_STORE_)) { - self.db.createObjectStore(FILE_STORE_/*, {keyPath: 'id', autoIncrement: true} */); - } - }; - - request.onsuccess = function (e) { - self.db = e.target.result; - self.db.onerror = onError; - successCallback(e); - }; - - request.onblocked = errorCallback || onError; - }; - - idb_.close = function () { - this.db.close(); - this.db = null; - }; - - idb_.get = function (fullPath, successCallback, errorCallback) { - if (!this.db) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - return; - } - - var tx = this.db.transaction([FILE_STORE_], 'readonly'); - - var request = tx.objectStore(FILE_STORE_).get(fullPath); - - tx.onabort = errorCallback || onError; - tx.oncomplete = function () { - var entry = request.result; - if (entry && entry.file_json) { - /* - Safari private browse mode cannot store Blob object to indexeddb. - Then use pure json object instead of Blob object. - */ - entry.file_ = new MyFile(entry.file_json.opt); - MyFileHelper.setBase64(entry.file_, entry.file_json.base64); - delete entry.file_json; - } - successCallback(entry); - }; - }; - - idb_.getAllEntries = function (fullPath, storagePath, successCallback, errorCallback) { - if (!this.db) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - return; - } - - var results = []; - - if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) { - storagePath = storagePath.substring(0, storagePath.length - 1); - } - - var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ', // eslint-disable-line no-undef - storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar)); - - var tx = this.db.transaction([FILE_STORE_], 'readonly'); - tx.onabort = errorCallback || onError; - tx.oncomplete = function () { - results = results.filter(function (val) { - var pathWithoutSlash = val.fullPath; - - if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) { - pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1); - } - - var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length; - var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length; - - /* Input fullPath parameter equals '//' for root folder */ - /* Entries in root folder has valPartsLen equals 2 (see below) */ - if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && fullPath.trim().length === 2) { - fullPathPartsLen = 1; - } else if (fullPath[fullPath.length - 1] === DIR_SEPARATOR) { - fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length; - } else { - fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length; - } - - if (valPartsLen === fullPathPartsLen + 1) { - // If this a subfolder and entry is a direct child, include it in - // the results. Otherwise, it's not an entry of this folder. - return val; - } else return false; - }); - - successCallback(results); - }; - - var request = tx.objectStore(FILE_STORE_).openCursor(range); - - request.onsuccess = function (e) { - var cursor = e.target.result; - if (cursor) { - var val = cursor.value; - - results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem)); - cursor['continue'](); - } - }; - }; - - idb_['delete'] = function (fullPath, successCallback, errorCallback, isDirectory) { - if (!idb_.db) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - return; - } - - var tx = this.db.transaction([FILE_STORE_], 'readwrite'); - tx.oncomplete = successCallback; - tx.onabort = errorCallback || onError; - tx.oncomplete = function () { - if (isDirectory) { - // We delete nested files and folders after deleting parent folder - // We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange - fullPath = fullPath + DIR_SEPARATOR; - - // Range contains all entries in the form fullPath where - // symbol in the range from ' ' to symbol which has code `unicodeLastChar` - var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar)); // eslint-disable-line no-undef - - var newTx = this.db.transaction([FILE_STORE_], 'readwrite'); - newTx.oncomplete = successCallback; - newTx.onabort = errorCallback || onError; - newTx.objectStore(FILE_STORE_)['delete'](range); - } else { - successCallback(); - } - }; - tx.objectStore(FILE_STORE_)['delete'](fullPath); - }; - - idb_.put = function (entry, storagePath, successCallback, errorCallback, retry) { - if (!this.db) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - return; - } - - var tx = this.db.transaction([FILE_STORE_], 'readwrite'); - tx.onabort = errorCallback || onError; - tx.oncomplete = function () { - // TODO: Error is thrown if we pass the request event back instead. - successCallback(entry); - }; - - try { - tx.objectStore(FILE_STORE_).put(entry, storagePath); - } catch (e) { - if (e.name === 'DataCloneError') { - tx.oncomplete = null; - /* - Safari private browse mode cannot store Blob object to indexeddb. - Then use pure json object instead of Blob object. - */ - - var successCallback2 = function (entry) { - entry.file_ = new MyFile(entry.file_json.opt); - delete entry.file_json; - successCallback(entry); - }; - - if (!retry) { - if (entry.file_ && entry.file_ instanceof MyFile && entry.file_.blob_) { - MyFileHelper.toJson(entry.file_, function (json) { - entry.file_json = json; - delete entry.file_; - idb_.put(entry, storagePath, successCallback2, errorCallback, true); - }); - return; - } - } - } - throw e; - } - }; - - // Global error handler. Errors bubble from request, to transaction, to db. - function onError (e) { - switch (e.target.errorCode) { - case 12: - console.log('Error - Attempt to open db with a lower version than the ' + - 'current one.'); - break; - default: - console.log('errorCode: ' + e.target.errorCode); - } - - console.log(e, e.code, e.message); - } - })(module.exports, window); require('cordova/exec/proxy').add('File', module.exports); From 2562aa9f541506f6ad012cea1401d7e5020cb963 Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 18:31:50 +1000 Subject: [PATCH 33/46] var -> const --- src/electron/FileProxy.js | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 3c889484d..74c1c8adc 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -182,8 +182,8 @@ }; exports.setMetadata = function (successCallback, errorCallback, args) { - var fullPath = args[0]; - var metadataObject = args[1]; + const fullPath = args[0]; + const metadataObject = args[1]; fs.utime(fullPath, metadataObject.modificationTime, metadataObject.modificationTime, (err) => { if (err) { @@ -227,34 +227,34 @@ }; exports.readAsText = function (successCallback, errorCallback, args) { - var fileName = args[0]; - var enc = args[1]; - var startPos = args[2]; - var endPos = args[3]; + const fileName = args[0]; + const enc = args[1]; + const startPos = args[2]; + const endPos = args[3]; readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback); }; exports.readAsDataURL = function (successCallback, errorCallback, args) { - var fileName = args[0]; - var startPos = args[1]; - var endPos = args[2]; + const fileName = args[0]; + const startPos = args[1]; + const endPos = args[2]; readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback); }; exports.readAsBinaryString = function (successCallback, errorCallback, args) { - var fileName = args[0]; - var startPos = args[1]; - var endPos = args[2]; + const fileName = args[0]; + const startPos = args[1]; + const endPos = args[2]; readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback); }; exports.readAsArrayBuffer = function (successCallback, errorCallback, args) { - var fileName = args[0]; - var startPos = args[1]; - var endPos = args[2]; + const fileName = args[0]; + const startPos = args[1]; + const endPos = args[2]; readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback); }; @@ -375,11 +375,11 @@ }; exports.moveTo = function (successCallback, errorCallback, args) { - var srcPath = args[0]; + const srcPath = args[0]; // parentFullPath and name parameters is ignored because // args is being passed downstream to exports.copyTo method - var parentFullPath = args[1]; // eslint-disable-line - var name = args[2]; // eslint-disable-line + const parentFullPath = args[1]; // eslint-disable-line + const name = args[2]; // eslint-disable-line exports.copyTo(function (fileEntry) { @@ -407,8 +407,8 @@ return; } - var indexPersistent = path.indexOf('persistent'); - var indexTemporary = path.indexOf('temporary'); + const indexPersistent = path.indexOf('persistent'); + const indexTemporary = path.indexOf('temporary'); // cdvfile://localhost/persistent/path/to/file if (indexPersistent !== -1) { From fc1528c1212e8f13d82aebcbd1827c66bc063d07 Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 18:37:49 +1000 Subject: [PATCH 34/46] Fix lint errors --- src/electron/FileProxy.js | 38 ++++++++++++++++++-------------------- src/electron/rimraf.js | 2 ++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 74c1c8adc..cd2d8743a 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -34,8 +34,6 @@ const fs = nodeRequire('fs'); const app = nodeRequire('electron').remote.app; - const LocalFileSystem = require('./LocalFileSystem'); - const FileSystem = require('./FileSystem'); const FileEntry = require('./FileEntry'); const FileError = require('./FileError'); const DirectoryEntry = require('./DirectoryEntry'); @@ -94,7 +92,7 @@ const exists = fs.existsSync(path); const baseName = nodeRequire('path').basename(path); - function createFile() { + function createFile () { fs.open(path, 'w', (err, fd) => { if (err) { if (errorCallback) { @@ -111,7 +109,7 @@ } successCallback(new FileEntry(baseName, path)); }); - }) + }); } if (options.create === true && options.exclusive === true && exists) { @@ -215,13 +213,13 @@ promisify(fs.open)(fileName, 'a') .then(fd => { return promisify(fs.write)(fd, buf, 0, buf.length, position) - .then(bw => bytesWritten = bw) + .then(bw => {bytesWritten = bw}) .finally(() => promisify(fs.close)(fd)); }) .then(() => successCallback(bytesWritten)) .catch(() => { if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR) + errorCallback(FileError.INVALID_MODIFICATION_ERR); } }); }; @@ -321,7 +319,7 @@ return; } successCallback(new DirectoryEntry(baseName, path)); - }) + }); } else if (options.create === true && exists) { if (fs.statSync(path).isDirectory()) { successCallback(new DirectoryEntry(baseName, path)); @@ -449,7 +447,7 @@ /** * Helpers ***/ function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) { - const promisify = nodeRequire('util').promisify + const promisify = nodeRequire('util').promisify; fs.open(fullPath, 'r', (err, fd) => { if (err) { @@ -462,18 +460,18 @@ promisify(fs.read)(fd, buf, 0, buf.length, startPos) .then(() => { switch (what) { - case 'text': - successCallback(buf.toString(encoding)); - break; - case 'dataURL': - successCallback('data:;base64,' + buf.toString('base64')); - break; - case 'arrayBuffer': - successCallback(buf); - break; - case 'binaryString': - successCallback(buf.toString('binary')); - break; + case 'text': + successCallback(buf.toString(encoding)); + break; + case 'dataURL': + successCallback('data:;base64,' + buf.toString('base64')); + break; + case 'arrayBuffer': + successCallback(buf); + break; + case 'binaryString': + successCallback(buf.toString('binary')); + break; } }) .catch(() => promisify(fs.close)(fd)) diff --git a/src/electron/rimraf.js b/src/electron/rimraf.js index 74584ff71..27d82362e 100644 --- a/src/electron/rimraf.js +++ b/src/electron/rimraf.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + const path = global.require("path") const fs = global.require("fs") let glob = undefined From c6c4f5ba29fe5dc0577bb099865836f718a046ae Mon Sep 17 00:00:00 2001 From: zorn-v Date: Fri, 24 Jan 2020 18:39:43 +1000 Subject: [PATCH 35/46] Fix lint --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index cd2d8743a..65353c729 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -213,7 +213,7 @@ promisify(fs.open)(fileName, 'a') .then(fd => { return promisify(fs.write)(fd, buf, 0, buf.length, position) - .then(bw => {bytesWritten = bw}) + .then(bw => { bytesWritten = bw; }) .finally(() => promisify(fs.close)(fd)); }) .then(() => successCallback(bytesWritten)) From 2d61ff9784de608acdc7eaf1ba270ed95aeda5ff Mon Sep 17 00:00:00 2001 From: zorn-v Date: Mon, 27 Jan 2020 15:54:09 +1000 Subject: [PATCH 36/46] Do not use nodejs *Sync functions --- src/electron/FileProxy.js | 212 ++++++++++++++++++++------------------ 1 file changed, 114 insertions(+), 98 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 65353c729..745731e2d 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -89,65 +89,74 @@ exports.getFile = function (successCallback, errorCallback, args) { const path = args[0] + args[1]; const options = args[2] || {}; - const exists = fs.existsSync(path); - const baseName = nodeRequire('path').basename(path); - function createFile () { - fs.open(path, 'w', (err, fd) => { - if (err) { - if (errorCallback) { - errorCallback(FileError.INVALID_STATE_ERR); - } - return; + fs.stat(path, (err, stats) => { + if (err && err.code !== 'ENOENT') { + if (errorCallback) { + errorCallback(FileError.INVALID_STATE_ERR); } - fs.close(fd, (err) => { + return; + } + const exists = !err; + const baseName = nodeRequire('path').basename(path); + + function createFile () { + fs.open(path, 'w', (err, fd) => { if (err) { if (errorCallback) { errorCallback(FileError.INVALID_STATE_ERR); } return; } - successCallback(new FileEntry(baseName, path)); + fs.close(fd, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.INVALID_STATE_ERR); + } + return; + } + successCallback(new FileEntry(baseName, path)); + }); }); - }); - } - - if (options.create === true && options.exclusive === true && exists) { - // If create and exclusive are both true, and the path already exists, - // getFile must fail. - if (errorCallback) { - errorCallback(FileError.PATH_EXISTS_ERR); } - } else if (options.create === true && !exists) { - // If create is true, the path doesn't exist, and no other error occurs, - // getFile must create it as a zero-length file and return a corresponding - // FileEntry. - createFile(); - } else if (options.create === true && exists) { - if (fs.statSync(path).isFile()) { - // Overwrite file, delete then create new. + + if (options.create === true && options.exclusive === true && exists) { + // If create and exclusive are both true, and the path already exists, + // getFile must fail. + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + } else if (options.create === true && !exists) { + // If create is true, the path doesn't exist, and no other error occurs, + // getFile must create it as a zero-length file and return a corresponding + // FileEntry. createFile(); - } else { + } else if (options.create === true && exists) { + if (stats.isFile()) { + // Overwrite file, delete then create new. + createFile(); + } else { + if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); + } + } + } else if (!options.create && !exists) { + // If create is not true and the path doesn't exist, getFile must fail. if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); + errorCallback(FileError.NOT_FOUND_ERR); } + } else if (!options.create && exists && stats.isDirectory()) { + // If create is not true and the path exists, but is a directory, getFile + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); + } + } else { + // Otherwise, if no other error occurs, getFile must return a FileEntry + // corresponding to path. + successCallback(new FileEntry(baseName, path)); } - } else if (!options.create && !exists) { - // If create is not true and the path doesn't exist, getFile must fail. - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); - } - } else if (!options.create && exists && fs.statSync(path).isDirectory()) { - // If create is not true and the path exists, but is a directory, getFile - // must fail. - if (errorCallback) { - errorCallback(FileError.TYPE_MISMATCH_ERR); - } - } else { - // Otherwise, if no other error occurs, getFile must return a FileEntry - // corresponding to path. - successCallback(new FileEntry(baseName, path)); - } + }); }; exports.getFileMetadata = function (successCallback, errorCallback, args) { @@ -298,50 +307,59 @@ exports.getDirectory = function (successCallback, errorCallback, args) { const path = args[0] + args[1]; const options = args[2] || {}; - const exists = fs.existsSync(path); - const baseName = nodeRequire('path').basename(path); - if (options.create === true && options.exclusive === true && exists) { - // If create and exclusive are both true, and the path already exists, - // getDirectory must fail. - if (errorCallback) { - errorCallback(FileError.PATH_EXISTS_ERR); + fs.stat(path, (err, stats) => { + if (err && err.code !== 'ENOENT') { + if (errorCallback) { + errorCallback(FileError.INVALID_STATE_ERR); + } + return; } - } else if (options.create === true && !exists) { - // If create is true, the path doesn't exist, and no other error occurs, - // getDirectory must create it as a zero-length file and return a corresponding - // MyDirectoryEntry. - fs.mkdir(path, (err) => { - if (err) { - if (errorCallback) { - errorCallback(FileError.PATH_EXISTS_ERR); + const exists = !err; + const baseName = nodeRequire('path').basename(path); + + if (options.create === true && options.exclusive === true && exists) { + // If create and exclusive are both true, and the path already exists, + // getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + } else if (options.create === true && !exists) { + // If create is true, the path doesn't exist, and no other error occurs, + // getDirectory must create it as a zero-length file and return a corresponding + // MyDirectoryEntry. + fs.mkdir(path, (err) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.PATH_EXISTS_ERR); + } + return; } - return; + successCallback(new DirectoryEntry(baseName, path)); + }); + } else if (options.create === true && exists) { + if (stats.isDirectory()) { + successCallback(new DirectoryEntry(baseName, path)); + } else if (errorCallback) { + errorCallback(FileError.INVALID_MODIFICATION_ERR); } + } else if (!options.create && !exists) { + // If create is not true and the path doesn't exist, getDirectory must fail. + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + } else if (!options.create && exists && stats.isFile()) { + // If create is not true and the path exists, but is a file, getDirectory + // must fail. + if (errorCallback) { + errorCallback(FileError.TYPE_MISMATCH_ERR); + } + } else { + // Otherwise, if no other error occurs, getDirectory must return a + // DirectoryEntry corresponding to path. successCallback(new DirectoryEntry(baseName, path)); - }); - } else if (options.create === true && exists) { - if (fs.statSync(path).isDirectory()) { - successCallback(new DirectoryEntry(baseName, path)); - } else if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - } - } else if (!options.create && !exists) { - // If create is not true and the path doesn't exist, getDirectory must fail. - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); } - } else if (!options.create && exists && fs.statSync(path).isFile()) { - // If create is not true and the path exists, but is a file, getDirectory - // must fail. - if (errorCallback) { - errorCallback(FileError.TYPE_MISMATCH_ERR); - } - } else { - // Otherwise, if no other error occurs, getDirectory must return a - // DirectoryEntry corresponding to path. - successCallback(new DirectoryEntry(baseName, path)); - } + }); }; exports.getParent = function (successCallback, errorCallback, args) { @@ -421,23 +439,21 @@ } } - if (path.indexOf(pathsPrefix.dataDirectory) === 0 && !fs.existsSync(pathsPrefix.dataDirectory)) { - fs.mkdirSync(pathsPrefix.dataDirectory, {recursive: true}); - } - - if (!fs.existsSync(path)) { - if (errorCallback) { - errorCallback(FileError.NOT_FOUND_ERR); + fs.stat(path, (err, stats) => { + if (err) { + if (errorCallback) { + errorCallback(FileError.NOT_FOUND_ERR); + } + return; } - return; - } - const baseName = nodeRequire('path').basename(path); - if (fs.statSync(path).isDirectory()) { - successCallback(new DirectoryEntry(baseName, path)); - } else { - successCallback(new FileEntry(baseName, path)); - } + const baseName = nodeRequire('path').basename(path); + if (stats.isDirectory()) { + successCallback(new DirectoryEntry(baseName, path)); + } else { + successCallback(new FileEntry(baseName, path)); + } + }); }; exports.requestAllPaths = function (successCallback) { From 7a8cb2514534ff6eb7bf09a172eeb62e45b6cab9 Mon Sep 17 00:00:00 2001 From: zorn Date: Tue, 28 Jan 2020 22:31:46 +1000 Subject: [PATCH 37/46] Fix typos --- src/electron/FileProxy.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 745731e2d..366ac4200 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -423,13 +423,15 @@ return; } + const indexApplication = path.indexOf('application'); const indexPersistent = path.indexOf('persistent'); const indexTemporary = path.indexOf('temporary'); - // cdvfile://localhost/persistent/path/to/file - if (indexPersistent !== -1) { + if (indexApplication !== -1) { // cdvfile://localhost/application/path/to/file + path = pathsPrefix.applicationDirectory + path.substr(indexApplication + 11); + } else if (indexPersistent !== -1) { // cdvfile://localhost/persistent/path/to/file path = pathsPrefix.dataDirectory + path.substr(indexPersistent + 10); - } else if (indexTemporary !== -1) { + } else if (indexTemporary !== -1) { // cdvfile://localhost/temporary/path/to/file path = pathsPrefix.tempDirectory + path.substr(indexTemporary + 9); } else { if (errorCallback) { From 29056e3e56c38d5d49e74c07196a548ea60a3736 Mon Sep 17 00:00:00 2001 From: zorn Date: Tue, 28 Jan 2020 22:33:07 +1000 Subject: [PATCH 38/46] Remove trailing separators in pathsPrefix --- src/electron/FileProxy.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 366ac4200..c168b0472 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -43,11 +43,11 @@ // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname const pathsPrefix = { - applicationDirectory: app.getAppPath() + nodePath.sep, - dataDirectory: app.getPath('userData') + nodePath.sep, - cacheDirectory: app.getPath('cache') + nodePath.sep, - tempDirectory: app.getPath('temp') + nodePath.sep, - documentsDirectory: app.getPath('documents') + nodePath.sep + applicationDirectory: app.getAppPath(), + dataDirectory: app.getPath('userData'), + cacheDirectory: app.getPath('cache'), + tempDirectory: app.getPath('temp'), + documentsDirectory: app.getPath('documents') }; /** * Exported functionality ***/ From 1ea98b7e10e7e09f7f6a81b8fcd71001b61f3d4b Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 29 Jan 2020 01:22:09 +1000 Subject: [PATCH 39/46] Revert "Remove trailing separators in pathsPrefix" This reverts commit 29056e3e56c38d5d49e74c07196a548ea60a3736. --- src/electron/FileProxy.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index c168b0472..366ac4200 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -43,11 +43,11 @@ // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname const pathsPrefix = { - applicationDirectory: app.getAppPath(), - dataDirectory: app.getPath('userData'), - cacheDirectory: app.getPath('cache'), - tempDirectory: app.getPath('temp'), - documentsDirectory: app.getPath('documents') + applicationDirectory: app.getAppPath() + nodePath.sep, + dataDirectory: app.getPath('userData') + nodePath.sep, + cacheDirectory: app.getPath('cache') + nodePath.sep, + tempDirectory: app.getPath('temp') + nodePath.sep, + documentsDirectory: app.getPath('documents') + nodePath.sep }; /** * Exported functionality ***/ From fac4d31f44c85f7565b395148b3c0d75810620b4 Mon Sep 17 00:00:00 2001 From: zorn Date: Wed, 29 Jan 2020 01:27:45 +1000 Subject: [PATCH 40/46] Fix resolveLocalFileSystemURI for cdvfile:// --- src/electron/FileProxy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 366ac4200..259ec56d8 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -428,11 +428,11 @@ const indexTemporary = path.indexOf('temporary'); if (indexApplication !== -1) { // cdvfile://localhost/application/path/to/file - path = pathsPrefix.applicationDirectory + path.substr(indexApplication + 11); + path = pathsPrefix.applicationDirectory + path.substr(indexApplication + 12); } else if (indexPersistent !== -1) { // cdvfile://localhost/persistent/path/to/file - path = pathsPrefix.dataDirectory + path.substr(indexPersistent + 10); + path = pathsPrefix.dataDirectory + path.substr(indexPersistent + 11); } else if (indexTemporary !== -1) { // cdvfile://localhost/temporary/path/to/file - path = pathsPrefix.tempDirectory + path.substr(indexTemporary + 9); + path = pathsPrefix.tempDirectory + path.substr(indexTemporary + 10); } else { if (errorCallback) { errorCallback(FileError.ENCODING_ERR); From 96e84aadd3f0f3896f094cfe8afc0b1a766ebeca Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 26 Mar 2020 20:48:12 +1000 Subject: [PATCH 41/46] Fix electron setMetadata --- src/electron/FileProxy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 259ec56d8..4ef6b9f7a 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -196,10 +196,10 @@ if (err) { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); - return; } - successCallback(); + return; } + successCallback(); }); }; From 9d1c1481352d57fe95b354751b4152bf7ccb2323 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 26 Mar 2020 21:04:47 +1000 Subject: [PATCH 42/46] Fix close fd in electron readAs --- src/electron/FileProxy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 4ef6b9f7a..b041f1f37 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -492,12 +492,12 @@ break; } }) - .catch(() => promisify(fs.close)(fd)) .catch(() => { if (errorCallback) { errorCallback(FileError.NOT_READABLE_ERR); } - }); + }) + .then(() => promisify(fs.close)(fd)); }); } From a2ba6b4e4461798c2379282134e5a8ab2a487b22 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 21 May 2020 15:06:56 +1000 Subject: [PATCH 43/46] truncate support --- src/electron/FileProxy.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index b041f1f37..8137e78c4 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -289,6 +289,21 @@ }); }; + exports.truncate = function (successCallback, errorCallback, args) { + const fullPath = args[0]; + const size = args[1]; + + fs.truncate(fullPath, size, err => { + if (err) { + if (errorCallback) { + errorCallback(FileError.INVALID_STATE_ERR); + } + return; + } + successCallback(size); + }) + }; + exports.removeRecursively = function (successCallback, errorCallback, args) { const fullPath = args[0]; const rimraf = require('./rimraf'); From 6e0540e4d924294b53d9781728c05a91a3fab413 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 21 May 2020 15:28:28 +1000 Subject: [PATCH 44/46] Fix lint error --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index 8137e78c4..c1e01efbd 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -301,7 +301,7 @@ return; } successCallback(size); - }) + }); }; exports.removeRecursively = function (successCallback, errorCallback, args) { From b2667d9892e3d9f0c3892d96d49f274b5b0b6f49 Mon Sep 17 00:00:00 2001 From: zorn Date: Thu, 15 Oct 2020 23:51:21 +1000 Subject: [PATCH 45/46] Fix electron app path --- src/electron/FileProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/FileProxy.js b/src/electron/FileProxy.js index c1e01efbd..962d3f508 100644 --- a/src/electron/FileProxy.js +++ b/src/electron/FileProxy.js @@ -43,7 +43,7 @@ // https://github.com/electron/electron/blob/master/docs/api/app.md#appgetpathname const pathsPrefix = { - applicationDirectory: app.getAppPath() + nodePath.sep, + applicationDirectory: nodePath.dirname(app.getAppPath()) + nodePath.sep, dataDirectory: app.getPath('userData') + nodePath.sep, cacheDirectory: app.getPath('cache') + nodePath.sep, tempDirectory: app.getPath('temp') + nodePath.sep, From 460f8d04d085b58632727c59acb63cbf92dce970 Mon Sep 17 00:00:00 2001 From: zorn Date: Tue, 22 Dec 2020 08:41:06 +1000 Subject: [PATCH 46/46] enableRemoteModule = true on plugin install --- scripts/electron/addNodeIntegration.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/electron/addNodeIntegration.js b/scripts/electron/addNodeIntegration.js index 6b2e660f4..1f6d2c093 100644 --- a/scripts/electron/addNodeIntegration.js +++ b/scripts/electron/addNodeIntegration.js @@ -6,5 +6,6 @@ module.exports = ctx => { cfg.browserWindow = cfg.browserWindow || {}; cfg.browserWindow.webPreferences = cfg.browserWindow.webPreferences || {}; cfg.browserWindow.webPreferences.nodeIntegration = true; + cfg.browserWindow.webPreferences.enableRemoteModule = true; fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 4), 'utf8'); }