Refactoring away from promises.

This commit is contained in:
Alan Kligman 2012-10-11 02:20:58 -04:00
parent 3daebb73ad
commit f762cf74b1
2 changed files with 453 additions and 72 deletions

View File

@ -3,6 +3,9 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body>
<div id="stdout"></div>
</body>
<script src="../lib/require.js"></script> <script src="../lib/require.js"></script>
<script> <script>
require.config({ require.config({
@ -12,14 +15,36 @@ require.config({
} }
}); });
require(["../../javascript-debug/debug", "src/filesystem"], function(debug, FileSystem) { require(["../../javascript-debug/debug", "src/filesystem"], function(debug, IDBFS) {
var fs = new FileSystem(undefined, true);
IDBFS.mount("default", function(error, fs) {
console.log("mount complete");
fs.mkdir("/tmp", function(error) {
fs.dump(document.getElementById("stdout"));
});
}, true);
/*
var fs = new IdbFS(undefined, true);
fs.then(function(fs) { fs.then(function(fs) {
fs.mkdir("/tmp"); fs.mkdir("/tmp").then(function(result) {
var d = fs.open("/tmp/0", IdbFS.O_CREATE, IdbFS.FMODE_RW);
d.then(function(fd) {
fs.dump(document.getElementById("stdout"));
}, function(e) {
console.log(e);
});
}, function(error) {
console.log(error);
});
fs.stat("/tmp").then(function(result) { fs.stat("/tmp").then(function(result) {
console.log(result); console.log(result);
}, function(error) {
console.log(error);
}); });
}); });
*/
}); });
</script> </script>
</html> </html>

View File

