You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
806 lines
29 KiB
JavaScript
806 lines
29 KiB
JavaScript
/*
|
|
*
|
|
* 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 IDBKeyRange */
|
|
|
|
var LocalFileSystem = require('./LocalFileSystem'),
|
|
FileSystem = require('./FileSystem'),
|
|
FileEntry = require('./FileEntry'),
|
|
FileError = require('./FileError'),
|
|
DirectoryEntry = require('./DirectoryEntry'),
|
|
File = require('./File');
|
|
|
|
/*
|
|
QUIRKS:
|
|
Does not fail when removing non-empty directories
|
|
Does not support metadata for directories
|
|
Does not support requestAllFileSystems
|
|
Does not support resolveLocalFileSystemURI
|
|
Methods copyTo and moveTo do not support directories
|
|
|
|
Heavily based on https://github.com/ebidel/idb.filesystem.js
|
|
*/
|
|
|
|
|
|
(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 DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1);
|
|
|
|
var pathsPrefix = {
|
|
// Read-only directory where the application is installed.
|
|
applicationDirectory: location.origin + "/",
|
|
// 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/',
|
|
};
|
|
|
|
/*** Exported functionality ***/
|
|
|
|
exports.requestFileSystem = function(successCallback, errorCallback, args) {
|
|
var type = args[0];
|
|
//var size = args[1];
|
|
|
|
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, '_');
|
|
|
|
var root = new DirectoryEntry('', DIR_SEPARATOR);
|
|
fs_ = new FileSystem(name, root);
|
|
|
|
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];
|
|
|
|
if (!successCallback) {
|
|
throw Error('Expected successCallback argument.');
|
|
}
|
|
|
|
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 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.INVALID_MODIFICATION_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;
|
|
}, errorCallback, [fullPath, null]);
|
|
};
|
|
|
|
exports.write = function(successCallback, errorCallback, args) {
|
|
var fileName = args[0],
|
|
data = args[1],
|
|
position = args[2];
|
|
//isBinary = args[3];
|
|
|
|
if (!data) {
|
|
if (errorCallback) {
|
|
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
|
}
|
|
return;
|
|
}
|
|
|
|
exports.getFile(function(fileEntry) {
|
|
var blob_ = fileEntry.file_.blob_;
|
|
|
|
if (!blob_) {
|
|
blob_ = new Blob([data], {type: data.type});
|
|
} else {
|
|
// Calc the head and tail fragments
|
|
var head = blob_.slice(0, position);
|
|
var tail = blob_.slice(position + 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],
|
|
{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 = data.lastModifiedDate || 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.byteLength);
|
|
}, errorCallback);
|
|
}, errorCallback, [fileName, null]);
|
|
};
|
|
|
|
exports.readAsText = function(successCallback, errorCallback, args) {
|
|
var fileName = args[0],
|
|
enc = args[1],
|
|
startPos = args[2],
|
|
endPos = args[3];
|
|
|
|
readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
|
|
};
|
|
|
|
exports.readAsDataURL = function(successCallback, errorCallback, args) {
|
|
var fileName = args[0],
|
|
startPos = args[1],
|
|
endPos = args[2];
|
|
|
|
readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
|
|
};
|
|
|
|
exports.readAsBinaryString = function(successCallback, errorCallback, args) {
|
|
var fileName = args[0],
|
|
startPos = args[1],
|
|
endPos = args[2];
|
|
|
|
readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
|
|
};
|
|
|
|
exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
|
|
var fileName = args[0],
|
|
startPos = args[1],
|
|
endPos = args[2];
|
|
|
|
readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
|
|
};
|
|
|
|
exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
|
|
var fullPath = args[0];
|
|
|
|
// 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();
|
|
}, errorCallback);
|
|
};
|
|
|
|
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.INVALID_MODIFICATION_ERR);
|
|
}
|
|
} else 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);
|
|
} else 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);
|
|
}
|
|
}
|
|
} else 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);
|
|
}
|
|
} else 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.INVALID_MODIFICATION_ERR);
|
|
}
|
|
} else {
|
|
// 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) {
|
|
var fullPath = args[0];
|
|
|
|
if (fullPath === DIR_SEPARATOR) {
|
|
successCallback(fs_.root);
|
|
return;
|
|
}
|
|
|
|
var pathArr = fullPath.split(DIR_SEPARATOR);
|
|
pathArr.pop();
|
|
var namesa = pathArr.pop();
|
|
var path = pathArr.join(DIR_SEPARATOR);
|
|
|
|
exports.getDirectory(successCallback, errorCallback, [path, namesa, {create: false}]);
|
|
};
|
|
|
|
exports.copyTo = function(successCallback, errorCallback, args) {
|
|
var srcPath = args[0];
|
|
var parentFullPath = args[1];
|
|
var name = args[2];
|
|
|
|
// Read src file
|
|
exports.getFile(function(srcFileEntry) {
|
|
|
|
// 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}]);
|
|
|
|
}, errorCallback, [srcPath, null]);
|
|
};
|
|
|
|
exports.moveTo = function(successCallback, errorCallback, args) {
|
|
var srcPath = args[0];
|
|
//var parentFullPath = args[1];
|
|
//var name = args[2];
|
|
|
|
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)) {
|
|
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 () {
|
|
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;
|
|
successCallback(entry);
|
|
}
|
|
};
|
|
fileWriter.onerror = function () {
|
|
if (errorCallback) {
|
|
errorCallback(FileError.NOT_READABLE_ERR);
|
|
}
|
|
};
|
|
fileWriter.write(new Blob([xhr.response]));
|
|
}, errorCallback);
|
|
}
|
|
};
|
|
|
|
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();
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
return {
|
|
storagePath: prefix + fullPath,
|
|
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(),
|
|
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);
|
|
var range = IDBKeyRange.bound(fullPath, fullPath + DIR_OPEN_BOUND,
|
|
false, true);
|
|
var request = tx.objectStore(FILE_STORE_).get(range);
|
|
|
|
tx.onabort = errorCallback || onError;
|
|
tx.oncomplete = function(e) {
|
|
successCallback(request.result);
|
|
};
|
|
};
|
|
|
|
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_OPEN_BOUND, false, true);
|
|
|
|
var tx = this.db.transaction([FILE_STORE_], 'readonly');
|
|
tx.onabort = errorCallback || onError;
|
|
tx.oncomplete = function(e) {
|
|
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) {
|
|
// 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) {
|
|
// 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;
|
|
}
|
|
});
|
|
|
|
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) {
|
|
if (!this.db) {
|
|
if (errorCallback) {
|
|
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
|
}
|
|
return;
|
|
}
|
|
|
|
var tx = this.db.transaction([FILE_STORE_], 'readwrite');
|
|
tx.oncomplete = successCallback;
|
|
tx.onabort = errorCallback || onError;
|
|
|
|
//var request = tx.objectStore(FILE_STORE_).delete(fullPath);
|
|
var range = IDBKeyRange.bound(
|
|
fullPath, fullPath + DIR_OPEN_BOUND, false, true);
|
|
tx.objectStore(FILE_STORE_)['delete'](range);
|
|
};
|
|
|
|
idb_.put = function(entry, storagePath, successCallback, errorCallback) {
|
|
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(e) {
|
|
// TODO: Error is thrown if we pass the request event back instead.
|
|
successCallback(entry);
|
|
};
|
|
|
|
tx.objectStore(FILE_STORE_).put(entry, storagePath);
|
|
};
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Clean up.
|
|
// TODO: Is there a place for this?
|
|
// global.addEventListener('beforeunload', function(e) {
|
|
// idb_.db && idb_.db.close();
|
|
// }, false);
|
|
|
|
})(module.exports, window);
|
|
|
|
require("cordova/exec/proxy").add("File", module.exports);
|