Repository: cordova-plugin-file Updated Branches: refs/heads/master 28662835e -> 282fa0983
Support for resolve URI, request all paths and local app directory. Project: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/commit/98fed5f7 Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/tree/98fed5f7 Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/diff/98fed5f7 Branch: refs/heads/master Commit: 98fed5f788f33c698e6a5bedb75b20a232641115 Parents: 429d072 Author: Rodrigo Silveira <[email protected]> Authored: Wed Jul 9 15:46:50 2014 -0700 Committer: Rodrigo Silveira <[email protected]> Committed: Tue Jul 22 15:13:33 2014 -0700 ---------------------------------------------------------------------- doc/index.md | 7 +- plugin.xml | 10 ++ src/firefoxos/FileProxy.js | 221 +++++++++++++++++++++++++++++---------- www/firefoxos/FileSystem.js | 29 +++++ 4 files changed, 210 insertions(+), 57 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/blob/98fed5f7/doc/index.md ---------------------------------------------------------------------- diff --git a/doc/index.md b/doc/index.md index 504b493..875e913 100644 --- a/doc/index.md +++ b/doc/index.md @@ -153,9 +153,14 @@ as a shim on top of indexedDB. * Does not fail when removing non-empty directories * Does not support metadata for directories -* Does not support `requestAllFileSystems` and `resolveLocalFileSystemURI` methods * Methods `copyTo` and `moveTo` do not support directories +The following data paths are supported: +* `applicationDirectory` - Uses `xhr` to get local files that are packaged with the app. +* `dataDirectory` - For persistent app-specific data files. +* `cacheDirectory` - Cached files that should survive app restarts (Apps should not rely +on the OS to delete files in here). + ## Upgrading Notes In v1.0.0 of this plugin, the `FileEntry` and `DirectoryEntry` structures have changed, http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/blob/98fed5f7/plugin.xml ---------------------------------------------------------------------- diff --git a/plugin.xml b/plugin.xml index 4e1c233..0da51ed 100644 --- a/plugin.xml +++ b/plugin.xml @@ -313,6 +313,16 @@ xmlns:android="http://schemas.android.com/apk/res/android" <js-module src="src/firefoxos/FileProxy.js" name="FileProxy"> <runs /> </js-module> + + <js-module src="www/fileSystemPaths.js" name="fileSystemPaths"> + <merges target="cordova" /> + <runs/> + </js-module> + + <!-- Firefox OS specific file apis --> + <js-module src="www/firefoxos/FileSystem.js" name="firefoxFileSystem"> + <merges target="window.FileSystem" /> + </js-module> </platform> </plugin> http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/blob/98fed5f7/src/firefoxos/FileProxy.js ---------------------------------------------------------------------- diff --git a/src/firefoxos/FileProxy.js b/src/firefoxos/FileProxy.js index 8f36632..81cfed7 100644 --- a/src/firefoxos/FileProxy.js +++ b/src/firefoxos/FileProxy.js @@ -46,7 +46,6 @@ QUIRKS: var fs_ = null; - var storageType_ = 'temporary'; var idb_ = {}; idb_.db = null; var FILE_STORE_ = 'entries'; @@ -54,6 +53,32 @@ QUIRKS: var DIR_SEPARATOR = '/'; var DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1); + var pathsPrefix = { + // Read-only directory where the application is installed. + applicationDirectory: location.origin + "/", + // Root of app's private writable storage + applicationStorageDirectory: null, + // 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/', + // Android: the application space on external storage. + externalApplicationStorageDirectory: null, + // Android: Where to put app-specific data files on external storage. + externalDataDirectory: null, + // Android: the application cache on external storage. + externalCacheDirectory: null, + // Android: the external storage (SD card) root. + externalRootDirectory: null, + // iOS: Temp directory that the OS can clear at will. + tempDirectory: null, + // iOS: Holds app-specific files that should be synced (e.g. to iCloud). + syncedDataDirectory: null, + // iOS: Files private to the app, but that are meaningful to other applciations (e.g. Office files) + documentsDirectory: null + }; + /*** Exported functionality ***/ exports.requestFileSystem = function(successCallback, errorCallback, args) { @@ -61,24 +86,25 @@ QUIRKS: var size = args[1]; if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) { - if (errorCallback) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); - return; - } + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); + return; } - storageType_ = type == LocalFileSystem.TEMPORARY ? 'Temporary' : 'Persistent'; - var name = (location.protocol + location.host).replace(/:/g, '_') + - ':' + storageType_; + var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent'; + var storageName = (location.protocol + location.host).replace(/:/g, '_'); var root = new DirectoryEntry('', DIR_SEPARATOR); - var fs_ = new FileSystem(name, root); + fs_ = new FileSystem(name, root); - idb_.open(fs_.name, function() { + idb_.open(storageName, function() { successCallback(fs_); }, errorCallback); }; + 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]; @@ -87,20 +113,22 @@ QUIRKS: throw Error('Expected successCallback argument.'); } - idb_.getAllEntries(fullPath, function(entries) { + var path = resolveToFullPath_(fullPath); + + idb_.getAllEntries(path.fullPath, path.storagePath, function(entries) { successCallback(entries); }, errorCallback); }; exports.getFile = function(successCallback, errorCallback, args) { - var fullpath = args[0]; + 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); + path = resolveToFullPath_(fullPath, path); - idb_.get(path, function(fileEntry) { + 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. @@ -112,16 +140,16 @@ QUIRKS: // 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 name = path.split(DIR_SEPARATOR).pop(); // Just need filename. - var newFileEntry = new FileEntry(name, path, fs_); + 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() + lastModifiedDate: new Date(), + storagePath: path.storagePath }); - idb_.put(newFileEntry, successCallback, errorCallback); + idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback); } else if (options.create === true && fileEntry) { if (fileEntry.isFile) { successCallback(fileEntryFromIdbEntry(fileEntry)); @@ -157,7 +185,7 @@ QUIRKS: exports.getFile(function(fileEntry) { successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate, fileEntry.file_.size)); - }, errorCallback, [null, fullPath]); + }, errorCallback, [fullPath, null]); }; exports.getMetadata = function(successCallback, errorCallback, args) { @@ -176,7 +204,7 @@ QUIRKS: exports.getFile(function (fileEntry) { fileEntry.file_.lastModifiedDate = metadataObject.modificationTime; - }, errorCallback, [null, fullPath]); + }, errorCallback, [fullPath, null]); }; exports.write = function(successCallback, errorCallback, args) { @@ -186,7 +214,7 @@ QUIRKS: isBinary = args[3]; if (!data) { - errorCallback(FileError.INVALID_MODIFICATION_ERR); + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); return; } @@ -218,10 +246,10 @@ QUIRKS: fileEntry.file_.name = blob_.name; fileEntry.file_.type = blob_.type; - idb_.put(fileEntry, function() { + idb_.put(fileEntry, fileEntry.file_.storagePath, function() { successCallback(data.byteLength); }, errorCallback); - }, errorCallback, [null, fileName]); + }, errorCallback, [fileName, null]); }; exports.readAsText = function(successCallback, errorCallback, args) { @@ -275,7 +303,7 @@ QUIRKS: // Create an absolute path if we were handed a relative one. path = resolveToFullPath_(fullPath, path); - idb_.get(path, function(folderEntry) { + idb_.get(path.storagePath, function(folderEntry) { if (!options) { options = {}; } @@ -290,10 +318,9 @@ QUIRKS: // 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 name = path.split(DIR_SEPARATOR).pop(); // Just need filename. - var dirEntry = new DirectoryEntry(name, path, fs_); + var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root)); - idb_.put(dirEntry, successCallback, errorCallback); + idb_.put(dirEntry, path.storagePath, successCallback, errorCallback); } else if (options.create === true && folderEntry) { if (folderEntry.isDirectory) { @@ -306,9 +333,8 @@ QUIRKS: } } else if ((!options.create || options.create === false) && !folderEntry) { // Handle root special. It should always exist. - if (path == DIR_SEPARATOR) { - folderEntry = new DirectoryEntry('', DIR_SEPARATOR, fs_); - successCallback(folderEntry); + if (path.fullPath === DIR_SEPARATOR) { + successCallback(fs_.root); return; } @@ -362,11 +388,11 @@ QUIRKS: exports.write(function() { successCallback(dstFileEntry); - }, errorCallback, [dstFileEntry.fullPath, srcFileEntry.file_.blob_, 0]); + }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]); }, errorCallback, [parentFullPath, name, {create: true}]); - }, errorCallback, [null, srcPath]); + }, errorCallback, [srcPath, null]); }; exports.moveTo = function(successCallback, errorCallback, args) { @@ -383,6 +409,78 @@ QUIRKS: }, 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)) { + path = decodeURI(path); + } + + if (path.indexOf(pathsPrefix.dataDirectory) === 0) { + path = path.substring(pathsPrefix.dataDirectory.length - 1); + + exports.requestFileSystem(function(fs) { + fs.root.getFile(path, {create: false}, successCallback, function() { + fs.root.getDirectory(path, {create: false}, successCallback, errorCallback); + }); + }, errorCallback, [LocalFileSystem.PERSISTENT]); + } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) { + path = path.substring(pathsPrefix.cacheDirectory.length - 1); + + exports.requestFileSystem(function(fs) { + fs.root.getFile(path, {create: false}, successCallback, function() { + fs.root.getDirectory(path, {create: false}, successCallback, errorCallback); + }); + }, errorCallback, [LocalFileSystem.TEMPORARY]); + } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) { + path = path.substring(pathsPrefix.applicationDirectory.length); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", path, true); + xhr.onreadystatechange = function () { + if (xhr.status === 200 && xhr.readyState === 4) { + exports.requestFileSystem(function(fs) { + fs.name = location.hostname; + fs.root.getFile(path, {create: true}, writeFile, errorCallback); + }, errorCallback, [LocalFileSystem.PERSISTENT]); + } + }; + + xhr.onerror = function () { + errorCallback && errorCallback(FileError.NOT_READABLE_ERR); + }; + + xhr.send(); + } else { + 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; + successCallback(entry); + } + }; + fileWriter.onerror = function () { + errorCallback && errorCallback(FileError.NOT_READABLE_ERR); + }; + fileWriter.write(new Blob([xhr.response])); + }, errorCallback); + } + }; + + exports.requestAllPaths = function(successCallback) { + successCallback(pathsPrefix); + }; + /*** Helpers ***/ /** @@ -397,12 +495,13 @@ QUIRKS: * @constructor */ function MyFile(opts) { - var blob_ = null; + var blob_ = new Blob(); 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. @@ -427,9 +526,17 @@ QUIRKS: // 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 = ''; - var relativePath = path[0] != DIR_SEPARATOR; + 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) { @@ -453,7 +560,7 @@ QUIRKS: }).join(DIR_SEPARATOR); // Add back in leading slash. - if (fullPath[0] != DIR_SEPARATOR) { + if (fullPath[0] !== DIR_SEPARATOR) { fullPath = DIR_SEPARATOR + fullPath; } @@ -472,7 +579,12 @@ QUIRKS: fullPath = fullPath.substring(0, fullPath.length - 1); } - return fullPath; + return { + storagePath: prefix + fullPath, + fullPath: fullPath, + fileName: fullPath.split(DIR_SEPARATOR).pop(), + fsName: prefix.split(DIR_SEPARATOR).pop() + }; } function fileEntryFromIdbEntry(fileEntry) { @@ -509,7 +621,7 @@ QUIRKS: break; } - }, errorCallback, [null, fullPath]); + }, errorCallback, [fullPath, null]); } /*** Core logic to handle IDB operations ***/ @@ -552,6 +664,7 @@ QUIRKS: idb_.get = function(fullPath, successCallback, errorCallback) { if (!this.db) { + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); return; } @@ -568,41 +681,35 @@ QUIRKS: }; }; - idb_.getAllEntries = function(fullPath, successCallback, errorCallback) { + idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) { if (!this.db) { + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); return; } var results = []; - //var range = IDBKeyRange.lowerBound(fullPath, true); - //var range = IDBKeyRange.upperBound(fullPath, true); - - // Treat the root entry special. Querying it returns all entries because - // they match '/'. - var range = null; - if (fullPath != DIR_SEPARATOR) { - //console.log(fullPath + '/', fullPath + DIR_OPEN_BOUND) - range = IDBKeyRange.bound( - fullPath + DIR_SEPARATOR, fullPath + DIR_OPEN_BOUND, false, true); + if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) { + storagePath = storagePath.substring(0, storagePath.length - 1); } + range = IDBKeyRange.bound( + storagePath + DIR_SEPARATOR, storagePath + DIR_OPEN_BOUND, false, true); + var tx = this.db.transaction([FILE_STORE_], 'readonly'); tx.onabort = errorCallback || onError; tx.oncomplete = function(e) { - // TODO: figure out how to do be range queries instead of filtering result - // in memory :( results = results.filter(function(val) { var valPartsLen = val.fullPath.split(DIR_SEPARATOR).length; var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length; - if (fullPath == DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) { + if (fullPath === DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) { // Hack to filter out entries in the root folder. This is inefficient // because reading the entires of fs.root (e.g. '/') returns ALL // results in the database, then filters out the entries not in '/'. return val; - } else if (fullPath != DIR_SEPARATOR && - valPartsLen == fullPathPartsLen + 1) { + } else if (fullPath !== DIR_SEPARATOR && + 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; @@ -627,6 +734,7 @@ QUIRKS: idb_['delete'] = function(fullPath, successCallback, errorCallback) { if (!this.db) { + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); return; } @@ -637,11 +745,12 @@ QUIRKS: //var request = tx.objectStore(FILE_STORE_).delete(fullPath); var range = IDBKeyRange.bound( fullPath, fullPath + DIR_OPEN_BOUND, false, true); - var request = tx.objectStore(FILE_STORE_)['delete'](range); + tx.objectStore(FILE_STORE_)['delete'](range); }; - idb_.put = function(entry, successCallback, errorCallback) { + idb_.put = function(entry, storagePath, successCallback, errorCallback) { if (!this.db) { + errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR); return; } @@ -652,7 +761,7 @@ QUIRKS: successCallback(entry); }; - var request = tx.objectStore(FILE_STORE_).put(entry, entry.fullPath); + tx.objectStore(FILE_STORE_).put(entry, storagePath); }; // Global error handler. Errors bubble from request, to transaction, to db. http://git-wip-us.apache.org/repos/asf/cordova-plugin-file/blob/98fed5f7/www/firefoxos/FileSystem.js ---------------------------------------------------------------------- diff --git a/www/firefoxos/FileSystem.js b/www/firefoxos/FileSystem.js new file mode 100644 index 0000000..23daa07 --- /dev/null +++ b/www/firefoxos/FileSystem.js @@ -0,0 +1,29 @@ +/* + * + * 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. + * +*/ + +FILESYSTEM_PREFIX = "cdvfile://"; + +module.exports = { + __format__: function(fullPath) { + return (FILESYSTEM_PREFIX + this.name + (fullPath[0] === '/' ? '' : '/') + encodeURI(fullPath)); + } +}; +