@ -12,7 +12,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
*/ */
define(function(require) { define(function(require) {
'use strict'; // 'use strict';
var when = require("when"); var when = require("when");
var debug = require("debug"); var debug = require("debug");
@ -24,6 +24,9 @@ define(function(require) {
var TEMPORARY = 0; var TEMPORARY = 0;
var PERSISTENT = 1; var PERSISTENT = 1;
var MIME_DIRECTORY = "application/directory";
var MIME_FILE = "application/file";
function guid() { function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
@ -46,33 +49,32 @@ define(function(require) {
return components.join("/"); return components.join("/");
} }
function FileError(code) { function makeDirectoryEntry(name, modtime) {
this.code = code; var parent = ("/" === name) ? null : dirname(name);
// FIXME: add a message field with the text error return {
}; "parent": parent,
FileError.NOT_FOUND_ERR = 1; "name": name,
FileError.SECURITY_ERR = 2; "content-type": "application/directory",
FileError.ABORT_ERR = 3; "last-modified": modtime || Date.now()
FileError.NOT_READABLE_ERR = 4; }
FileError.ENCODING_ERR = 5; }
FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
FileError.INVALID_STATE_ERR = 7;
FileError.SYNTAX_ERR = 8;
FileError.INVALID_MODIFICATION_ERR = 9;
FileError.QUOTA_EXCEEDED_ERR = 10;
FileError.TYPE_MISMATCH_ERR = 11;
FileError.PATH_EXISTS_ERR = 12;
function DirectoryError(code) { function makeFileEntry(name, oid, size, modtime) {
this.code = code; var parent = ("/" === name) ? null : dirname(name);
// FIXME: add a message field with the text error return {
}; "parent": parent,
DirectoryError.PATH_EXISTS_ERR = 1; "name": name,
DirectoryError.MISSING_PATH_COMPONENT_ERR = 2; "content-type": "application/file",
"last-modified": modtime || Date.now(),
"size": size || 0,
"object-id": oid || guid()
}
}
var RO = "readonly",
RW = "readwrite";
/*
function FileSystem(name, optFormat) { function FileSystem(name, optFormat) {
function Transaction(db, scope, mode) { function Transaction(db, scope, mode) {
var id = this.id = guid(); var id = this.id = guid();
@ -98,14 +100,14 @@ define(function(require) {
var deferred = when.defer(); var deferred = when.defer();
this._IdbRequest.onsuccess = function(e) { this._IdbRequest.onsuccess = function(e) {
deferred.resolve(e); deferred.resolve(e);
clearPending(id); // clearPending(id);
}; };
this._IdbRequest.onerror = function(e) { this._IdbRequest.onerror = function(e) {
deferred.reject(e); deferred.reject(e);
clearPending(id); // clearPending(id);
}; };
this.then = deferred.promise.then; this.then = deferred.promise.then;
queuePending(this, id); // queuePending(this, id);
} }
function OpenDBRequest(request, upgrade) { function OpenDBRequest(request, upgrade) {
@ -128,8 +130,11 @@ define(function(require) {
} }
var fs = this; var fs = this;
fs.id = guid();
var FILE_STORE_NAME = "files"; var FILE_STORE_NAME = "files";
var METADATA_STORE_NAME = "metadata"; var METADATA_STORE_NAME = "metadata";
var NAME_INDEX = "name";
var PARENT_INDEX = "parent";
fs.name = name || "default"; fs.name = name || "default";
fs.pending = {}; fs.pending = {};
@ -185,8 +190,8 @@ define(function(require) {
db.deleteObjectStore(FILE_STORE_NAME); db.deleteObjectStore(FILE_STORE_NAME);
} }
var metadata = db.createObjectStore(METADATA_STORE_NAME); var metadata = db.createObjectStore(METADATA_STORE_NAME);
metadata.createIndex("parent", "parent", {unique: false}); metadata.createIndex(NAME_INDEX, "parent", {unique: false});
metadata.createIndex("name", "name", {unique: true}); metadata.createIndex(NAME_INDEX, "name", {unique: true});
var files = db.createObjectStore(FILE_STORE_NAME); var files = db.createObjectStore(FILE_STORE_NAME);
format = true; format = true;
@ -200,7 +205,7 @@ define(function(require) {
debug.info("format required"); debug.info("format required");
var clearRequest = new Request(store.clear()); var clearRequest = new Request(store.clear());
clearRequest.then(function() { clearRequest.then(function() {
mkdir("/", transaction).then(function() { mkdir(transation, "/").then(function() {
debug.info("format complete"); debug.info("format complete");
}); });
}); });
@ -209,49 +214,75 @@ define(function(require) {
// API // API
var mkdir = this.mkdir = function mkdir(name, transaction) { function updateLastModified(transaction, name, timestamp) {
debug.info("mkdir invoked"); debug.info("updateLastModified invoked");
var deferred = when.defer(); var deferred = when.defer();
var transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RW); transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RW);
timestamp = timestamp || Date.now();
var store = transaction.objectStore(METADATA_STORE_NAME); var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index("name"); var nameIndex = store.index(NAME_INDEX);
var getRequest = new Request(nameIndex.get(name));
getRequest.then(function(e) {
var result = e.target.result;
if(result) {
debug.info("mkdir error: PATH_EXISTS_ERR");
deferred.reject(new DirectoryError(DirectoryError.PATH_EXISTS_ERR));
} else {
var parent = ("/" === name) ? null : dirname(name);
var directoryRequest = new Request(store.put({
"parent": parent,
"name": name,
"last-modified": Date.now(),
"content-type": "application/directory"
}, name));
directoryRequest.then(function(e) {
debug.info("mkdir complete");
deferred.resolve();
});
}
});
return deferred.promise;
};
var stat = this.stat = function stat(name, transaction) {
debug.info("stat invoked");
var deferred = when.defer();
var transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RO);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index("name");
var getRequest = new Request(nameIndex.get(name)); var getRequest = new Request(nameIndex.get(name));
getRequest.then(function(e) { getRequest.then(function(e) {
var result = e.target.result; var result = e.target.result;
if(!result) { if(!result) {
debug.info("stat error: MISSING_PATH_COMPONENT_ERR"); debug.info("updateLastModified error: E_NOENT");
deferred.reject(new DirectoryError(DirectoryError.MISSING_PATH_COMPONENT_ERR)); deferred.reject(new FileSystemError(T_NONE, E_NOENT));
} else {
result["last-modified"] = timestamp;
var updateRequest = new Request(store.put(result, result.name));
updateRequest.then(function(e) {
debug.info("updateLastModified complete");
deferred.resolve();
});
}
});
return deferred.promise;
}
function mkdir(transaction, name) {
debug.info("mkdir invoked");
var deferred = when.defer();
transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var getRequest = new Request(nameIndex.get(name));
getRequest.then(function(e) {
var result = e.target.result;
if(result) {
debug.info("mkdir error: E_EXIST");
deferred.reject(new FileSystemError(T_MKDIR, E_EXIST));
} else {
var entry = makeDirectoryEntry(name, Date.now());
var directoryRequest = new Request(store.put(entry, name));
directoryRequest.then(function(e) {
debug.info("mkdir complete");
deferred.resolve();
}, function(e) {
debug.info("mkdir error: " + e);
deferred.reject(e);
});
}
});
return deferred.promise;
}
fs.mkdir = mkdir.bind(null, null);
function stat(transaction, name) {
debug.info("stat invoked");
var deferred = when.defer();
transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RO);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var getRequest = new Request(nameIndex.get(name));
getRequest.then(function(e) {
var result = e.target.result;
if(!result) {
debug.info("stat error: E_NOENT");
deferred.reject(new FileSystemError(T_STAT, E_NOENT));
} else { } else {
debug.info("stat complete"); debug.info("stat complete");
deferred.resolve(result); deferred.resolve(result);
@ -259,11 +290,336 @@ define(function(require) {
}); });
return deferred.promise; return deferred.promise;
} }
fs.stat = stat.bind(null, null);
function OpenFileDescription(filesystem, name, oid, flags, mode) {
this.fs = filesystem;
this.name = name;
this.oid = oid;
this.fags = flags;
this.mode = mode;
this.offset = 0;
} }
var fds = {};
function FileDescriptor(ofd) {
this.descriptor = guid();
fds[this.descriptor] = ofd;
}
// Close a file
FileDescriptor.prototype.close = function close() {
debug.info("close invoked");
var deferred = when();
delete fds[this.descriptor];
debug.info("close complete");
return deferred.promise;
};
// Read from a file
FileDescriptor.prototype.read = function read(bytes, buffer) {
};
// Write to a file
FileDescriptor.prototype.write = function write(bytes, buffer) {
};
// Set absolute offset
FileDescriptor.prototype.seek = function seek(offset) {
this.offset = offset;
};
// Set relative offset (from current offset)
FileDescriptor.prototype.rseek = function rseek(offset) {
this.offset += offset;
};
function open(name, flags, mode) {
debug.info("open invoked");
var deferred = when.defer();
var transaction = new Transaction(fs.db, [METADATA_STORE_NAME], RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var metadataRequest = new Request(nameIndex.get(name));
metadataRequest.then(function(e) {
var result = e.target.result;
if(!result && !(flags & FileSystem.O_CREATE)) {
debug.info("open error: E_NOENT");
deferred.reject(new FileSystemError(T_OPEN, E_NOENT));
} else if(result && "application/directory" === result["content-type"] && mode === FileSystem.FMODE_RW) {
debug.info("open error: E_ISDIR");
deferred.reject(new FileSystemError(T_OPEN, E_ISDIR));
} else {
function complete() {
var ofd = new OpenFileDescription(fs, name, result["object-id"], flags, mode);
var fd = new FileDescriptor(ofd);
debug.info("open complete");
deferred.resolve(fd);
}
if(!result) {
result = makeFileEntry(name);
var makeRequest = new Request(store.put(result, name));
makeRequest.then(complete);
} else {
complete();
}
}
});
return deferred.promise;
}
fs.open = open;
function dump(element) {
element.innerHTML = "";
var transaction = new Transaction(fs.db, [METADATA_STORE_NAME], RO);
var store = transaction.objectStore(METADATA_STORE_NAME);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function(e) {
var cursor = e.target.result;
if(cursor) {
var getRequest = store.get(cursor.key);
getRequest.onsuccess = function(e) {
var result = e.target.result;
element.innerHTML += JSON.stringify(result) + "<br>";
cursor.continue();
};
}
};
}
this.dump = dump;
}
// File system states
FileSystem.READY = 0; FileSystem.READY = 0;
FileSystem.PENDING = 1; FileSystem.PENDING = 1;
FileSystem.UNINITIALIZED = 2; FileSystem.UNINITIALIZED = 2;
// Open flags
FileSystem.O_CREATE = 0x1;
FileSystem.O_TRUNCATE = 0x2;
// Open modes
FileSystem.FMODE_RO = 0;
FileSystem.FMODE_RW = 1;
*/
/////////////////////////////////////////////////////////////////////////////
return FileSystem; 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 IDB_RO = "readonly";
var IDB_RW = "readwrite";
function IDBFSError(type, code) {
this.type = type;
this.code = code;
}
// Types
var T_OPEN = 0x0;
var T_MKDIR = 0x1;
var T_STAT = 0x2;
var T_NONE = 0x3;
// Codes
var E_EXIST = 0x0;
var E_ISDIR = 0x1;
var E_NOENT = 0x2;
function genericIDBErrorHandler(scope, callback) {
return function(error) {
debug.error("[" + scope + "] error: ", error);
if(callback && "function" === typeof callback) {
callback.call(undefined, error);
}
}
}
function FileSystem(db) {
var fs = this;
var pending = {}; // Pending transactions
var fds = {}; // Open file descriptors
// Internal prototypes
function OpenFileDescription(name, oid, flags, mode) {
this.fs = filesystem;
this.name = name;
this.oid = oid;
this.flags = flags;
this.mode = mode;
this.pointer = 0;
}
function FileDescriptor(ofd) {
this.descriptor = guid();
fds[this.descriptor] = ofd;
function read(buffer, bytes, callback) {
}
function write(buffer, bytes, callback) {
}
function seek(offset, whence, callback) {
}
}
// API
function open(pathname, flags, mode, callback) {
}
function close(fd, callback) {
}
function mkdir(transaction, pathname, callback) {
debug.info("mkdir invoked");
transaction = transaction || db.transaction([METADATA_STORE_NAME], IDB_RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
var nameIndex = store.index(NAME_INDEX);
var onerror = genericIDBErrorHandler("mkdir", callback);
var getRequest = nameIndex.get(pathname);
getRequest.onsuccess = function(e) {
var result = e.target.result;
if(result) {
onerror(new IDBFSError(T_MKDIR, E_EXIST));
} else {
var entry = makeDirectoryEntry(pathname, Date.now());
var directoryRequest = store.put(entry, pathname);
directoryRequest.onsuccess = function(e) {
debug.info("mkdir complete");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined);
}
};
directoryRequest.onerror = onerror;
}
};
getRequest.onerror = onerror;
}
function rmdir(pathname, callback) {
}
function stat(transaction, pathname, callback) {
}
function link(oldpath, newpath, callback) {
}
function unlink(pathname, callback) {
}
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) {
element.innerHTML = "";
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RO);
var store = transaction.objectStore(METADATA_STORE_NAME);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function(e) {
var cursor = e.target.result;
if(cursor) {
var getRequest = store.get(cursor.key);
getRequest.onsuccess = function(e) {
var result = e.target.result;
element.innerHTML += JSON.stringify(result) + "<br>";
cursor.continue();
};
}
};
}
this.dump = dump;
}
function mount(name, callback, optFormat) {
optFormat = (undefined === optFormat) ? false : optFormat;
var onerror = genericIDBErrorHandler("mount", callback);
var openRequest = indexedDB.open(name);
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);
metadata.createIndex(PARENT_INDEX, PARENT_INDEX_KEY_PATH, {unique: false});
metadata.createIndex(NAME_INDEX, NAME_INDEX_KEY_PATH, {unique: true});
var files = db.createObjectStore(FILE_STORE_NAME);
optFormat = true;
};
openRequest.onsuccess = function(e) {
var db = e.target.result;
var fs = new FileSystem(db);
if(optFormat) {
var transaction = db.transaction([METADATA_STORE_NAME], IDB_RW);
var store = transaction.objectStore(METADATA_STORE_NAME);
debug.info("format required");
var clearRequest = store.clear();
clearRequest.onsuccess = function() {
fs.mkdir(transaction, "/", function(error) {
if(error) {
onerror(error);
} else {
debug.info("format complete");
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, fs.api());
}
}
});
};
clearRequest.onerror = onerror;
} else {
if(callback && "function" === typeof callback) {
callback.call(undefined, undefined, fs.api());
}
}
};
openRequest.onerror = onerror;
}
function umount(fs, callback) {
}
var IDBFS = {
mount: mount,
umount: undefined
};
return IDBFS;
}); });