diff --git a/examples/test.html b/examples/test.html index c17e890..15fbc59 100644 --- a/examples/test.html +++ b/examples/test.html @@ -3,6 +3,9 @@ + +
+ \ No newline at end of file diff --git a/src/filesystem.js b/src/filesystem.js index 1f732c6..efdc8fd 100644 --- a/src/filesystem.js +++ b/src/filesystem.js @@ -12,7 +12,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND */ define(function(require) { - 'use strict'; + // 'use strict'; var when = require("when"); var debug = require("debug"); @@ -24,6 +24,9 @@ define(function(require) { var TEMPORARY = 0; var PERSISTENT = 1; + var MIME_DIRECTORY = "application/directory"; + var MIME_FILE = "application/file"; + 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); @@ -46,33 +49,32 @@ define(function(require) { return components.join("/"); } - function FileError(code) { - this.code = code; - // FIXME: add a message field with the text error - }; - FileError.NOT_FOUND_ERR = 1; - FileError.SECURITY_ERR = 2; - FileError.ABORT_ERR = 3; - 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 makeDirectoryEntry(name, modtime) { + var parent = ("/" === name) ? null : dirname(name); + return { + "parent": parent, + "name": name, + "content-type": "application/directory", + "last-modified": modtime || Date.now() + } + } - function DirectoryError(code) { - this.code = code; - // FIXME: add a message field with the text error - }; - DirectoryError.PATH_EXISTS_ERR = 1; - DirectoryError.MISSING_PATH_COMPONENT_ERR = 2; + function makeFileEntry(name, oid, size, modtime) { + var parent = ("/" === name) ? null : dirname(name); + return { + "parent": parent, + "name": name, + "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 Transaction(db, scope, mode) { var id = this.id = guid(); @@ -98,14 +100,14 @@ define(function(require) { var deferred = when.defer(); this._IdbRequest.onsuccess = function(e) { deferred.resolve(e); - clearPending(id); + // clearPending(id); }; this._IdbRequest.onerror = function(e) { deferred.reject(e); - clearPending(id); + // clearPending(id); }; this.then = deferred.promise.then; - queuePending(this, id); + // queuePending(this, id); } function OpenDBRequest(request, upgrade) { @@ -128,8 +130,11 @@ define(function(require) { } var fs = this; + fs.id = guid(); var FILE_STORE_NAME = "files"; var METADATA_STORE_NAME = "metadata"; + var NAME_INDEX = "name"; + var PARENT_INDEX = "parent"; fs.name = name || "default"; fs.pending = {}; @@ -185,8 +190,8 @@ define(function(require) { db.deleteObjectStore(FILE_STORE_NAME); } var metadata = db.createObjectStore(METADATA_STORE_NAME); - metadata.createIndex("parent", "parent", {unique: false}); - metadata.createIndex("name", "name", {unique: true}); + metadata.createIndex(NAME_INDEX, "parent", {unique: false}); + metadata.createIndex(NAME_INDEX, "name", {unique: true}); var files = db.createObjectStore(FILE_STORE_NAME); format = true; @@ -200,7 +205,7 @@ define(function(require) { debug.info("format required"); var clearRequest = new Request(store.clear()); clearRequest.then(function() { - mkdir("/", transaction).then(function() { + mkdir(transation, "/").then(function() { debug.info("format complete"); }); }); @@ -209,49 +214,75 @@ define(function(require) { // API - var mkdir = this.mkdir = function mkdir(name, transaction) { - debug.info("mkdir invoked"); + function updateLastModified(transaction, name, timestamp) { + debug.info("updateLastModified invoked"); var deferred = when.defer(); - var transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RW); - var store = transaction.objectStore(METADATA_STORE_NAME); - var nameIndex = store.index("name"); - - 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); + transaction = transaction || new Transaction(fs.db, [METADATA_STORE_NAME], RW); + timestamp = timestamp || Date.now(); 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("stat error: MISSING_PATH_COMPONENT_ERR"); - deferred.reject(new DirectoryError(DirectoryError.MISSING_PATH_COMPONENT_ERR)); + debug.info("updateLastModified error: E_NOENT"); + 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 { debug.info("stat complete"); deferred.resolve(result); @@ -259,11 +290,336 @@ define(function(require) { }); 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) + "
"; + cursor.continue(); + }; + } + }; + } + this.dump = dump; } + // File system states FileSystem.READY = 0; FileSystem.PENDING = 1; 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) + "
"; + 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; }); \ No newline at end of file