| /* |
| * |
| * 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 IDBKeyRange */ |
| /* 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<symbol> 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); |
| })(); |