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