diff --git a/libs/async_storage.js b/libs/async_storage.js deleted file mode 100644 index 2282ae9a..00000000 --- a/libs/async_storage.js +++ /dev/null @@ -1,179 +0,0 @@ -'use strict'; - -/** - * This module defines an asynchronous version of the localStorage API, backed by - * an IndexedDB database. It creates a global asyncStorage object that has - * methods like the localStorage object. - * - * To store a value use setItem: - * - * asyncStorage.setItem('key', 'value'); - * - * If you want confirmation that the value has been stored, pass a callback - * function as the third argument: - * - * asyncStorage.setItem('key', 'newvalue', function() { - * console.log('new value stored'); - * }); - * - * To read a value, call getItem(), but note that you must supply a callback - * function that the value will be passed to asynchronously: - * - * asyncStorage.getItem('key', function(value) { - * console.log('The value of key is:', value); - * }); - * - * Note that unlike localStorage, asyncStorage does not allow you to store and - * retrieve values by setting and querying properties directly. You cannot just - * write asyncStorage.key; you have to explicitly call setItem() or getItem(). - * - * removeItem(), clear(), length(), and key() are like the same-named methods of - * localStorage, but, like getItem() and setItem() they take a callback - * argument. - * - * The asynchronous nature of getItem() makes it tricky to retrieve multiple - * values. But unlike localStorage, asyncStorage does not require the values you - * store to be strings. So if you need to save multiple values and want to - * retrieve them together, in a single asynchronous operation, just group the - * values into a single object. The properties of this object may not include - * DOM elements, but they may include things like Blobs and typed arrays. - */ - -var asyncStorage = (function() { - var indexedDB = window.indexedDB || window.webkitIndexedDB || - window.mozIndexedDB || window.msIndexedDB; - - var DBNAME = 'asyncStorage'; - var DBVERSION = 1; - var STORENAME = 'keyvaluepairs'; - var db = null; - - function withDatabase(f) { - if (db) { - f(); - } else { - var openreq = indexedDB.open(DBNAME, DBVERSION); - openreq.onerror = function withStoreOnError() { - console.error('asyncStorage: can\'t open database:', - openreq.error.name); - }; - openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() { - // First time setup: create an empty object store - openreq.result.createObjectStore(STORENAME); - }; - openreq.onsuccess = function withStoreOnSuccess() { - db = openreq.result; - f(); - }; - } - } - - function withStore(type, callback, oncomplete) { - withDatabase(function() { - var transaction = db.transaction(STORENAME, type); - if (oncomplete) { - transaction.oncomplete = oncomplete; - } - callback(transaction.objectStore(STORENAME)); - }); - } - - function getItem(key, callback) { - var req; - withStore('readonly', function getItemBody(store) { - req = store.get(key); - req.onerror = function getItemOnError() { - console.error('Error in asyncStorage.getItem(): ', req.error.name); - }; - }, function onComplete() { - var value = req.result; - if (value === undefined) { - value = null; - } - callback(value); - }); - } - - function setItem(key, value, callback) { - withStore('readwrite', function setItemBody(store) { - var req = store.put(value, key); - req.onerror = function setItemOnError() { - console.error('Error in asyncStorage.setItem(): ', req.error.name); - }; - }, callback); - } - - function removeItem(key, callback) { - withStore('readwrite', function removeItemBody(store) { - var req = store.delete(key); - req.onerror = function removeItemOnError() { - console.error('Error in asyncStorage.removeItem(): ', req.error.name); - }; - }, callback); - } - - function clear(callback) { - withStore('readwrite', function clearBody(store) { - var req = store.clear(); - req.onerror = function clearOnError() { - console.error('Error in asyncStorage.clear(): ', req.error.name); - }; - }, callback); - } - - function length(callback) { - var req; - withStore('readonly', function lengthBody(store) { - req = store.count(); - req.onerror = function lengthOnError() { - console.error('Error in asyncStorage.length(): ', req.error.name); - }; - }, function onComplete() { - callback(req.result); - }); - } - - function key(n, callback) { - if (n < 0) { - callback(null); - return; - } - - var req; - withStore('readonly', function keyBody(store) { - var advanced = false; - req = store.openCursor(); - req.onsuccess = function keyOnSuccess() { - var cursor = req.result; - if (!cursor) { - // this means there weren't enough keys - return; - } - if (n === 0 || advanced) { - // Either 1) we have the first key, return it if that's what they - // wanted, or 2) we've got the nth key. - return; - } - - // Otherwise, ask the cursor to skip ahead n records - advanced = true; - cursor.advance(n); - }; - req.onerror = function keyOnError() { - console.error('Error in asyncStorage.key(): ', req.error.name); - }; - }, function onComplete() { - var cursor = req.result; - callback(cursor ? cursor.key : null); - }); - } - - return { - getItem: getItem, - setItem: setItem, - removeItem: removeItem, - clear: clear, - length: length, - key: key - }; -})(); diff --git a/libs/fs.js b/libs/fs.js index 44ba6c49..03655dc4 100644 --- a/libs/fs.js +++ b/libs/fs.js @@ -1,6 +1,88 @@ 'use strict'; +var DEBUG_FS = false; + var fs = (function() { + var Store = function() { + this.map = new Map(); + this.db = null; + }; + + Store.DBNAME = "asyncStorage"; + Store.DBVERSION = 1; + Store.DBSTORENAME = "keyvaluepairs"; + + Store.prototype.init = function(cb) { + var openreq = indexedDB.open(Store.DBNAME, Store.DBVERSION); + openreq.onerror = function() { + console.error("error opening database: " + openreq.error.name); + }; + openreq.onupgradeneeded = function() { + openreq.result.createObjectStore(Store.DBSTORENAME); + }; + openreq.onsuccess = (function() { + this.db = openreq.result; + cb(); + }).bind(this); + }; + + Store.prototype.getItem = function(key, cb) { + if (this.map.has(key)) { + var value = this.map.get(key); + window.setZeroTimeout(function() { cb(value) }); + } else { + var transaction = this.db.transaction(Store.DBSTORENAME, "readonly"); + var objectStore = transaction.objectStore(Store.DBSTORENAME); + var req = objectStore.get(key); + req.onerror = function() { + console.error("Error getting " + key + ": " + req.error.name); + }; + transaction.oncomplete = (function() { + var value = req.result; + if (value === undefined) { + value = null; + } + this.map.set(key, value); + cb(value); + }).bind(this); + } + }; + + Store.prototype.setItem = function(key, value) { + this.map.set(key, value); + + var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite"); + var objectStore = transaction.objectStore(Store.DBSTORENAME); + var req = objectStore.put(value, key); + req.onerror = function() { + console.error("Error putting " + key + ": " + req.error.name); + }; + }; + + Store.prototype.removeItem = function(key) { + this.map.delete(key); + + var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite"); + var objectStore = transaction.objectStore(Store.DBSTORENAME); + var req = objectStore.delete(key); + req.onerror = function() { + console.error("Error deleting " + key + ": " + req.error.name); + }; + }; + + Store.prototype.clear = function() { + this.map.clear(); + + var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite"); + var objectStore = transaction.objectStore(Store.DBSTORENAME); + var req = objectStore.clear(); + req.onerror = function() { + console.error("Error clearing store: " + req.error.name); + }; + } + + var store = new Store(); + var FileBuffer = function(array) { this.array = array; this.contentSize = array.byteLength; @@ -75,25 +157,31 @@ var fs = (function() { return path.slice(path.lastIndexOf("/") + 1); } - function init(cb) { - asyncStorage.getItem("/", function(data) { + function initRootDir(cb) { + store.getItem("/", function(data) { if (data) { cb(); } else { - asyncStorage.setItem("/", [], function() { - setStat("/", { mtime: Date.now(), isDir: true }, cb); - }); + store.setItem("/", []); + setStat("/", { mtime: Date.now(), isDir: true }); + cb(); } }); } + function init(cb) { + store.init(function() { + initRootDir(cb || function() {}); + }); + } + var openedFiles = [null, null, null]; - var fileStats = {}; function open(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs open " + path); } - asyncStorage.getItem(path, function(blob) { + store.getItem(path, function(blob) { if (blob == null || !(blob instanceof Blob)) { cb(-1); } else { @@ -112,20 +200,11 @@ var fs = (function() { }); } - function close(fd, cb) { + function close(fd) { if (fd >= 0 && openedFiles[fd]) { - flush(fd, function() { - // Replace descriptor object with null value instead of removing it from - // the array so we don't change the indexes of the other objects. - openedFiles.splice(fd, 1, null); - if (cb) { - cb(); - } - }); - } else { - if (cb) { - cb(); - } + if (DEBUG_FS) { console.log("fs close " + openedFiles[fd].path); } + flush(fd); + openedFiles.splice(fd, 1, null); } } @@ -133,6 +212,7 @@ var fs = (function() { if (!openedFiles[fd]) { return null; } + if (DEBUG_FS) { console.log("fs read " + openedFiles[fd].path); } var buffer = openedFiles[fd].buffer; @@ -153,6 +233,8 @@ var fs = (function() { } function write(fd, data, from) { + if (DEBUG_FS) { console.log("fs write " + openedFiles[fd].path); } + if (typeof from == "undefined") { from = openedFiles[fd].position; } @@ -173,7 +255,6 @@ var fs = (function() { file.position = from + data.byteLength; file.stat = { mtime: Date.now(), isDir: false, size: buffer.contentSize }; file.dirty = true; - fileStats[file.path] = file.stat; } function getpos(fd) { @@ -192,22 +273,22 @@ var fs = (function() { return openedFiles[fd].buffer.contentSize; } - function flush(fd, cb) { + function flush(fd) { + if (DEBUG_FS) { console.log("fs flush " + openedFiles[fd].path); } + + var openedFile = openedFiles[fd]; + // Bail early if the file has not been modified. - if (!openedFiles[fd].dirty) { - cb(); + if (!openedFile.dirty) { return; } - var blob = new Blob([openedFiles[fd].buffer.getContent()]); - asyncStorage.setItem(openedFiles[fd].path, blob, function() { - openedFiles[fd].dirty = false; - if (openedFiles[fd].stat) { - setStat(openedFiles[fd].path, openedFiles[fd].stat, cb); - } else { - cb(); - } - }); + var blob = new Blob([openedFile.buffer.getContent()]); + store.setItem(openedFile.path, blob); + openedFile.dirty = false; + if (openedFile.stat) { + setStat(openedFile.path, openedFile.stat); + } } function flushAll() { @@ -230,8 +311,9 @@ var fs = (function() { function list(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs list " + path); } - asyncStorage.getItem(path, function(files) { + store.getItem(path, function(files) { if (files == null || files instanceof Blob) { cb(null); } else { @@ -242,6 +324,7 @@ var fs = (function() { function exists(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs exists " + path); } stat(path, function(stat) { cb(stat ? true : false); @@ -250,13 +333,13 @@ var fs = (function() { function truncate(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs truncate " + path); } stat(path, function(stat) { if (stat && !stat.isDir) { - asyncStorage.setItem(path, new Blob(), function() { - setStat(path, { mtime: Date.now(), isDir: false, size: 0 }); - cb(true); - }); + store.setItem(path, new Blob()); + setStat(path, { mtime: Date.now(), isDir: false, size: 0 }); + cb(true); } else { cb(false); } @@ -264,16 +347,19 @@ var fs = (function() { } function ftruncate(fd, size) { + if (DEBUG_FS) { console.log("fs ftruncate " + openedFiles[fd].path); } + var file = openedFiles[fd]; if (size != file.buffer.contentSize) { file.buffer.setSize(size); file.dirty = true; - fileStats[file.path] = file.stat = { mtime: Date.now(), isDir: false, size: size }; + file.stat = { mtime: Date.now(), isDir: false, size: size }; } } function remove(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs remove " + path); } if (openedFiles.findIndex(function(file) { return file && file.path === path; }) != -1) { setZeroTimeout(function() { cb(false); }); @@ -298,13 +384,10 @@ var fs = (function() { } files.splice(index, 1); - asyncStorage.setItem(dir, files, function() { - asyncStorage.removeItem(path, function() { - removeStat(path, function() { - cb(true); - }); - }); - }); + store.setItem(dir, files); + store.removeItem(path); + removeStat(path); + cb(true); }); }); } @@ -320,43 +403,39 @@ var fs = (function() { } files.push(name); - asyncStorage.setItem(dir, files, function() { - asyncStorage.setItem(path, data, function() { - cb(true); - }); - }); + store.setItem(dir, files); + store.setItem(path, data); + cb(true); }); } function create(path, blob, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs create " + path); } createInternal(path, blob, function(created) { if (created) { - setStat(path, { mtime: Date.now(), isDir: false, size: blob.size }, function() { - cb(created); - }); - } else { - cb(created); + setStat(path, { mtime: Date.now(), isDir: false, size: blob.size }); } + cb(created); }); } function mkdir(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs mkdir " + path); } createInternal(path, [], function(created) { if (created) { - setStat(path, { mtime: Date.now(), isDir: true }, function() { - cb(created); - }); - } else { - cb(created); + setStat(path, { mtime: Date.now(), isDir: true }); } + cb(created); }); } function mkdirp(path, cb) { + if (DEBUG_FS) { console.log("fs mkdirp " + path); } + if (path[0] !== "/") { console.error("mkdirp called on relative path: " + path); cb(false); @@ -400,19 +479,12 @@ var fs = (function() { function size(path, cb) { path = normalizePath(path); + if (DEBUG_FS) { console.log("fs size " + path); } - if (fileStats[path] && typeof fileStats[path].size != "undefined") { - cb(fileStats[path].size); - return; - } - - asyncStorage.getItem(path, function(blob) { + store.getItem(path, function(blob) { if (blob == null || !(blob instanceof Blob)) { cb(-1); } else { - if (fileStats[path]) { - fileStats[path].size = blob.size; - } cb(blob.size); } }); @@ -423,6 +495,7 @@ var fs = (function() { function rename(oldPath, newPath, cb) { oldPath = normalizePath(oldPath); newPath = normalizePath(newPath); + if (DEBUG_FS) { console.log("fs rename " + oldPath + " -> " + newPath); } if (openedFiles.findIndex(function(file) { return file && file.path === oldPath; }) != -1) { setZeroTimeout(function() { cb(false); }); @@ -435,7 +508,7 @@ var fs = (function() { return; } - asyncStorage.getItem(oldPath, function(data) { + store.getItem(oldPath, function(data) { if (data == null) { cb(false); return; @@ -457,24 +530,21 @@ var fs = (function() { }); } - function setStat(path, stat, cb) { - fileStats[path] = stat; - asyncStorage.setItem("!" + path, stat, cb); + function setStat(path, stat) { + if (DEBUG_FS) { console.log("fs setStat " + path); } + + store.setItem("!" + path, stat); } - function removeStat(path, cb) { - delete fileStats[path]; - asyncStorage.removeItem("!" + path, cb); + function removeStat(path) { + if (DEBUG_FS) { console.log("fs removeStat " + path); } + + store.removeItem("!" + path); } function stat(path, cb) { path = normalizePath(path); - - var stat = fileStats[path]; - if (stat) { - setZeroTimeout(function() { cb(stat); }); - return; - } + if (DEBUG_FS) { console.log("fs stat " + path); } var file = openedFiles.find(function (file) { return file && file.stat && file.path === path }); if (file) { @@ -482,12 +552,12 @@ var fs = (function() { return; } - asyncStorage.getItem("!" + path, function(stat) { - if (stat) { - fileStats[path] = stat; - } - cb(stat); - }); + store.getItem("!" + path, cb); + } + + function clear(cb) { + store.clear(); + initRootDir(cb || function() {}); } return { @@ -512,5 +582,6 @@ var fs = (function() { size: size, rename: rename, stat: stat, + clear: clear, }; })(); diff --git a/main.html b/main.html index 080d49de..8c1b5bd1 100644 --- a/main.html +++ b/main.html @@ -33,7 +33,6 @@ - diff --git a/main.js b/main.js index a6034208..40095359 100644 --- a/main.js +++ b/main.js @@ -63,7 +63,7 @@ if (urlParams.pushConn && urlParams.pushMidlet) { var initFS = new Promise(function(resolve, reject) { fs.init(resolve); }).then(function() { - return Promise.all([ + var fsPromises = [ new Promise(function(resolve, reject) { fs.mkdir("/Persistent", resolve); }), @@ -81,7 +81,27 @@ var initFS = new Promise(function(resolve, reject) { } }); }), - ]); + ]; + + if (MIDP.midletClassName == "RunTests") { + fsPromises.push( + new Promise(function(resolve, reject) { + fs.exists("/_test.ks", function(exists) { + if (exists) { + resolve(); + } else { + load("certs/_test.ks", "blob").then(function(data) { + fs.create("/_test.ks", data, function() { + resolve(); + }); + }); + } + }); + }) + ); + } + + return Promise.all(fsPromises); }); // Mobile info gets accessed a lot, so we cache it on startup. @@ -120,21 +140,6 @@ if (MIDP.midletClassName == "RunTests") { loadingPromises.push(loadScript("tests/native.js"), loadScript("tests/override.js"), loadScript("tests/mozactivitymock.js")); - loadingPromises.push( - new Promise(function(resolve, reject) { - fs.exists("/_test.ks", function(exists) { - if (exists) { - resolve(); - } else { - load("certs/_test.ks", "blob").then(function(data) { - fs.create("/_test.ks", data, function() { - resolve(); - }); - }); - } - }); - }) - ); } Promise.all(loadingPromises).then(function() { @@ -152,7 +157,7 @@ function toggle(button) { window.onload = function() { document.getElementById("clearstorage").onclick = function() { - asyncStorage.clear(); + fs.clear(); }; document.getElementById("trace").onclick = function() { VM.DEBUG = !VM.DEBUG; diff --git a/midp/fs.js b/midp/fs.js index f9a7fa91..c47b663a 100644 --- a/midp/fs.js +++ b/midp/fs.js @@ -109,25 +109,17 @@ Native.create("com/sun/midp/rms/RecordStoreFile.writeBytes.(I[BII)V", function(h }); Native.create("com/sun/midp/rms/RecordStoreFile.commitWrite.(I)V", function(handle) { - return new Promise(function(resolve, reject) { - fs.flush(handle, resolve); - }); -}, true); + fs.flush(handle); +}); Native.create("com/sun/midp/rms/RecordStoreFile.closeFile.(I)V", function(handle) { - return new Promise(function(resolve, reject) { - fs.close(handle, resolve); - }); -}, true); + fs.close(handle); +}); Native.create("com/sun/midp/rms/RecordStoreFile.truncateFile.(II)V", function(handle, size) { - return new Promise(function(resolve, reject) { - fs.flush(handle, function() { - fs.ftruncate(handle, size); - resolve(); - }); - }); -}, true); + fs.flush(handle); + fs.ftruncate(handle, size); +}); MIDP.RecordStoreCache = []; @@ -442,7 +434,8 @@ Native.create("com/ibm/oti/connection/file/Connection.truncateImpl.([BJ)V", func } fs.ftruncate(fd, newLength.toNumber()); - fs.close(fd, resolve); + fs.close(fd); + resolve(); }); }); }, true); @@ -500,10 +493,8 @@ Native.create("com/ibm/oti/connection/file/FCInputStream.closeImpl.(I)V", functi }); Native.create("com/ibm/oti/connection/file/FCOutputStream.closeImpl.(I)V", function(fd) { - return new Promise(function(resolve, reject) { - fs.close(fd, resolve); - }); -}, true); + fs.close(fd); +}); Native.create("com/ibm/oti/connection/file/FCOutputStream.openImpl.([B)I", function(jPath) { var path = util.decodeUtf8(jPath); @@ -559,10 +550,8 @@ Native.create("com/ibm/oti/connection/file/FCOutputStream.openOffsetImpl.([BJ)I" }, true); Native.create("com/ibm/oti/connection/file/FCOutputStream.syncImpl.(I)V", function(fd) { - return new Promise(function(resolve, reject) { - fs.flush(fd, resolve); - }); -}, true); + fs.flush(fd); +}); Native.create("com/ibm/oti/connection/file/FCOutputStream.writeByteImpl.(II)V", function(val, fd) { var buf = new Uint8Array(1); @@ -630,10 +619,8 @@ function(handle, buffer, offset, length) { }); Native.create("com/sun/midp/io/j2me/storage/RandomAccessStream.commitWrite.(I)V", function(handle) { - return new Promise(function(resolve, reject) { - fs.flush(handle, resolve); - }); -}, true); + fs.flush(handle); +}); Native.create("com/sun/midp/io/j2me/storage/RandomAccessStream.position.(II)V", function(handle, position) { fs.setpos(handle, position); @@ -650,10 +637,8 @@ Native.create("com/sun/midp/io/j2me/storage/RandomAccessStream.sizeOf.(I)I", fun }); Native.create("com/sun/midp/io/j2me/storage/RandomAccessStream.close.(I)V", function(handle) { - return new Promise(function(resolve, reject) { - fs.close(handle, resolve); - }); -}, true); + fs.close(handle); +}); Native.create("javax/microedition/io/file/FileSystemRegistry.getRootsImpl.()[Ljava/lang/String;", function() { var array = util.newArray("[Ljava/lang/String;", 1); diff --git a/tests/TestFileSystemPerf.java b/tests/TestFileSystemPerf.java new file mode 100644 index 00000000..e750c000 --- /dev/null +++ b/tests/TestFileSystemPerf.java @@ -0,0 +1,62 @@ +import javax.microedition.io.*; +import javax.microedition.io.file.*; +import java.io.*; + +public class TestFileSystemPerf { + public static void main(String args[]) { + try { + String dirPath = System.getProperty("fileconn.dir.private"); + long then; + + String str = "I am the very model of a modern major general."; + byte[] bytes = str.getBytes(); + + then = System.currentTimeMillis(); + FileConnection file = (FileConnection)Connector.open(dirPath + "test.txt"); + System.out.println("Time to open file: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + file.create(); + System.out.println("Time to create file: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + OutputStream out = file.openOutputStream(); + for (int i = 0; i < 1000; i++) { + out.write(bytes); + out.flush(); + } + System.out.println("Time to write/flush to output stream: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + out.close(); + System.out.println("Time to close output stream: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + file.delete(); + System.out.println("Time to delete file: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + file.close(); + System.out.println("Time to close file: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + file = (FileConnection)Connector.open(dirPath + "test.txt"); + System.out.println("Time to reopen file: " + (System.currentTimeMillis() - then) + "ms"); + + then = System.currentTimeMillis(); + file = (FileConnection)Connector.open(dirPath + "test2.txt"); + file.create(); + out = file.openOutputStream(); + out.write(bytes); + out.flush(); + out.close(); + file.delete(); + file.close(); + System.out.println("Time to access another file: " + (System.currentTimeMillis() - then) + "ms"); + + } catch (Exception e) { + System.out.println("Unexpected exception: " + e); + e.printStackTrace(); + } + } +} diff --git a/tests/automation.js b/tests/automation.js index 149410ec..962c2f20 100644 --- a/tests/automation.js +++ b/tests/automation.js @@ -41,7 +41,7 @@ var gfxTests = [ ]; var expectedUnitTestResults = [ - { name: "pass", number: 71128 }, + { name: "pass", number: 71126 }, { name: "fail", number: 0 }, { name: "known fail", number: 180 }, { name: "unknown pass", number: 0 } diff --git a/tests/fstests.html b/tests/fstests.html index 2e252a40..32fbb66e 100644 --- a/tests/fstests.html +++ b/tests/fstests.html @@ -4,7 +4,6 @@ -