зеркало из https://github.com/mozilla/pluotsorbet.git
Merge branch 'master' of https://github.com/andreasgal/j2me.js into indicator
Conflicts: manifest.webapp
This commit is contained in:
Коммит
589f91e42f
4
Makefile
4
Makefile
|
@ -11,6 +11,10 @@ test: all
|
|||
cd tests && python sslEchoServer.py &
|
||||
cd tests && python waitServers.py
|
||||
casperjs --engine=slimerjs test `pwd`/tests/automation.js > test.log
|
||||
mkdir test-profile-fs-v1
|
||||
casperjs --engine=slimerjs -profile `pwd`/test-profile-fs-v1 `pwd`/tests/fs/make-fs-v1.js >> test.log
|
||||
casperjs --engine=slimerjs test -profile `pwd`/test-profile-fs-v1 `pwd`/tests/automation.js >> test.log
|
||||
rm -rf test-profile-fs-v1
|
||||
killall python Python || true
|
||||
python dumplog.py
|
||||
if grep -q FAIL test.log; \
|
||||
|
|
4
index.js
4
index.js
|
@ -350,6 +350,10 @@ DumbPipe.registerOpener("camera", function(message, sender) {
|
|||
document.body.appendChild(video);
|
||||
video.style.position = "absolute";
|
||||
video.style.visibility = "hidden";
|
||||
// Some MIDlets need user touch/click on the screen to complete the snapshot,
|
||||
// to make sure the MIDlet itself instead of the video element can capture
|
||||
// the mouse/touch events, we need to set `pointer-events` as `none`.
|
||||
video.style.pointerEvents = "none";
|
||||
|
||||
video.addEventListener('canplay', function(ev) {
|
||||
// We should use videoWidth and videoHeight, but they are unavailable (https://bugzilla.mozilla.org/show_bug.cgi?id=926753)
|
||||
|
|
|
@ -156,6 +156,9 @@ Instrument.enter["com/sun/midp/ssl/Out.write.([BII)V"] = function(caller, callee
|
|||
var connection = _this.class.getField("I.ssc.Lcom/sun/midp/ssl/SSLStreamConnection;").get(_this);
|
||||
var range = b.subarray(off, off + len);
|
||||
for (var i = 0; i < range.length; i++) {
|
||||
if (range[i] == 0) {
|
||||
break;
|
||||
}
|
||||
connection.logBuffer += String.fromCharCode(range[i] & 0xff);
|
||||
}
|
||||
};
|
||||
|
@ -174,6 +177,9 @@ Instrument.exit["com/sun/midp/ssl/In.read.([BII)I"] = function(caller, callee) {
|
|||
var connection = _this.class.getField("I.ssc.Lcom/sun/midp/ssl/SSLStreamConnection;").get(_this);
|
||||
var range = b.subarray(off, off + len);
|
||||
for (var i = 0; i < range.length; i++) {
|
||||
if (range[i] == 0) {
|
||||
break;
|
||||
}
|
||||
connection.logBuffer += String.fromCharCode(range[i] & 0xff);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
this.currentFilterText = "";
|
||||
window.addEventListener(
|
||||
'console-filters-changed', this.onFiltersChanged.bind(this));
|
||||
window.addEventListener(
|
||||
'console-clear', this.onClear.bind(this));
|
||||
}
|
||||
|
||||
PageConsole.prototype = {
|
||||
|
@ -129,6 +131,11 @@
|
|||
}, this);
|
||||
this.el.innerHTML = "";
|
||||
this.el.appendChild(fragment);
|
||||
},
|
||||
|
||||
onClear: function() {
|
||||
this.items = [];
|
||||
this.el.innerHTML = "";
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -221,6 +228,10 @@
|
|||
var consoleFilterTextInput = document.querySelector('#console-filter-input');
|
||||
var autoScrollCheckbox = document.querySelector('#auto-scroll');
|
||||
|
||||
document.querySelector('#console-clear').addEventListener('click', function() {
|
||||
window.dispatchEvent(new CustomEvent('console-clear'));
|
||||
});
|
||||
|
||||
function updateFilters() {
|
||||
minLogLevel = logLevelSelect.value;
|
||||
CONSOLES.page.currentFilterText = consoleFilterTextInput.value.toLowerCase();
|
||||
|
|
355
libs/fs.js
355
libs/fs.js
|
@ -5,20 +5,81 @@ var DEBUG_FS = false;
|
|||
var fs = (function() {
|
||||
var Store = function() {
|
||||
this.map = new Map();
|
||||
|
||||
// Pending changes to the persistent datastore, indexed by record key.
|
||||
//
|
||||
// Changes can represent puts or deletes and comprise a type and (for puts)
|
||||
// the value to write:
|
||||
// key: { type: "delete" } or key: { type: "put", value: <value> }
|
||||
//
|
||||
// We index by key, storing only the most recent change for a given key,
|
||||
// to coalesce multiple changes, so that we always sync only the most recent
|
||||
// change for a given record.
|
||||
this.changesToSync = new Map();
|
||||
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
Store.DBNAME = "asyncStorage";
|
||||
Store.DBVERSION = 1;
|
||||
Store.DBSTORENAME = "keyvaluepairs";
|
||||
Store.DBVERSION = 2;
|
||||
Store.DBSTORENAME = "fs";
|
||||
|
||||
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.onupgradeneeded = function(event) {
|
||||
if (DEBUG_FS) { console.log("upgrade needed from " + event.oldVersion + " to " + event.newVersion); }
|
||||
|
||||
var db = event.target.result;
|
||||
var transaction = openreq.transaction;
|
||||
|
||||
if (event.oldVersion == 0) {
|
||||
// If the database doesn't exist yet, then all we have to do
|
||||
// is create the object store for the latest version of the database.
|
||||
openreq.result.createObjectStore(Store.DBSTORENAME);
|
||||
} else if (event.oldVersion == 1) {
|
||||
// Create new object store.
|
||||
var newObjectStore = openreq.result.createObjectStore(Store.DBSTORENAME);
|
||||
|
||||
// Iterate the keys in the old object store and copy their values
|
||||
// to the new one, converting them from old- to new-style records.
|
||||
var oldObjectStore = transaction.objectStore("keyvaluepairs");
|
||||
var oldRecords = {};
|
||||
oldObjectStore.openCursor().onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
|
||||
if (cursor) {
|
||||
oldRecords[cursor.key] = cursor.value;
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the old records to new ones.
|
||||
for (var key in oldRecords) {
|
||||
// Records that start with an exclamation mark are stats,
|
||||
// which we don't iterate (but do use below when processing
|
||||
// their equivalent data records).
|
||||
if (key[0] == "!") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldRecord = oldRecords[key];
|
||||
var oldStat = oldRecords["!" + key];
|
||||
var newRecord = oldStat;
|
||||
if (newRecord.isDir) {
|
||||
newRecord.files = oldRecord;
|
||||
} else {
|
||||
newRecord.data = oldRecord;
|
||||
}
|
||||
|
||||
newObjectStore.put(newRecord, key);
|
||||
}
|
||||
|
||||
db.deleteObjectStore("keyvaluepairs");
|
||||
};
|
||||
}
|
||||
};
|
||||
openreq.onsuccess = (function() {
|
||||
this.db = openreq.result;
|
||||
|
@ -52,36 +113,17 @@ var fs = (function() {
|
|||
|
||||
Store.prototype.setItem = function(key, value) {
|
||||
this.map.set(key, value);
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("put " + key + " initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
var req = objectStore.put(value, key);
|
||||
req.onerror = function() {
|
||||
console.error("Error putting " + key + ": " + req.error.name);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("put " + key + " completed"); }
|
||||
};
|
||||
this.changesToSync.set(key, { type: "put", value: value });
|
||||
};
|
||||
|
||||
Store.prototype.removeItem = function(key) {
|
||||
this.map.delete(key);
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("delete " + key + " initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
var req = objectStore.delete(key);
|
||||
req.onerror = function() {
|
||||
console.error("Error deleting " + key + ": " + req.error.name);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("delete " + key + " completed"); }
|
||||
};
|
||||
this.map.set(key, null);
|
||||
this.changesToSync.set(key, { type: "delete" });
|
||||
};
|
||||
|
||||
Store.prototype.clear = function() {
|
||||
this.map.clear();
|
||||
this.changesToSync.clear();
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("clear initiated"); }
|
||||
|
@ -95,17 +137,53 @@ var fs = (function() {
|
|||
};
|
||||
}
|
||||
|
||||
Store.prototype.purge = function(cb) {
|
||||
cb = cb || function() {};
|
||||
|
||||
// We have to sync to the persistent store before we purge the memory cache
|
||||
// to ensure a caller who writes data to a file, purges the cache, and then
|
||||
// immediately reads the file will get the data.
|
||||
this.sync((function() {
|
||||
this.map.clear();
|
||||
cb();
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
Store.prototype.sync = function(cb) {
|
||||
// Process a readwrite transaction to ensure previous writes have completed,
|
||||
// so we leave the datastore in a consistent state. This is a bit hacky;
|
||||
// we should instead monitor ongoing transactions and call our callback
|
||||
// once they've all completed.
|
||||
cb = cb || function() {};
|
||||
|
||||
// If there are no changes to sync, merely call the callback
|
||||
// (in a timeout so the callback always gets called asynchronously).
|
||||
if (this.changesToSync.size == 0) {
|
||||
setZeroTimeout(cb);
|
||||
return;
|
||||
}
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("get \"\" initiated"); }
|
||||
if (DEBUG_FS) { console.log("sync initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
objectStore.get("");
|
||||
|
||||
this.changesToSync.forEach((function(change, key) {
|
||||
var req;
|
||||
if (change.type == "put") {
|
||||
req = objectStore.put(change.value, key);
|
||||
if (DEBUG_FS) { console.log("put " + key); }
|
||||
req.onerror = function() {
|
||||
console.error("Error putting " + key + ": " + req.error.name);
|
||||
};
|
||||
} else if (change.type == "delete") {
|
||||
req = objectStore.delete(key);
|
||||
if (DEBUG_FS) { console.log("delete " + key); }
|
||||
req.onerror = function() {
|
||||
console.error("Error deleting " + key + ": " + req.error.name);
|
||||
};
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
this.changesToSync.clear();
|
||||
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("get \"\" completed"); }
|
||||
if (DEBUG_FS) { console.log("sync completed"); }
|
||||
cb();
|
||||
};
|
||||
}
|
||||
|
@ -191,8 +269,11 @@ var fs = (function() {
|
|||
if (data) {
|
||||
cb();
|
||||
} else {
|
||||
store.setItem("/", []);
|
||||
setStat("/", { mtime: Date.now(), isDir: true });
|
||||
store.setItem("/", {
|
||||
isDir: true,
|
||||
mtime: Date.now(),
|
||||
files: [],
|
||||
});
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
@ -210,8 +291,8 @@ var fs = (function() {
|
|||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs open " + path); }
|
||||
|
||||
store.getItem(path, function(blob) {
|
||||
if (blob == null || !(blob instanceof Blob)) {
|
||||
store.getItem(path, function(record) {
|
||||
if (record == null || record.isDir) {
|
||||
cb(-1);
|
||||
} else {
|
||||
var reader = new FileReader();
|
||||
|
@ -221,10 +302,11 @@ var fs = (function() {
|
|||
path: path,
|
||||
buffer: new FileBuffer(new Uint8Array(reader.result)),
|
||||
position: 0,
|
||||
record: record,
|
||||
}) - 1;
|
||||
cb(fd);
|
||||
});
|
||||
reader.readAsArrayBuffer(blob);
|
||||
reader.readAsArrayBuffer(record.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -282,7 +364,8 @@ var fs = (function() {
|
|||
|
||||
var file = openedFiles[fd];
|
||||
file.position = from + data.byteLength;
|
||||
file.stat = { mtime: Date.now(), isDir: false, size: buffer.contentSize };
|
||||
file.record.mtime = Date.now();
|
||||
file.record.size = buffer.contentSize;
|
||||
file.dirty = true;
|
||||
}
|
||||
|
||||
|
@ -312,12 +395,9 @@ var fs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
var blob = new Blob([openedFile.buffer.getContent()]);
|
||||
store.setItem(openedFile.path, blob);
|
||||
openedFile.record.data = new Blob([openedFile.buffer.getContent()]);
|
||||
store.setItem(openedFile.path, openedFile.record);
|
||||
openedFile.dirty = false;
|
||||
if (openedFile.stat) {
|
||||
setStat(openedFile.path, openedFile.stat);
|
||||
}
|
||||
}
|
||||
|
||||
function flushAll() {
|
||||
|
@ -327,6 +407,13 @@ var fs = (function() {
|
|||
}
|
||||
flush(fd);
|
||||
}
|
||||
|
||||
// After flushing to the in-memory datastore, sync it to the persistent one.
|
||||
// We might want to decouple this from the flushAll calls, so we can do them
|
||||
// at different interval (f.e. flushing to memory every five seconds
|
||||
// but only syncing to the persistent datastore every minute or so), though
|
||||
// we should continue to do both immediately on pagehide.
|
||||
syncStore();
|
||||
}
|
||||
|
||||
// Due to bug #227, we don't support Object::finalize(). But the Java
|
||||
|
@ -342,11 +429,11 @@ var fs = (function() {
|
|||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs list " + path); }
|
||||
|
||||
store.getItem(path, function(files) {
|
||||
if (files == null || files instanceof Blob) {
|
||||
store.getItem(path, function(record) {
|
||||
if (record == null || !record.isDir) {
|
||||
cb(null);
|
||||
} else {
|
||||
cb(files);
|
||||
cb(record.files);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -355,8 +442,8 @@ var fs = (function() {
|
|||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs exists " + path); }
|
||||
|
||||
stat(path, function(stat) {
|
||||
cb(stat ? true : false);
|
||||
store.getItem(path, function(record) {
|
||||
cb(record ? true : false);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -364,13 +451,15 @@ var fs = (function() {
|
|||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs truncate " + path); }
|
||||
|
||||
stat(path, function(stat) {
|
||||
if (stat && !stat.isDir) {
|
||||
store.setItem(path, new Blob());
|
||||
setStat(path, { mtime: Date.now(), isDir: false, size: 0 });
|
||||
cb(true);
|
||||
} else {
|
||||
store.getItem(path, function(record) {
|
||||
if (record == null || record.isDir) {
|
||||
cb(false);
|
||||
} else {
|
||||
record.data = new Blob();
|
||||
record.mtime = Date.now();
|
||||
record.size = 0;
|
||||
store.setItem(path, record);
|
||||
cb(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -382,7 +471,8 @@ var fs = (function() {
|
|||
if (size != file.buffer.contentSize) {
|
||||
file.buffer.setSize(size);
|
||||
file.dirty = true;
|
||||
file.stat = { mtime: Date.now(), isDir: false, size: size };
|
||||
file.record.mtime = Date.now();
|
||||
file.record.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,8 +485,9 @@ var fs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
list(path, function(files) {
|
||||
if (files != null && files.length > 0) {
|
||||
store.getItem(path, function(record) {
|
||||
// If it's a directory that isn't empty, then we can't remove it.
|
||||
if (record && record.isDir && record.files.length > 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
@ -404,36 +495,44 @@ var fs = (function() {
|
|||
var name = basename(path);
|
||||
var dir = dirname(path);
|
||||
|
||||
list(dir, function(files) {
|
||||
store.getItem(dir, function(parentRecord) {
|
||||
var index = -1;
|
||||
|
||||
if (files == null || (index = files.indexOf(name)) < 0) {
|
||||
// If it isn't in the parent directory, then we can't remove it.
|
||||
if (parentRecord == null || (index = parentRecord.files.indexOf(name)) < 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
files.splice(index, 1);
|
||||
store.setItem(dir, files);
|
||||
parentRecord.files.splice(index, 1);
|
||||
store.setItem(dir, parentRecord);
|
||||
store.removeItem(path);
|
||||
removeStat(path);
|
||||
cb(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createInternal(path, data, cb) {
|
||||
function createInternal(path, record, cb) {
|
||||
var name = basename(path);
|
||||
var dir = dirname(path);
|
||||
|
||||
list(dir, function(files) {
|
||||
if (files == null || files.indexOf(name) >= 0) {
|
||||
store.getItem(dir, function(parentRecord) {
|
||||
// If the parent directory doesn't exist or isn't a directory,
|
||||
// then we can't create the file.
|
||||
if (parentRecord == null || !parentRecord.isDir) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
files.push(name);
|
||||
store.setItem(dir, files);
|
||||
store.setItem(path, data);
|
||||
// If the file already exists, we can't create it.
|
||||
if (parentRecord.files.indexOf(name) >= 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
parentRecord.files.push(name);
|
||||
store.setItem(dir, parentRecord);
|
||||
store.setItem(path, record);
|
||||
cb(true);
|
||||
});
|
||||
}
|
||||
|
@ -442,24 +541,27 @@ var fs = (function() {
|
|||
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 });
|
||||
}
|
||||
cb(created);
|
||||
});
|
||||
var record = {
|
||||
isDir: false,
|
||||
mtime: Date.now(),
|
||||
data: blob,
|
||||
size: blob.size,
|
||||
};
|
||||
|
||||
createInternal(path, record, cb);
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
cb(created);
|
||||
});
|
||||
var record = {
|
||||
isDir: true,
|
||||
mtime: Date.now(),
|
||||
files: [],
|
||||
};
|
||||
|
||||
createInternal(path, record, cb);
|
||||
}
|
||||
|
||||
function mkdirp(path, cb) {
|
||||
|
@ -486,12 +588,12 @@ var fs = (function() {
|
|||
|
||||
partPath += "/" + parts.shift();
|
||||
|
||||
stat(partPath, function(stat) {
|
||||
if (!stat) {
|
||||
store.getItem(partPath, function(record) {
|
||||
if (!record) {
|
||||
// The part doesn't exist; make it, then continue to next part.
|
||||
mkdir(partPath, mkpart);
|
||||
}
|
||||
else if (stat.isDir) {
|
||||
else if (record.isDir) {
|
||||
// The part exists and is a directory; continue to next part.
|
||||
mkpart(true);
|
||||
}
|
||||
|
@ -510,11 +612,11 @@ var fs = (function() {
|
|||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs size " + path); }
|
||||
|
||||
store.getItem(path, function(blob) {
|
||||
if (blob == null || !(blob instanceof Blob)) {
|
||||
store.getItem(path, function(record) {
|
||||
if (record == null || record.isDir) {
|
||||
cb(-1);
|
||||
} else {
|
||||
cb(blob.size);
|
||||
cb(record.data.size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -531,57 +633,63 @@ var fs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
list(oldPath, function(files) {
|
||||
if (files != null && files.length > 0) {
|
||||
store.getItem(oldPath, function(oldRecord) {
|
||||
// If the old path doesn't exist, we can't move it.
|
||||
if (oldRecord == null) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
store.getItem(oldPath, function(data) {
|
||||
if (data == null) {
|
||||
// If the old path is a dir with files in it, we don't move it.
|
||||
// XXX Shouldn't we move it along with its files?
|
||||
if (oldRecord.isDir && oldRecord.files.length > 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
remove(oldPath, function(removed) {
|
||||
if (!removed) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
remove(oldPath, function(removed) {
|
||||
if (!removed) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data instanceof Blob) {
|
||||
create(newPath, data, cb);
|
||||
} else {
|
||||
mkdir(newPath, cb);
|
||||
}
|
||||
});
|
||||
if (oldRecord.isDir) {
|
||||
mkdir(newPath, cb);
|
||||
} else {
|
||||
create(newPath, oldRecord.data, cb);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setStat(path, stat) {
|
||||
if (DEBUG_FS) { console.log("fs setStat " + path); }
|
||||
|
||||
store.setItem("!" + path, stat);
|
||||
}
|
||||
|
||||
function removeStat(path) {
|
||||
if (DEBUG_FS) { console.log("fs removeStat " + path); }
|
||||
|
||||
store.removeItem("!" + path);
|
||||
}
|
||||
|
||||
function stat(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs stat " + path); }
|
||||
|
||||
var file = openedFiles.find(function (file) { return file && file.stat && file.path === path });
|
||||
var file = openedFiles.find(function (file) { return file && file.path === path });
|
||||
if (file) {
|
||||
setZeroTimeout(function() { cb(file.stat); });
|
||||
var stat = {
|
||||
isDir: file.record.isDir,
|
||||
mtime: file.record.mtime,
|
||||
size: file.record.size,
|
||||
};
|
||||
setZeroTimeout(function() { cb(stat); });
|
||||
return;
|
||||
}
|
||||
|
||||
store.getItem("!" + path, cb);
|
||||
store.getItem(path, function(record) {
|
||||
if (record == null) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var stat = {
|
||||
isDir: record.isDir,
|
||||
mtime: record.mtime,
|
||||
size: record.size,
|
||||
};
|
||||
cb(stat);
|
||||
});
|
||||
}
|
||||
|
||||
function clear(cb) {
|
||||
|
@ -589,10 +697,14 @@ var fs = (function() {
|
|||
initRootDir(cb || function() {});
|
||||
}
|
||||
|
||||
function storeSync(cb) {
|
||||
function syncStore(cb) {
|
||||
store.sync(cb);
|
||||
}
|
||||
|
||||
function purgeStore(cb) {
|
||||
store.purge(cb);
|
||||
}
|
||||
|
||||
var _creatingFile = false;
|
||||
var _creatingQueue = [];
|
||||
function createUniqueFile(parentDir, completeName, blob, callback) {
|
||||
|
@ -663,7 +775,8 @@ var fs = (function() {
|
|||
rename: rename,
|
||||
stat: stat,
|
||||
clear: clear,
|
||||
storeSync: storeSync,
|
||||
syncStore: syncStore,
|
||||
purgeStore: purgeStore,
|
||||
createUniqueFile: createUniqueFile,
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
* pushConn
|
||||
* pushMidlet
|
||||
* autosize
|
||||
* fontSize
|
||||
*
|
||||
* Keep this list up-to-date!
|
||||
*/
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
<option value="5">Log Level: Silent
|
||||
</select>
|
||||
<input id="console-filter-input" type="text" placeholder="Filter Console Output" value="">
|
||||
<button id="console-clear">Clear console</button>
|
||||
<label><input type="checkbox" id="auto-scroll" checked>Auto-Scroll At Bottom</label>
|
||||
</section>
|
||||
<section>
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
"audio-capture": {
|
||||
"description": "Required to capture audio via getUserMedia"
|
||||
},
|
||||
"video-capture": {
|
||||
"description": "Required to take pictures"
|
||||
},
|
||||
"desktop-notification": {
|
||||
"description": "Required to display notifications"
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@
|
|||
var SIZE_LARGE = 16;
|
||||
|
||||
Native.create("javax/microedition/lcdui/Font.init.(III)V", function(face, style, size) {
|
||||
var defaultSize = Math.max(10, (MIDP.Context2D.canvas.height / 48) | 0);
|
||||
var defaultSize = urlParams.fontSize ? urlParams.fontSize : Math.max(10, (MIDP.Context2D.canvas.height / 35) | 0);
|
||||
if (size & SIZE_SMALL)
|
||||
size = defaultSize / 1.25;
|
||||
else if (size & SIZE_LARGE)
|
||||
|
|
|
@ -439,7 +439,7 @@ ImageRecorder.prototype.recipient = function(message) {
|
|||
|
||||
MIDP.sendNativeEvent({
|
||||
type: MIDP.MMAPI_EVENT,
|
||||
intParam1: this.playerContainer.handle,
|
||||
intParam1: this.playerContainer.pId,
|
||||
intParam2: 0,
|
||||
intParam3: 0,
|
||||
intParam4: Media.EVENT_MEDIA_SNAPSHOT_FINISHED,
|
||||
|
@ -499,8 +499,12 @@ ImageRecorder.prototype.getSnapshotData = function(imageType) {
|
|||
return this.snapshotData;
|
||||
}
|
||||
|
||||
function PlayerContainer(url) {
|
||||
function PlayerContainer(url, pId) {
|
||||
this.url = url;
|
||||
// `pId` is the player id used in PlayerImpl.java, don't confuse with the id we used
|
||||
// here in Javascript. The reason we need to hold this `pId` is we need to send it
|
||||
// back when dispatch Media.EVENT_MEDIA_SNAPSHOT_FINISHED.
|
||||
this.pId = pId;
|
||||
|
||||
this.mediaFormat = url ? this.guessFormatFromURL(url) : "UNKNOWN";
|
||||
this.contentType = "";
|
||||
|
@ -870,7 +874,7 @@ AudioRecorder.prototype.close = function() {
|
|||
Native.create("com/sun/mmedia/PlayerImpl.nInit.(IILjava/lang/String;)I", function(appId, pId, jURI) {
|
||||
var url = util.fromJavaString(jURI);
|
||||
var id = pId + (appId << 32);
|
||||
Media.PlayerCache[id] = new PlayerContainer(url);
|
||||
Media.PlayerCache[id] = new PlayerContainer(url, pId);
|
||||
return id;
|
||||
});
|
||||
|
||||
|
|
|
@ -99,7 +99,9 @@ Native.create("java/lang/System.getProperty0.(Ljava/lang/String;)Ljava/lang/Stri
|
|||
value = "file:///";
|
||||
break;
|
||||
case "fileconn.dir.photos":
|
||||
value = "file:///Photos/";
|
||||
// We need to create the dir in the FS init process if it's
|
||||
// not the root dir.
|
||||
value = "file:///";
|
||||
break;
|
||||
case "fileconn.dir.roots.names":
|
||||
// The names here should be localized.
|
||||
|
@ -194,7 +196,10 @@ Native.create("java/lang/System.getProperty0.(Ljava/lang/String;)Ljava/lang/Stri
|
|||
value = "audio/ogg";
|
||||
break;
|
||||
case "video.snapshot.encodings":
|
||||
value = "encoding=jpeg";
|
||||
// FIXME Some MIDlets pass a string that contains lots of constraints
|
||||
// as the `imageType` which is not yet handled in DirectVideo.jpp, let's
|
||||
// just put the whole string here as a workaround and fix this in issue #688.
|
||||
value = "encoding=jpeg&quality=80&progressive=true&type=jfif&width=400&height=400";
|
||||
break;
|
||||
default:
|
||||
console.warn("UNKNOWN PROPERTY (java/lang/System): " + util.fromJavaString(key));
|
||||
|
|
|
@ -15,6 +15,52 @@ public class TestFileSystemPerf {
|
|||
FileConnection file = (FileConnection)Connector.open(dirPath + "test.txt");
|
||||
System.out.println("Time to open file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
boolean exists = file.exists();
|
||||
System.out.println("Time to check if file exists: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
if (exists) {
|
||||
then = System.currentTimeMillis();
|
||||
InputStream in = file.openInputStream();
|
||||
byte[] input = new byte[1024];
|
||||
int numBytes;
|
||||
int totalNumBytes = 0;
|
||||
while ((numBytes = in.read(input)) != -1) {
|
||||
totalNumBytes += numBytes;
|
||||
}
|
||||
in.close();
|
||||
System.out.println("Time to read " + totalNumBytes + " bytes: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file.delete();
|
||||
System.out.println("Time to delete file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
}
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file = (FileConnection)Connector.open(dirPath + "test2.txt");
|
||||
System.out.println("Time to open test2: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
exists = file.exists();
|
||||
System.out.println("Time to check if test2 exists: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
if (exists) {
|
||||
then = System.currentTimeMillis();
|
||||
InputStream in = file.openInputStream();
|
||||
byte[] input = new byte[1024];
|
||||
int numBytes;
|
||||
int totalNumBytes = 0;
|
||||
while ((numBytes = in.read(input)) != -1) {
|
||||
totalNumBytes += numBytes;
|
||||
}
|
||||
in.close();
|
||||
System.out.println("Time to read " + totalNumBytes + " bytes: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file.delete();
|
||||
System.out.println("Time to delete test2: " + (System.currentTimeMillis() - then) + "ms");
|
||||
}
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file.create();
|
||||
System.out.println("Time to create file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
@ -25,26 +71,18 @@ public class TestFileSystemPerf {
|
|||
out.write(bytes);
|
||||
out.flush();
|
||||
}
|
||||
System.out.println("Time to write/flush to output stream: " + (System.currentTimeMillis() - then) + "ms");
|
||||
System.out.println("Time to write/flush 1,000 times: " + (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 = (FileConnection)Connector.open(dirPath + "uncached");
|
||||
file.create();
|
||||
out = file.openOutputStream();
|
||||
out.write(bytes);
|
||||
|
@ -52,8 +90,50 @@ public class TestFileSystemPerf {
|
|||
out.close();
|
||||
file.delete();
|
||||
file.close();
|
||||
System.out.println("Time to access another file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
System.out.println("open/create/write/delete/close uncached: " + (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.close();
|
||||
System.out.println("Time to reclose file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file = (FileConnection)Connector.open(dirPath + "test2.txt");
|
||||
System.out.println("Time to open another file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file.create();
|
||||
out = file.openOutputStream();
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
out.write(bytes);
|
||||
}
|
||||
System.out.println("Time to write 100,000 times: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
out.flush();
|
||||
System.out.println("Time to flush once: " + (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.close();
|
||||
System.out.println("Time to close file: " + (System.currentTimeMillis() - then) + "ms");
|
||||
|
||||
then = System.currentTimeMillis();
|
||||
file = (FileConnection)Connector.open(dirPath + "uncached2");
|
||||
file.create();
|
||||
out = file.openOutputStream();
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
out.close();
|
||||
file.delete();
|
||||
file.close();
|
||||
System.out.println("open/create/write/delete/close uncached2: " + (System.currentTimeMillis() - then) + "ms");
|
||||
} catch (Exception e) {
|
||||
System.out.println("Unexpected exception: " + e);
|
||||
e.printStackTrace();
|
||||
|
|
|
@ -116,9 +116,9 @@ casper.test.begin("unit tests", 7 + gfxTests.length, function(test) {
|
|||
});
|
||||
|
||||
casper
|
||||
.thenOpen("http://localhost:8000/tests/fstests.html")
|
||||
.thenOpen("http://localhost:8000/tests/fs/fstests.html")
|
||||
.waitForText("DONE", function() {
|
||||
test.assertTextExists("DONE: 127 PASS, 0 FAIL", "run fs.js unit tests");
|
||||
test.assertTextExists("DONE: 129 PASS, 0 FAIL", "run fs.js unit tests");
|
||||
});
|
||||
|
||||
casper
|
||||
|
@ -166,7 +166,7 @@ casper.test.begin("unit tests", 7 + gfxTests.length, function(test) {
|
|||
|
||||
gfxTests.forEach(function(testCase) {
|
||||
casper
|
||||
.thenOpen("http://localhost:8000/index.html?main=com/sun/midp/main/MIDletSuiteLoader&midletClassName=" + testCase.name + "&jars=tests/tests.jar")
|
||||
.thenOpen("http://localhost:8000/index.html?fontSize=10&main=com/sun/midp/main/MIDletSuiteLoader&midletClassName=" + testCase.name + "&jars=tests/tests.jar")
|
||||
.withFrame(0, function() {
|
||||
casper.waitForText("PAINTED", function() {
|
||||
this.waitForSelector("#canvas", function() {
|
||||
|
|
|
@ -0,0 +1,621 @@
|
|||
'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");
|
||||
if (DEBUG_FS) { console.log("get " + key + " initiated"); }
|
||||
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() {
|
||||
if (DEBUG_FS) { console.log("get " + key + " completed"); }
|
||||
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");
|
||||
if (DEBUG_FS) { console.log("put " + key + " initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
var req = objectStore.put(value, key);
|
||||
req.onerror = function() {
|
||||
console.error("Error putting " + key + ": " + req.error.name);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("put " + key + " completed"); }
|
||||
};
|
||||
};
|
||||
|
||||
Store.prototype.removeItem = function(key) {
|
||||
this.map.delete(key);
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("delete " + key + " initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
var req = objectStore.delete(key);
|
||||
req.onerror = function() {
|
||||
console.error("Error deleting " + key + ": " + req.error.name);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("delete " + key + " completed"); }
|
||||
};
|
||||
};
|
||||
|
||||
Store.prototype.clear = function() {
|
||||
this.map.clear();
|
||||
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("clear initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
var req = objectStore.clear();
|
||||
req.onerror = function() {
|
||||
console.error("Error clearing store: " + req.error.name);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("clear completed"); }
|
||||
};
|
||||
}
|
||||
|
||||
Store.prototype.sync = function(cb) {
|
||||
// Process a readwrite transaction to ensure previous writes have completed,
|
||||
// so we leave the datastore in a consistent state. This is a bit hacky;
|
||||
// we should instead monitor ongoing transactions and call our callback
|
||||
// once they've all completed.
|
||||
var transaction = this.db.transaction(Store.DBSTORENAME, "readwrite");
|
||||
if (DEBUG_FS) { console.log("get \"\" initiated"); }
|
||||
var objectStore = transaction.objectStore(Store.DBSTORENAME);
|
||||
objectStore.get("");
|
||||
transaction.oncomplete = function() {
|
||||
if (DEBUG_FS) { console.log("get \"\" completed"); }
|
||||
cb();
|
||||
};
|
||||
}
|
||||
|
||||
var store = new Store();
|
||||
|
||||
var FileBuffer = function(array) {
|
||||
this.array = array;
|
||||
this.contentSize = array.byteLength;
|
||||
}
|
||||
|
||||
FileBuffer.prototype.setSize = function(newContentSize) {
|
||||
if (newContentSize < this.array.byteLength) {
|
||||
this.contentSize = newContentSize;
|
||||
return;
|
||||
}
|
||||
|
||||
var newBufferSize = 512;
|
||||
|
||||
// The buffer grows exponentially until the content size
|
||||
// reaches 65536. After this threshold, it starts to grow
|
||||
// linearly in increments of 65536 bytes.
|
||||
if (newContentSize < 65536) {
|
||||
while (newContentSize > newBufferSize) {
|
||||
newBufferSize <<= 1;
|
||||
}
|
||||
} else {
|
||||
while (newContentSize > newBufferSize) {
|
||||
newBufferSize += 65536;
|
||||
}
|
||||
}
|
||||
|
||||
var newArray = new Uint8Array(newBufferSize);
|
||||
newArray.set(this.array);
|
||||
|
||||
this.array = newArray;
|
||||
this.contentSize = newContentSize;
|
||||
}
|
||||
|
||||
FileBuffer.prototype.getContent = function() {
|
||||
return this.array.subarray(0, this.contentSize);
|
||||
}
|
||||
|
||||
function normalizePath(path) {
|
||||
// Remove a trailing slash.
|
||||
if (path.length != 1 && path.lastIndexOf("/") == path.length-1) {
|
||||
path = path.substring(0, path.length-1);
|
||||
}
|
||||
|
||||
// Coalesce multiple consecutive slashes.
|
||||
path = path.replace(/\/{2,}/, "/");
|
||||
|
||||
// XXX Replace "." and ".." parts.
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function dirname(path) {
|
||||
path = normalizePath(path);
|
||||
|
||||
var index = path.lastIndexOf("/");
|
||||
if (index == -1) {
|
||||
return ".";
|
||||
}
|
||||
|
||||
while (index >= 0 && path[index] == "/") {
|
||||
--index;
|
||||
}
|
||||
|
||||
var dir = path.slice(0, index + 1);
|
||||
if (dir == "") {
|
||||
dir = "/";
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
function basename(path) {
|
||||
return path.slice(path.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
function initRootDir(cb) {
|
||||
store.getItem("/", function(data) {
|
||||
if (data) {
|
||||
cb();
|
||||
} else {
|
||||
store.setItem("/", []);
|
||||
setStat("/", { mtime: Date.now(), isDir: true });
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init(cb) {
|
||||
store.init(function() {
|
||||
initRootDir(cb || function() {});
|
||||
});
|
||||
}
|
||||
|
||||
var openedFiles = [null, null, null];
|
||||
|
||||
function open(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs open " + path); }
|
||||
|
||||
store.getItem(path, function(blob) {
|
||||
if (blob == null || !(blob instanceof Blob)) {
|
||||
cb(-1);
|
||||
} else {
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener("loadend", function() {
|
||||
var fd = openedFiles.push({
|
||||
dirty: false,
|
||||
path: path,
|
||||
buffer: new FileBuffer(new Uint8Array(reader.result)),
|
||||
position: 0,
|
||||
}) - 1;
|
||||
cb(fd);
|
||||
});
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function close(fd) {
|
||||
if (fd >= 0 && openedFiles[fd]) {
|
||||
if (DEBUG_FS) { console.log("fs close " + openedFiles[fd].path); }
|
||||
flush(fd);
|
||||
openedFiles.splice(fd, 1, null);
|
||||
}
|
||||
}
|
||||
|
||||
function read(fd, from, to) {
|
||||
if (!openedFiles[fd]) {
|
||||
return null;
|
||||
}
|
||||
if (DEBUG_FS) { console.log("fs read " + openedFiles[fd].path); }
|
||||
|
||||
var buffer = openedFiles[fd].buffer;
|
||||
|
||||
if (typeof from === "undefined") {
|
||||
from = openedFiles[fd].position;
|
||||
}
|
||||
|
||||
if (!to || to > buffer.contentSize) {
|
||||
to = buffer.contentSize;
|
||||
}
|
||||
|
||||
if (from > buffer.contentSize) {
|
||||
from = buffer.contentSize;
|
||||
}
|
||||
|
||||
openedFiles[fd].position += to - from;
|
||||
return buffer.array.subarray(from, to);
|
||||
}
|
||||
|
||||
function write(fd, data, from) {
|
||||
if (DEBUG_FS) { console.log("fs write " + openedFiles[fd].path); }
|
||||
|
||||
if (typeof from == "undefined") {
|
||||
from = openedFiles[fd].position;
|
||||
}
|
||||
|
||||
var buffer = openedFiles[fd].buffer;
|
||||
|
||||
if (from > buffer.contentSize) {
|
||||
from = buffer.contentSize;
|
||||
}
|
||||
|
||||
var newLength = (from + data.byteLength > buffer.contentSize) ? (from + data.byteLength) : (buffer.contentSize);
|
||||
|
||||
buffer.setSize(newLength);
|
||||
|
||||
buffer.array.set(data, from);
|
||||
|
||||
var file = openedFiles[fd];
|
||||
file.position = from + data.byteLength;
|
||||
file.stat = { mtime: Date.now(), isDir: false, size: buffer.contentSize };
|
||||
file.dirty = true;
|
||||
}
|
||||
|
||||
function getpos(fd) {
|
||||
return openedFiles[fd].position;
|
||||
}
|
||||
|
||||
function setpos(fd, pos) {
|
||||
openedFiles[fd].position = pos;
|
||||
}
|
||||
|
||||
function getsize(fd) {
|
||||
if (!openedFiles[fd]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return openedFiles[fd].buffer.contentSize;
|
||||
}
|
||||
|
||||
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 (!openedFile.dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
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() {
|
||||
for (var fd = 0; fd < openedFiles.length; fd++) {
|
||||
if (!openedFiles[fd] || !openedFiles[fd].dirty) {
|
||||
continue;
|
||||
}
|
||||
flush(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Due to bug #227, we don't support Object::finalize(). But the Java
|
||||
// filesystem implementation requires the `finalize` method to save cached
|
||||
// file data if user doesn't flush or close the file explicitly. To avoid
|
||||
// losing data, we flush files periodically.
|
||||
setInterval(flushAll, 5000);
|
||||
|
||||
// Flush files when app goes into background.
|
||||
window.addEventListener("pagehide", flushAll);
|
||||
|
||||
function list(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs list " + path); }
|
||||
|
||||
store.getItem(path, function(files) {
|
||||
if (files == null || files instanceof Blob) {
|
||||
cb(null);
|
||||
} else {
|
||||
cb(files);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exists(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs exists " + path); }
|
||||
|
||||
stat(path, function(stat) {
|
||||
cb(stat ? true : false);
|
||||
});
|
||||
}
|
||||
|
||||
function truncate(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs truncate " + path); }
|
||||
|
||||
stat(path, function(stat) {
|
||||
if (stat && !stat.isDir) {
|
||||
store.setItem(path, new Blob());
|
||||
setStat(path, { mtime: Date.now(), isDir: false, size: 0 });
|
||||
cb(true);
|
||||
} else {
|
||||
cb(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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); });
|
||||
return;
|
||||
}
|
||||
|
||||
list(path, function(files) {
|
||||
if (files != null && files.length > 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var name = basename(path);
|
||||
var dir = dirname(path);
|
||||
|
||||
list(dir, function(files) {
|
||||
var index = -1;
|
||||
|
||||
if (files == null || (index = files.indexOf(name)) < 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
files.splice(index, 1);
|
||||
store.setItem(dir, files);
|
||||
store.removeItem(path);
|
||||
removeStat(path);
|
||||
cb(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createInternal(path, data, cb) {
|
||||
var name = basename(path);
|
||||
var dir = dirname(path);
|
||||
|
||||
list(dir, function(files) {
|
||||
if (files == null || files.indexOf(name) >= 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
files.push(name);
|
||||
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 });
|
||||
}
|
||||
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 });
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// Split the path into parts across "/", discarding the initial, empty part.
|
||||
var parts = normalizePath(path).split("/").slice(1);
|
||||
|
||||
var partPath = "";
|
||||
|
||||
function mkpart(created) {
|
||||
if (!created) {
|
||||
return cb(false);
|
||||
}
|
||||
|
||||
if (!parts.length) {
|
||||
return cb(true);
|
||||
}
|
||||
|
||||
partPath += "/" + parts.shift();
|
||||
|
||||
stat(partPath, function(stat) {
|
||||
if (!stat) {
|
||||
// The part doesn't exist; make it, then continue to next part.
|
||||
mkdir(partPath, mkpart);
|
||||
}
|
||||
else if (stat.isDir) {
|
||||
// The part exists and is a directory; continue to next part.
|
||||
mkpart(true);
|
||||
}
|
||||
else {
|
||||
// The part exists but isn't a directory; fail.
|
||||
console.error("mkdirp called on path with non-dir part: " + partPath);
|
||||
cb(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mkpart(true);
|
||||
}
|
||||
|
||||
function size(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs size " + path); }
|
||||
|
||||
store.getItem(path, function(blob) {
|
||||
if (blob == null || !(blob instanceof Blob)) {
|
||||
cb(-1);
|
||||
} else {
|
||||
cb(blob.size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Callers of this function should make sure
|
||||
// newPath doesn't exist.
|
||||
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); });
|
||||
return;
|
||||
}
|
||||
|
||||
list(oldPath, function(files) {
|
||||
if (files != null && files.length > 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
store.getItem(oldPath, function(data) {
|
||||
if (data == null) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
remove(oldPath, function(removed) {
|
||||
if (!removed) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data instanceof Blob) {
|
||||
create(newPath, data, cb);
|
||||
} else {
|
||||
mkdir(newPath, cb);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setStat(path, stat) {
|
||||
if (DEBUG_FS) { console.log("fs setStat " + path); }
|
||||
|
||||
store.setItem("!" + path, stat);
|
||||
}
|
||||
|
||||
function removeStat(path) {
|
||||
if (DEBUG_FS) { console.log("fs removeStat " + path); }
|
||||
|
||||
store.removeItem("!" + path);
|
||||
}
|
||||
|
||||
function stat(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs stat " + path); }
|
||||
|
||||
var file = openedFiles.find(function (file) { return file && file.stat && file.path === path });
|
||||
if (file) {
|
||||
setZeroTimeout(function() { cb(file.stat); });
|
||||
return;
|
||||
}
|
||||
|
||||
store.getItem("!" + path, cb);
|
||||
}
|
||||
|
||||
function clear(cb) {
|
||||
store.clear();
|
||||
initRootDir(cb || function() {});
|
||||
}
|
||||
|
||||
function storeSync(cb) {
|
||||
store.sync(cb);
|
||||
}
|
||||
|
||||
return {
|
||||
dirname: dirname,
|
||||
init: init,
|
||||
open: open,
|
||||
close: close,
|
||||
read: read,
|
||||
write: write,
|
||||
getpos: getpos,
|
||||
setpos: setpos,
|
||||
getsize: getsize,
|
||||
flush: flush,
|
||||
list: list,
|
||||
exists: exists,
|
||||
truncate: truncate,
|
||||
ftruncate: ftruncate,
|
||||
remove: remove,
|
||||
create: create,
|
||||
mkdir: mkdir,
|
||||
mkdirp: mkdirp,
|
||||
size: size,
|
||||
rename: rename,
|
||||
stat: stat,
|
||||
clear: clear,
|
||||
storeSync: storeSync,
|
||||
};
|
||||
})();
|
|
@ -4,8 +4,8 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="initial-scale=1.0">
|
||||
<script type="text/javascript" src="../libs/fs.js" defer></script>
|
||||
<script type="text/javascript" src="../timer.js" defer></script>
|
||||
<script type="text/javascript" src="../../libs/fs.js" defer></script>
|
||||
<script type="text/javascript" src="../../timer.js" defer></script>
|
||||
<script type="text/javascript">
|
||||
console.log = function() {
|
||||
var s = Array.prototype.join.call(arguments, ",") +"\n";
|
|
@ -809,15 +809,35 @@ tests.push(function() {
|
|||
});
|
||||
|
||||
tests.push(function() {
|
||||
fs.storeSync(function() {
|
||||
fs.syncStore(function() {
|
||||
// There's nothing we can check, since the sync status of the store
|
||||
// is private to the fs module, but we have at least confirmed that the call
|
||||
// resulted in the callback being called.
|
||||
ok(true, "storeSync callback called");
|
||||
ok(true, "syncStore callback called");
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
tests.push(function() {
|
||||
fs.create("/write-purge-read", new Blob(), function(created) {
|
||||
fs.open("/write-purge-read", function(fd) {
|
||||
fs.write(fd, new TextEncoder().encode("marco"));
|
||||
fs.close(fd);
|
||||
fs.purgeStore(function() {
|
||||
fs.open("/write-purge-read", function(fd) {
|
||||
var data = fs.read(fd);
|
||||
is(data.byteLength, 5, "read from a file with 5 bytes");
|
||||
is(new TextDecoder().decode(data), "marco", "read correct");
|
||||
fs.close(fd);
|
||||
fs.remove("/write-purge-read", function() {
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fs.init(function() {
|
||||
fs.clear(function() {
|
||||
next();
|
|
@ -0,0 +1,72 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script type="text/javascript" src="../../timer.js"></script>
|
||||
<script type="text/javascript" src="fs-v1.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function load(file, responseType) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", file, true);
|
||||
xhr.responseType = responseType;
|
||||
xhr.onload = function () {
|
||||
resolve(xhr.response);
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
reject();
|
||||
};
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
new Promise(function(resolve, reject) {
|
||||
fs.init(resolve);
|
||||
}).then(function() {
|
||||
return Promise.all([
|
||||
new Promise(function(resolve, reject) {
|
||||
fs.mkdir("/Persistent", resolve);
|
||||
}),
|
||||
|
||||
new Promise(function(resolve, reject) {
|
||||
fs.exists("/_main.ks", function(exists) {
|
||||
if (exists) {
|
||||
resolve();
|
||||
} else {
|
||||
load("../../certs/_main.ks", "blob").then(function(data) {
|
||||
fs.create("/_main.ks", data, function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}).then(function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.storeSync(resolve);
|
||||
});
|
||||
}).then(function() {
|
||||
document.body.appendChild(document.createTextNode("DONE"));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set shiftwidth=4 tabstop=4 autoindent cindent expandtab: */
|
||||
|
||||
var casper = require('casper').create({
|
||||
verbose: true,
|
||||
logLevel: "debug",
|
||||
});
|
||||
|
||||
casper.start("http://localhost:8000/tests/fs/make-fs-v1.html");
|
||||
|
||||
casper.waitForText(
|
||||
"DONE",
|
||||
function() {
|
||||
casper.exit(0);
|
||||
},
|
||||
function() {
|
||||
casper.exit(1);
|
||||
}
|
||||
);
|
||||
|
||||
casper.run();
|
Загрузка…
Ссылка в новой задаче