Refactor to support open() on directories so that we can list files.

This commit is contained in:
Alan Kligman 2012-11-29 19:46:35 -05:00
parent 9859e7988a
commit 9053d67146
3 changed files with 275 additions and 916 deletions

View File

@ -21,7 +21,7 @@ require(["src/fs"], function(IDBFS) {
if(error) {
return console.error(error);
}
fs.mkdir("/tmp", function() {
fs.mkdir("/tmp", function(error) {
if(error) {
return console.error(error);
}

View File

@ -1,632 +0,0 @@
/*
Copyright (c) 2012, Alan Kligman
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the Mozilla Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define(function(require) {
// 'use strict';
var debug = require("debug");
var _ = require("lodash");
var path = require("src/path");
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}).toUpperCase();
}
function makeDirectoryEntry(name, modtime) {
var parent = path.dirname(name);
return {
"parent": parent,
"name": name,
"contenttype": "application/directory",
"lastmodified": modtime || Date.now()
}
}
function makeFileEntry(name, oid, size, modtime) {
var parent = path.dirname(name);
return {
"parent": parent,
"name": name,
"contenttype": "application/file",
"lastmodified": modtime || Date.now(),
"size": size || 0,
"oid": oid || guid()
}
}
function Data(handle, bytes) {
return {
handle: handle || guid(),
bytes: bytes || undefined
}
}
function File(handle, size, atime, ctime, mtime, nlinks, type, flags, data, xattrs) {
var now = Date.now();
return {
handle: handle || guid(),
size: size || 0,
atime: atime || now,
ctime: ctime || now,
mtime: mtime || now,
type: type || "application/octet-stream",
flags: flags || "",
xattrs: xattrs || {},
data: data || guid()
}
}
function FileEntry(fullpath, file) {
return {
name: fullpath,
parent: path.dirname(fullpath),
file: file,
type: "application/file"
}
}
function DirectoryEntry(fullpath, atime, ctime, mtime, xattrs) {
var now = Date.now();
return {
name: fullpath,
parent: path.dirname(fullpath),
atime: atime || now,
ctime: ctime || now,
mtime: mtime || now,
xattrs: xattrs || {},
type: "application/directory"
}
}
var METADATA_STORE_NAME = "metadata";
var FILE_STORE_NAME = "files";
var NAME_INDEX = "name";
var NAME_INDEX_KEY_PATH = "name";
var PARENT_INDEX = "parent";
var PARENT_INDEX_KEY_PATH = "parent";
var OBJECT_ID_INDEX = "oid";
var OBJECT_ID_INDEX_KEY_PATH = "oid";
var MIME_DIRECTORY = "application/directory";
var MIME_FILE = "application/file";
var IDB_RO = "readonly";
var IDB_RW = "readwrite";
function genericErrorHandler(callback) {
return function(transaction, error) {
debug.error("error: ", error);
if(transaction && !transaction.error) {
try {
transaction.abort();
} catch(e) {
// Transaction has is already completed or aborted, this is probably a programming error
debug.warn("attempt to abort and already completed or aborted transaction");
}
}
if(callback && "function" === typeof callback) {
callback.call(undefined, error);
}
}
}
function FileSystem(db) {
var fs = this;
var fds = {}; // Open file descriptors
// Internal prototypes
function OpenFileDescription(name, entry, flags, mode) {
this.name = name;
this.entry = entry;
this.flags = flags;
this.mode = mode;
this.pointer = 0;
this.pending = 0;
this.valid = true;
this.oncomplete = undefined;
}
function FileDescriptor(ofd) {
var descriptor = this.descriptor = guid();
function start() {
if(!ofd.valid) {
return false;
}
++ ofd.pending;
return true;
}
function end() {
-- ofd.pending;
if(!ofd.valid && !ofd.pending) {
if(ofd.oncomplete && "function" === typeof ofd.oncomplete) {
debug.info("close <--");
delete fds[descriptor];
ofd.oncomplete.call();
}
}
}
function read(buffer, callback) {
debug.info("read -->");
var onerror = genericErrorHandler(callback);
if(!start()) {
onerror(null, new BadFileDescriptorError());
return;
}
transaction = db.transaction([FILE_STORE_NAME], IDB_RO);
transaction.oncomplete = function(e)
end();
}
var store = transaction.objectStore(FILE_STORE_NAME);
if(MIME_FILE === ofd.entry["contenttype"]) {
var oid = ofd.entry["oid"];
var getRequest = store.get(oid);
getRequest.onsuccess = function(e) {
var storedBuffer = e.target.result;
if(!storedBuffer) {
// There's no file data, so return zero bytes read
end();
debug.info("read <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, 0, buffer);
}
} else {
// Make sure we're not going to read past the end of the file
var bytes = (ofd.pointer + buffer.length > storedBuffer.length) ? (storedBuffer.length - ofd.pointer) : buffer.length;
// Copy the desired region from the file into the buffer supplied
var storedBufferView = storedBuffer.subarray(ofd.pointer, ofd.pointer + bytes);
buffer.set(storedBufferView);
ofd.pointer += bytes;
debug.info("read <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, bytes, buffer);
}
}
};
getRequest.onerror = onerror.bind(null, transaction);
} else if(MIME_DIRECTORY === ofd.entry["contenttype"]) {
// NOT IMPLEMENTED
onerror(transaction, new NotImplementedError());
}
}
function write(buffer, callback) {
debug.info("write -->");
var onerror = genericErrorHandler(callback);
if(OM_RO === ofd.mode) {
onerror(null, new BadFileDescriptorError());
return;
}
if(!start()) {
onerror(null, new BadFileDescriptorError());
return;
}
transaction = db.transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
transaction.oncomplete = function(e) {
end();
}
var metaStore = transaction.objectStore(METADATA_STORE_NAME);
var fileStore = transaction.objectStore(FILE_STORE_NAME);
var oid = ofd.entry["oid"];
var getRequest = fileStore.get(oid);
getRequest.onsuccess = function(e) {
var storedBuffer = e.target.result;
if(!storedBuffer) {
storedBuffer = new Uint8Array();
}
var bytes = buffer.length;
var size = (storedBuffer.length > ofd.pointer + bytes) ? storedBuffer.length : ofd.pointer + bytes;
var writeBuffer = new Uint8Array(size);
writeBuffer.set(storedBuffer);
writeBuffer.set(buffer);
ofd.pointer += bytes;
var putRequest = fileStore.put(writeBuffer, oid);
putRequest.onsuccess = function(e) {
var readMetadataRequest = metaStore.get(ofd.entry["name"]);
readMetadataRequest.onsuccess = function(e) {
var entry = e.target.result;
entry = makeFileEntry(entry["name"], entry["oid"], size);
ofd.entry = entry;
var writeMetadataRequest = metaStore.put(entry, entry["name"]);
writeMetadataRequest.onsuccess = function(e) {
debug.info("write <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, size, buffer);
}
};
writeMetadataRequest.onerror = onerror.bind(null, transaction);
}
readMetadataRequest.onerror = onerror.bind(null, transaction);
};
putRequest.onerror = onerror.bind(null, transaction);
};
getRequest.onerror = onerror.bind(null, transaction);
}
var SW_SET = "SET";
var SW_CURRENT = "CURRENT";
var SW_END = "END";
function seek(offset, whence) {
whence = whence || SW_CURRENT;
if(SW_SET === whence) {
ofd.pointer = offset;
} else if(SW_CURRENT === whence) {
ofd.pointer += offset;
} else if(SW_END === whence) {
ofd.pointer = ofd.entry["size"] + offset;
}
}
this.read = read;
this.seek = seek;
this.write = write;
Object.defineProperty(this, "valid", {
get: function() {
return ofd.valid;
}
});
fds[descriptor] = ofd;
}
// API
// Flags
var OF_CREATE = "CREATE";
var OF_APPEND = "APPEND";
var OF_TRUNCATE = "TRUNCATE";
var OF_DIRECTORY = "DIRECTORY";
// Modes
var OM_RO = "RO";
var OM_RW = "RW";
function open(fullpath, flags, mode, callback) {
debug.info("open -->");
fullpath = path.normalize(fullpath);
transaction = db.transaction([METADATA_STORE_NAME], IDB_RW);
var metaStore = transaction.objectStore(METADATA_STORE_NAME);
var fileStore = transaction.objectStore(FILE_STORE_NAME);
var nameIndex = metaStore.index(NAME_INDEX);
var onerror = genericErrorHandler(callback);
if(undefined === flags) {
flags = [];
} else if("string" === typeof flags) {
flags = [flags];
}
var getRequest = nameIndex.get(fullpath);
getRequest.onsuccess = function(e) {
var entry = e.target.result;
var file;
if(!entry) {
if(!_(flags).contains(OF_CREATE)) {
onerror(transaction, new NoEntryError());
return;
} else {
file = new File();
entry = new FileEntry(fullpath, file.handle);
var createFileRequest = fileStore.put(file, file.handle);
createFileRequest.onsuccess = function(e) {
var createEntryRequest = fileStore.put(entry, fullpath);
createEntryRequest.onsuccess = complete;
createEntryRequest.onerror = onerror.bind(null, transaction);
};
createFileRequest.onerror = onerror.bind(null, transaction);
}
} else {
if(entry["type"] === MIME_DIRECTORY && mode === OM_RW) {
onerror(transaction, new IsDirectoryError());
return;
} else {
var getFileRequest = fileStore.get(entry.file);
getFileRequest.onsuccess = function(e) {
file = e.target.result;
complete();
};
getFileRequest.onerror = onerror.bind(null, transaction);
}
}
function complete() {
var ofd;
ofd = new OpenFileDescription(fullpath, file, flags, mode);
var fd = new FileDescriptor(ofd);
debug.info("open <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, fd);
}
}
};
getRequest.onerror = onerror.bind(null, transaction);
}
function close(fd, callback) {
debug.info("close -->");
var onerror = genericErrorHandler(callback);
if(!fds.hasOwnProperty(fd.descriptor)) {
onerror(null, new BadFileDescriptorError());
return;
}
var ofd = fds[fd.descriptor];
ofd.valid = false;
if(!ofd.valid && !ofd.pending) {
debug.info("close <--");
callback.call()
} else {
ofd.oncomplete = callback;
}
}
function mkdir(transaction, fullpath, callback) {
debug.info("mkdir -->");
fullpath = path.normalize(fullpath);
transaction = transaction || db.transaction([METADATA_STORE_NAME], IDB_RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var onerror = genericErrorHandler(callback);
var getRequest = nameIndex.get(fullpath);
getRequest.onsuccess = function(e) {
var result = e.target.result;
if(result) {
onerror(null, new PathExistsError());
} else {
var entry = new DirectoryEntry(fullpath);
var directoryRequest = store.put(entry, fullpath);
directoryRequest.onsuccess = function(e) {
debug.info("mkdir <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined);
}
};
directoryRequest.onerror = onerror.bind(null, transaction);
}
};
getRequest.onerror = onerror.bind(null, transaction);
}
function rmdir(fullpath, callback) {
debug.info("rmdir -->");
fullpath = path.normalize(fullpath);
transaction = db.transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var metaStore = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = metaStore.index(NAME_INDEX);
var parentIndex = metaStore.index(PARENT_INDEX);
var onerror = genericErrorHandler(callback);
var getRequest = nameIndex.get(fullpath);
getRequest.onsuccess = function(e) {
var result = e.target.result;
if(!result) {
onerror(transaction, new NoEntryError());
return;
} else {
var contentRequest = parentIndex.get(fullpath);
contentRequest.onsuccess = function(e) {
var result = e.target.result;
if(result) {
onerror(transaction, new NotEmptyError());
return;
} else {
var removeRequest = metaStore.delete(fullpath);
removeRequest.onsuccess = function(e) {
debug.info("rmdir <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined);
}
};
removeRequest.onerror = onerror.bind(null, transaction);
}
};
contentRequest.onerror = onerror.bind(null, transaction);
}
};
getRequest.onerror = onerror.bind(null, transaction);
}
function stat(transaction, fullpath, callback) {
debug.info("stat -->");
fullpath = path.normalize(fullpath);
transaction = transaction || db.transaction([METADATA_STORE_NAME], IDB_RO);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var onerror = genericErrorHandler(callback);
var getRequest = nameIndex.get(fullpath);
getRequest.onsuccess = function(e) {
var result = e.target.result;
if(!result) {
onerror(transaction, new NoEntryError());
return;
} else {
debug.info("stat <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, result);
}
}
};
getRequest.onerror = onerror.bind(null, transaction);
}
function link(oldpath, newpath, callback) {
}
function unlink(pathname, callback) {
debug.info("unlink -->");
pathname = path.normalize(pathname);
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RW);
var metaStore = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = metaStore.index(NAME_INDEX);
var onerror = genericErrorHandler(callback);
stat(transaction, pathname, function(error, entry) {
if(error) {
onerror(transaction, error);
return;
}
if(MIME_DIRECTORY === entry["contenttype"]) {
onerror(transaction, new IsDirectoryError());
return;
}
var unlinkRequest = metaStore.delete(entry["name"]);
unlinkRequest.onsuccess = function(e) {
// We don't support links, so this entry is the only entry for the file data
var transaction = db.transaction([FILE_STORE_NAME], IDB_RW);
var fileStore = transaction.objectStore(FILE_STORE_NAME);
var deleteRequest = fileStore.delete(entry["oid"]);
deleteRequest.onsuccess = function(e) {
debug.info("unlink <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined);
}
};
deleteRequest.onerror = onerror.bind(null, transaction);
};
unlinkRequest.onerror = onerror.bind(null, transaction);
});
}
function api() {
return {
open: open,
close: close,
mkdir: mkdir.bind(null, null),
rmdir: rmdir,
stat: stat.bind(null, null),
link: link,
unlink: unlink,
dump: dump
}
}
this.open = open;
this.close = close;
this.mkdir = mkdir;
this.rmdir = rmdir;
this.stat = stat;
// this.link = link;
this.unlink = unlink;
this.api = api;
// DEBUG
function dump(element, callback, clear) {
if(clear) {
element.innerHTML = "";
}
element.innerHTML += "Metadata://<br>";
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RO);
var metaStore = transaction.objectStore(METADATA_STORE_NAME);
var metaRequest = metaStore.openCursor();
metaRequest.onsuccess = function(e) {
var metaCursor = e.target.result;
if(metaCursor) {
element.innerHTML += JSON.stringify(metaCursor.value) + "<br>";
metaCursor.continue();
} else {
element.innerHTML += "Files://<br>"
transaction = db.transaction([FILE_STORE_NAME], IDB_RO);
var fileStore = transaction.objectStore(FILE_STORE_NAME);
var fileRequest = fileStore.openCursor();
fileRequest.onsuccess = function(e) {
var fileCursor = e.target.result;
if(fileCursor) {
element.innerHTML += JSON.stringify(fileCursor.key) + "<br>";
fileCursor.continue();
} else {
element.innerHTML += "-----------------<br>";
if(callback && "function" === typeof callback) {
callback.call();
}
}
}
}
};
}
this.dump = dump;
}
function mount(name, flags, callback) {
debug.info("mount -->");
indexedDB.deleteDatabase(name);
var format = _(flags).contains("FORMAT");
var onerror = genericErrorHandler(callback);
var openRequest = indexedDB.open(name);
openRequest.onupgradeneeded = function(e) {
var db = e.target.result;
if(db.objectStoreNames.contains(FILE_STORE_NAME)) {
db.deleteObjectStore(FILE_STORE_NAME);
}
var metadata = db.createObjectStore(METADATA_STORE_NAME);
metadata.createIndex(PARENT_INDEX, PARENT_INDEX_KEY_PATH, {unique: false});
metadata.createIndex(NAME_INDEX, NAME_INDEX_KEY_PATH, {unique: true});
metadata.createIndex(OBJECT_ID_INDEX, OBJECT_ID_INDEX_KEY_PATH, {unique: false});
var files = db.createObjectStore(FILE_STORE_NAME);
format = true;
};
openRequest.onsuccess = function(e) {
var db = e.target.result;
var fs = new FileSystem(db);
if(format) {
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
debug.info("format -->");
var clearRequest = store.clear();
clearRequest.onsuccess = function() {
fs.mkdir(transaction, "/", function(error) {
if(error) {
onerror(error);
} else {
debug.info("format <--");
debug.info("mount <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, fs.api());
}
}
});
};
clearRequest.onerror = onerror.bind(null, transaction);
} else {
debug.info("mount <--");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, fs.api());
}
}
};
openRequest.onerror = onerror.bind(null, null);
}
function umount(fs, callback) {
}
var IDBFS = {
mount: mount,
umount: undefined,
path: path
};
return IDBFS;
});

557
src/fs.js
View File

@ -18,6 +18,7 @@ define(function(require) {
var _ = require("lodash");
var Path = require("src/path");
var guid = require("src/guid");
var error = require("src/error");
require("crypto-js/rollups/sha256"); var Crypto = CryptoJS;
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
@ -43,63 +44,55 @@ define(function(require) {
}
}
function runCallback(callback) {
function runcallback(callback) {
if("function" === typeof callback) {
callback.apply(undefined, Array.prototype.slice.call(arguments, 1));
}
}
function hash(string) {
return Crypto.SHA256(string).toString(Crypto.enc.hex);
}
function Data(bytes) {
return {
bytes: bytes || undefined
}
}
function File(size, atime, ctime, mtime, nlinks, type, flags, data, xattrs, links) {
var FILE_MIME_TYPE = "application/file";
var DIRECTORY_MIME_TYPE = "application/directory";
var BINARY_MIME_TYPE = "application/octet-stream";
var JSON_MIME_TYPE = "application/json";
var DEFAULT_DATA_TYPE = BINARY_MIME_TYPE;
function File(mode, data, type, size, version, atime, ctime, mtime, flags, xattrs, links) {
var now = Date.now();
return {
size: size || 0,
atime: atime || now,
ctime: ctime || now,
mtime: mtime || now,
type: type || "application/octet-stream",
mode: mode || FILE_MIME_TYPE,
flags: flags || "",
xattrs: xattrs || {},
data: data || Crypto.SHA256(guid()).toString(Crypto.enc.hex),
links: links || 0
type: type || DEFAULT_DATA_TYPE,
links: links || 0,
version: version || 0,
id: Crypto.SHA256(guid()).toString(Crypto.enc.hex)
}
}
var FILE_ENTRY_MIME_TYPE = "application/file-entry";
function FileEntry(fullpath, file, version) {
return {
name: fullpath,
parent: Path.dirname(fullpath),
file: file || Crypto.SHA256(guid()).toString(Crypto.enc.hex),
type: FILE_ENTRY_MIME_TYPE,
version: version || 0
}
function signature(file) {
// Compute file signature based on file id and version
}
var DIRECTORY_ENTRY_MIME_TYPE = "application/directory-entry";
function DirectoryEntry(fullpath, atime, ctime, mtime, xattrs, version) {
var now = Date.now();
return {
name: fullpath,
parent: Path.dirname(fullpath),
atime: atime || now,
ctime: ctime || now,
mtime: mtime || now,
xattrs: xattrs || {},
type: DIRECTORY_ENTRY_MIME_TYPE,
version: version || 0
}
}
function Stats(size, handle, atime, ctime, mtime, links) {
function Stats(size, data, atime, ctime, mtime, links) {
return {
size: size,
handle: handle,
data: data,
atime: atime,
ctime: ctime,
mtime: mtime,
@ -150,52 +143,64 @@ define(function(require) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
flags = parseFlags(flags);
mode = mode.toUpperCase();
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
var file;
if(!entry) {
var name = Path.basename(fullpath);
var parentpath = Path.dirname(fullpath);
var parenthandle = hash(parentpath);
var getParentRequest = files.get(parenthandle);
getParentRequest.onsuccess = function(e) {
var parent = e.target.result;
var data = parent.data;
var file, filehandle;
if(!_(data).has(name)) {
if(_(flags).contains(OF_CREATE)) {
entry = new FileEntry(fullpath);
filehandle = data[name] = hash(guid());
file = new File();
++ file.links;
var createFileRequest = files.put(file, entry.file);
var createFileRequest = files.put(file, data[name]);
createFileRequest.onsuccess = function(e) {
var createEntryRequest = metadata.put(entry, entry.name);
createEntryRequest.onsuccess = function(e) {
_createFileDescriptor(entry, flags, mode);
var updateParentRequest = files.put(parent, parenthandle);
updateParentRequest.onsuccess = function(e) {
_createFileDescriptor(filehandle, file, flags, mode);
};
createEntryRequest.onerror = function(e) {
runCallback(callback, e);
updateParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
createFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
}
} else {
if(OM_RW === mode && DIRECTORY_ENTRY_MIME_TYPE === entry.type) {
runCallback(callback, new error.EIsDirectory());
}
_createFileDescriptor(entry, flags, mode);
filehandle = data[name];
var getFileRequest = files.get(filehandle);
getFileRequest.onsuccess = function(e) {
file = e.target.result;
if(OM_RW === mode && DIRECTORY_MIME_TYPE === file.mode) {
runcallback(callback, new error.EIsDirectory());
}
_createFileDescriptor(filehandle, file, flags, mode);
};
getFileRequest.onerror = function(e) {
runcallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
getParentRequest.onerror = function(e) {
runcallback(callback, e);
};
function _createFileDescriptor(entry, flags, mode) {
var openfile = new OpenFile(fs, entry, flags, mode);
function _createFileDescriptor(handle, file, flags, mode) {
var openfile = new OpenFile(fs, handle, file, flags, mode);
var descriptor = new FileDescriptor(openfile);
fs._descriptors[descriptor] = openfile;
runCallback(callback, null, descriptor);
runcallback(callback, null, descriptor);
}
};
FileSystem.prototype.close = function close(descriptor, callback) {
@ -208,277 +213,268 @@ define(function(require) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME], IDB_RW);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var getResult = e.target.result;
if(getResult) {
handleError(transaction, new error.EPathExists());
var directoryhandle = hash(fullpath);
var parentpath = Path.dirname(fullpath);
var parenthandle = hash(parentpath);
var getDirectoryRequest = files.get(directoryhandle);
getDirectoryRequest.onsuccess = function(e) {
var directory = e.target.result;
if(directory) {
runcallback(callback, new error.EPathExists());
} else {
var entry = new DirectoryEntry(fullpath);
var putRequest = metadata.put(entry, fullpath);
putRequest.onsuccess = function(e) {
runCallback(callback);
directory = new File(DIRECTORY_MIME_TYPE, {
".": directoryhandle,
"..": parenthandle,
}, JSON_MIME_TYPE, 2);
++ directory.links;
var createDirectoryRequest = files.put(directory, directoryhandle);
createDirectoryRequest.onsuccess = function(e) {
var getParentRequest = files.get(parenthandle);
getParentRequest.onsuccess = function(e) {
var parent = e.target.result;
parent.data[Path.basename(fullpath)] = directoryhandle;
++ parent.version;
var updateParentRequest = files.put(parent, parenthandle);
updateParentRequest.onsuccess = function(e) {
runcallback(callback);
};
updateParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
getParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
putRequest.onerror = function(e) {
runCallback(callback, e);
createDirectoryRequest.onerror = function(e) {
runcallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
}
getDirectoryRequest.onerror = function(e) {
runcallback(callback, e);
};
};
FileSystem.prototype.rmdir = function rmdir(fullpath, callback, optTransaction) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME], IDB_RW);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var parentIndex = metadata.index(PARENT_INDEX);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
if(!entry) {
runCallback(callback, new error.ENoEntry());
var directoryhandle = hash(fullpath);
var getDirectoryRequest = files.get(directoryhandle);
getDirectoryRequest.onsuccess = function(e) {
var directory = e.target.result;
if(!directory) {
runcallback(callback, new error.ENoEntry());
} else {
var contentRequest = parentIndex.get(fullpath);
contentRequest.onsuccess = function(e) {
var contentResult = e.target.result;
if(contentResult) {
runCallback(callback, new error.ENotEmpty());
} else {
var removeRequest = metadata.delete(fullpath);
removeRequest.onsuccess = function(e) {
runCallback(callback);
var data = directory.data;
if(Object.keys(data).length > 2) {
runcallback(callback, new error.ENotEmpty());
} else {
var removeDirectoryRequest = files.delete(directoryhandle);
removeDirectoryRequest.onsuccess = function(e) {
var parentpath = Path.dirname(fullpath);
var parenthandle = hash(parentpath);
var getParentRequest = files.get(parenthandle);
getParentRequest.onsuccess = function(e) {
var parent = e.target.result;
delete parent.data[directoryhandle];
++ parent.version;
var updateParentRequest = files.put(parent, parenthandle);
updateParentRequest.onsuccess = function(e) {
runcallback(callback);
};
updateParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
removeRequest.onerror = function(e) {
runCallback(callback, e);
}
}
};
contentRequest.onerror = function(e) {
runCallback(callback, e);
getParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
removeDirectoryRequest.onerror = function(e) {
runcallback(callback, e);
};
}
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
}
getDirectoryRequest.onerror = function(e) {
runcallback(callback, e);
};
};
FileSystem.prototype.stat = function stat(fullpath, callback, optTransaction) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RO);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RO);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
var stats;
if(!entry) {
runCallback(callback, new error.ENoEntry());
var parentpath = Path.dirname(fullpath);
var parenthandle = hash(parentpath);
var getParentRequest = files.get(parenthandle);
getParentRequest.onsuccess = function(e) {
var parent = e.target.result;
var data = parent.data;
var name = Path.basename(fullpath);
if(!_(data).has(name)) {
runcallback(callback, new error.ENoEntry());
} else {
if(DIRECTORY_ENTRY_MIME_TYPE === entry.type) {
stats = new Stats(undefined, undefined, entry.atime, entry.ctime, entry.mtime, undefined);
runCallback(callback, null, stats);
} else if(FILE_ENTRY_MIME_TYPE === entry.type) {
var getFileRequest = files.get(entry.file);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
stats = new Stats(file.size, entry.file, file.atime, file.ctime, file.mtime, file.links);
runCallback(callback, null, stats);
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
};
}
var filehandle = data[name];
var getFileRequest = files.get(filehandle);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
var stats = new Stats(file.size, file.data, file.atime, file.ctime, file.mtime, file.links);
runcallback(callback, null, stats);
};
getFileRequest.onerror = function(e) {
runcallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
getParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
FileSystem.prototype.link = function link(oldpath, newpath, callback, optTransaction) {
var fs = this;
oldpath = Path.normalize(oldpath);
newpath = Path.normalize(newpath);
var oldparentpath = Path.dirname(oldpath);
var newparentpath = Path.dirname(newpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getOldEntryRequest = metadata.get(oldpath);
getOldEntryRequest.onsuccess = function(e) {
var oldentry = e.target.result;
if(!oldentry) {
runCallback(callback, new error.ENoEntry());
var oldparenthandle = hash(oldparentpath);
var newparenthandle = hash(newparentpath);
var getOldParentRequest = files.get(oldparenthandle);
getOldParentRequest.onsuccess = function(e) {
var oldparent = e.target.result;
if(!oldparent) {
runcallback(callback, new error.ENoEntry());
} else {
var getNewEntryRequest = metadata.get(newpath);
getNewEntryRequest.onsuccess = function(e) {
var newentry = e.target.result;
if(newentry) {
runCallback(callback, new error.EPathExists());
} else {
newentry = new FileEntry(newpath, oldentry.file);
var putNewEntryRequest = metadata.put(newentry, newentry.name);
putNewEntryRequest.onsuccess = function(e) {
var getFileRequest = files.get(newentry.file);
var olddata = oldparent.data;
var filehandle = olddata[Path.basename(oldpath)];
if(!filehandle) {
runcallback(callback, new error.ENoEntry());
} else {
var getNewParentRequest = files.get(newparenthandle);
getNewParentRequest.onsuccess = function(e) {
var newparent = e.target.result;
if(!newparent) {
runcallback(callback, new error.ENoEntry());
} else {
var getFileRequest = files.get(filehandle);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
++ file.links;
var putFileRequest = files.put(file, newentry.file);
putFileRequest.onsuccess = function(e) {
runCallback(callback);
++ file.version;
var updateFileRequest = files.put(file, filehandle);
updateFileRequest.onsuccess = function(e) {
var newdata = newparent.data;
var newname = Path.basename(newpath);
if(_(newdata).has(newname)) {
runcallback(callback, new error.EPathExists());
} else {
newdata[newname] = filehandle;
++ parent.version;
var updateNewParentRequest = files.put(newparent, newparenthandle);
updateNewParentRequest.onsuccess = function(e) {
runcallback(callback);
};
updateNewParentRequest.onerror = function(e) {
runcallback(callback, e);
};
}
};
putFileRequest.onerror = function(e) {
runCallback(callback, e);
updateFileRequest.onerror = function(e) {
runcallback(callback, e);
};
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
};
};
putNewEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
}
};
getNewEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
runcallback(callback, e);
};
}
};
getNewParentRequest.onerror = function(e) {
runcallback(callback, e);
};
}
}
};
getOldEntryRequest.onerror = function(e) {
runCallback(callback, e);
}
getOldParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
FileSystem.prototype.unlink = function unlink(fullpath, callback, optTransaction) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var transaction = optTransaction || new fs.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
if(!entry) {
runCallback(callback, new error.ENoEntry());
} else if(DIRECTORY_ENTRY_MIME_TYPE === entry.type) {
runCallback(callback, new error.EIsDirectory());
var parentpath = Path.dirname(fullpath);
var parenthandle = hash(parentpath);
var getParentRequest = files.get(parenthandle);
getParentRequest.onsuccess = function(e) {
var parent = e.target.result;
var data = parent.data;
var name = Path.basename(fullpath);
if(!_(data).has(name)) {
runcallback(callback, new error.ENoEntry());
} else {
var deleteEntryRequest = metadata.delete(entry.name);
deleteEntryRequest.onsuccess = function(e) {
var getFileRequest = files.get(entry.file);
var filehandle = data[name];
delete data[name];
var updateParentRequest = files.put(parent, parenthandle);
updateParentRequest.onsuccess = function(e) {
var getFileRequest = files.get(filehandle);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
-- file.links;
if(0 === files.links) {
var deleteFileRequest = files.delete(entry.file);
-- file.links;
if(0 === file.links) {
var deleteFileRequest = files.delete(filehandle);
deleteFileRequest.onsuccess = complete;
deleteFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
} else {
var putFileRequest = files.put(file, entry.file);
++ file.version;
var putFileRequest = files.put(file, filehandle);
putFileRequest.onsuccess = complete;
putFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
}
function complete() {
runCallback(callback);
runcallback(callback);
}
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
};
deleteEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
updateParentRequest.onerror = function(e) {
runcallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
getParentRequest.onerror = function(e) {
runcallback(callback, e);
};
};
FileSystem.prototype.setxattr = function setxattr(fullpath, name, value, callback, optTransaction) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
if(!entry) {
runCallback(callback, new error.ENoEntry());
} else {
var getFileRequest = files.get(entry.file);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
file.xattrs[name] = value;
var putFileRequest = files.put(file, entry.file);
putFileRequest.onsuccess = function(e) {
runCallback(callback);
};
putFileRequest.onerror = function(e) {
runCallback(callback, e);
};
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
};
FileSystem.prototype.getxattr = function getxattr(fullpath, name, callback, optTransaction) {
var fs = this;
fullpath = Path.normalize(fullpath);
var transaction = optTransaction || new fs.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RO);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var getEntryRequest = metadata.get(fullpath);
getEntryRequest.onsuccess = function(e) {
var entry = e.target.result;
if(!entry) {
runCallback(callback, new error.ENoEntry());
} else {
var getFileRequest = files.get(entry.file);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
runCallback(callback, null, file.xattrs[name]);
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
};
}
};
getEntryRequest.onerror = function(e) {
runCallback(callback, e);
};
FileSystem.prototype.getxattr = function getxattr(fullpath, name, callback, optTransaction) {
};
function FileSystemContext(fs, optCwd) {
@ -523,7 +519,7 @@ define(function(require) {
this._fs.getxattr(Path.normalize(this._cwd + "/" + path), name, callback);
};
function OpenFile(fs, entry, flags, mode, size) {
function OpenFile(fs, handle, file, flags, mode, size) {
this._fs = fs;
this._pending = 0;
this._valid = true;
@ -532,7 +528,8 @@ define(function(require) {
this._mode = mode;
this._size = size;
this._entry = entry; // Cached entry, might require an update
this._handle = handle;
this._file = file; // Cached file node, might require an update
this._deferred = when.defer();
this._deferred.resolve();
@ -572,17 +569,17 @@ define(function(require) {
var size = file.size;
offset += size;
openfile._position = offset;
runCallback(callback, null, offset);
runcallback(callback, null, offset);
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
} else if(SK_CURRENT === origin) {
openfile._position += offset;
runCallback(callback, null, openfile._position);
runcallback(callback, null, openfile._position);
} else if(SK_SET === origin) {
openfile._position = offset;
runCallback(callback, null, offset);
runcallback(callback, null, offset);
}
};
OpenFile.prototype.read = function read(buffer, callback, optTransaction) {
@ -592,13 +589,13 @@ define(function(require) {
var files = transaction.objectStore(FILE_STORE_NAME);
if(FILE_ENTRY_MIME_TYPE === openfile._entry.type) {
var getDataRequest = files.get(Crypto.SHA256(openfile._handle).toString(Crypto.enc.hex));
if(FILE_MIME_TYPE === openfile._file.mode) {
var getDataRequest = files.get(openfile._file.data);
getDataRequest.onsuccess = function(e) {
var data = e.target.result;
if(!data) {
// There's not file data, so return zero bytes read
runCallback(callback, null, 0, buffer);
runcallback(callback, null, 0, buffer);
} else {
// Make sure we won't read past the end of the file
var bytes = (openfile._position + buffer.length > data.length) ? data.length - openfile._position : buffer.length;
@ -606,14 +603,14 @@ define(function(require) {
var dataView = data.subarray(openfile._position, openfile._position + bytes);
buffer.set(dataView);
openfile._position += bytes;
runCallback(callback, null, bytes, buffer);
runcallback(callback, null, bytes, buffer);
}
};
getDataRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
}
} else if(DIRECTORY_ENTRY_MIME_TYPE === openfile._type) {
runCallback(callback, new error.ENotImplemented());
} else if(DIRECTORY_MIME_TYPE === openfile._file.mode) {
runcallback(callback, new error.ENotImplemented());
}
};
OpenFile.prototype.write = function write(buffer, callback, optTransaction) {
@ -621,17 +618,15 @@ define(function(require) {
var fs = openfile._fs;
if(OM_RO === openfile._mode) {
runCallback(callback, new error.EBadFileDescriptor());
runcallback(callback, new error.EBadFileDescriptor());
return;
}
var transaction = optTransaction || openfile.Transaction([METADATA_STORE_NAME, FILE_STORE_NAME], IDB_RW);
var transaction = optTransaction || openfile.Transaction([FILE_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var files = transaction.objectStore(FILE_STORE_NAME);
var handle = Crypto.SHA256(openfile._handle).toString(Crypto.enc.hex);
var getDataRequest = files.get(handle);
var getDataRequest = files.get(openfile._file.data);
getDataRequest.onsuccess = function(e) {
var data = e.target.result;
var bytes = buffer.length;
@ -643,31 +638,31 @@ define(function(require) {
}
newData.set(buffer, openfile._position);
openfile._position += bytes;
var putDataRequest = files.put(newData, handle);
var putDataRequest = files.put(newData, openfile._file.data);
putDataRequest.onsuccess = function(e) {
var getFileRequest = files.get(openfile._entry.file);
var getFileRequest = files.get(openfile._handle);
getFileRequest.onsuccess = function(e) {
var file = e.target.result;
file.size = size;
file.mtime = Date.now();
var putFileRequest = files.put(file, openfile._entry.file);
var putFileRequest = files.put(file, openfile._handle);
putFileRequest.onsuccess = function(e) {
runCallback(callback, null, size);
runcallback(callback, null, size);
};
putFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
};
getFileRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
};
putDataRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
};
getDataRequest.onerror = function(e) {
runCallback(callback, e);
runcallback(callback, e);
};
};
@ -697,15 +692,10 @@ define(function(require) {
openRequest.onupgradeneeded = function(e) {
var db = e.target.result;
if(db.objectStoreNames.contains(METADATA_STORE_NAME)) {
db.deleteObjectStore(METADATA_STORE_NAME);
}
if(db.objectStoreNames.contains(FILE_STORE_NAME)) {
db.deleteObjectStore(FILE_STORE_NAME);
}
var metadata = db.createObjectStore(METADATA_STORE_NAME);
var files = db.createObjectStore(FILE_STORE_NAME);
metadata.createIndex(PARENT_INDEX, PARENT_INDEX_KEY_PATH, {unique: false});
format = true;
};
@ -715,16 +705,17 @@ define(function(require) {
var context = new fs.Context();
if(format) {
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RW);
var metadata = transaction.objectStore(METADATA_STORE_NAME);
var transaction = db.transaction([FILE_STORE_NAME], IDB_RW);
var clearRequest = metadata.clear();
var files = transaction.objectStore(FILE_STORE_NAME);
var clearRequest = files.clear();
clearRequest.onsuccess = function() {
fs.mkdir("/", function(error) {
if(error) {
runCallback(callback, error);
runcallback(callback, error);
} else {
runCallback(callback, null, context);
runcallback(callback, null, context);
}
}, transaction);
};
@ -732,7 +723,7 @@ define(function(require) {
console.log(e);
};
} else {
runCallback(callback, null, context)
runcallback(callback, null, context)
}
};
openRequest.onerror = function(e) {