Filestorage: Refactor fallback and load-from-server

Fallback logic is moved to the caller's responsibility.

Empty FileStorages and FileStorages that load from the server are
separated into different classes. This is to avoid faults where the
caller of the constructor forgets to pass in a `baseurl` parameter and
leads to some confusing bug.
This commit is contained in:
Ernest Wong 2018-11-13 01:28:03 +13:00 committed by Fabian
parent c029944965
commit c2dcb440dd
2 changed files with 185 additions and 112 deletions

View file

@ -25,12 +25,9 @@ FileStorageInterface.prototype.set = function(sha256sum, buffer) {};
/**
* @constructor
* @implements {FileStorageInterface}
* @param {string=} baseurl
*/
function MemoryFileStorage(baseurl)
function MemoryFileStorage()
{
this.baseurl = baseurl;
/**
* From sha256sum to file data.
* @type {Map<string,Uint8Array>}
@ -39,95 +36,64 @@ function MemoryFileStorage(baseurl)
}
/**
* @private
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
* @return {Uint8Array}
*/
MemoryFileStorage.prototype.load_from_server = function(sha256sum)
{
return new Promise((resolve, reject) =>
{
if(!this.baseurl)
{
resolve(null);
return;
}
v86util.load_file(this.baseurl + sha256sum, { done: buffer =>
{
const data = new Uint8Array(buffer);
this.filedata.set(sha256sum, data);
resolve(data);
}});
});
};
/**
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
MemoryFileStorage.prototype.get = function(sha256sum)
MemoryFileStorage.prototype.get = async function(sha256sum) // jshint ignore:line
{
dbg_assert(sha256sum !== "", "MemoryFileStorage get: sha256sum should not be an empty string");
return new Promise((resolve, reject) =>
{
if(this.filedata.has(sha256sum))
{
resolve(this.filedata.get(sha256sum));
}
else
{
this.load_from_server(sha256sum).then(data => resolve(data));
}
});
};
return this.filedata.get(sha256sum);
}; // jshint ignore:line
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
* @return {!Promise}
*/
MemoryFileStorage.prototype.set = function(sha256sum, buffer)
MemoryFileStorage.prototype.set = async function(sha256sum, buffer) // jshint ignore:line
{
dbg_assert(sha256sum !== "", "MemoryFileStorage set: sha256sum should not be an empty string");
return new Promise((resolve, reject) =>
{
this.filedata.set(sha256sum, buffer);
resolve();
});
};
this.filedata.set(sha256sum, buffer);
}; // jshint ignore:line
/**
* @constructor
* @implements {FileStorageInterface}
* @param {string=} baseurl
*/
function IndexedDBFileStorage(baseurl)
function IndexedDBFileStorage()
{
this.fallback_storage = new MemoryFileStorage(baseurl);
this.baseurl = baseurl;
dbg_assert(window.indexedDB, "IndexedDBFileStorage - indexedDB not available.");
this.db = null;
}
if(typeof indexedDB === "undefined")
IndexedDBFileStorage.try_create = async function() // jshint ignore:line
{
if(typeof window === "undefined" || !window.indexedDB)
{
dbg_log("IndexedDB not available. Using MemoryFileStorage as fallback.", LOG_9P);
throw new Error("IndexedDB is not available");
}
else
const file_storage = new IndexedDBFileStorage();
await file_storage.init(); // jshint ignore:line
return file_storage;
}; // jshint ignore:line
IndexedDBFileStorage.prototype.init = function()
{
dbg_assert(!this.db, "IndexedDBFileStorage init: Database already intiialised");
return new Promise((resolve, reject) =>
{
const open_request = indexedDB.open(INDEXEDDB_STORAGE_NAME, INDEXEDDB_STORAGE_VERSION);
open_request.onblocked = event =>
{
dbg_log("IndexedDB blocked by an older database version being opened.", LOG_9P);
dbg_log("Using MemoryFileStorage until unblocked.", LOG_9P);
};
open_request.onerror = event =>
{
dbg_log("Error opening IndexedDB! Are you in private browsing mode? ", LOG_9P);
dbg_log("Falling back to MemoryFileStorage. Error: " + open_request.error, LOG_9P);
dbg_log("Error opening IndexedDB! Are you in private browsing mode? Error:", LOG_9P);
dbg_log(open_request.error, LOG_9P);
reject();
};
open_request.onupgradeneeded = event =>
@ -138,9 +104,6 @@ function IndexedDBFileStorage(baseurl)
open_request.onsuccess = event =>
{
// Fallback no longer needed.
this.fallback_storage = null;
this.db = open_request.result;
this.db.onabort = event =>
{
@ -156,34 +119,12 @@ function IndexedDBFileStorage(baseurl)
};
this.db.onversionchange = event =>
{
// TODO: double check this message
dbg_log("Warning: another v86 instance is trying to open IndexedDB database but " +
"is blocked by this current v86 instance.", LOG_9P);
};
resolve();
};
}
}
/**
* @private
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
IndexedDBFileStorage.prototype.load_from_server = function(sha256sum)
{
dbg_assert(this.db, "IndexedDBFileStorage load_from_server called without opening database");
return new Promise((resolve, reject) =>
{
if(!this.baseurl)
{
resolve(null);
return;
}
v86util.load_file(this.baseurl + sha256sum, { done: buffer =>
{
const data = new Uint8Array(buffer);
this.set(sha256sum, data).then(() => resolve(data));
}});
});
};
@ -193,13 +134,9 @@ IndexedDBFileStorage.prototype.load_from_server = function(sha256sum)
*/
IndexedDBFileStorage.prototype.get = function(sha256sum)
{
dbg_assert(this.db, "IndexedDBFileStorage get: Database is not initialised");
dbg_assert(sha256sum !== "", "IndexedDBFileStorage get: sha256sum should not be an empty string");
if(!this.db)
{
return this.fallback_storage.get(sha256sum);
}
return new Promise((resolve, reject) =>
{
const transaction = this.db.transaction(INDEXEDDB_STORAGE_STORE, "readonly");
@ -216,7 +153,7 @@ IndexedDBFileStorage.prototype.get = function(sha256sum)
}
else
{
this.load_from_server(sha256sum).then(data => resolve(data));
resolve(null);
}
};
});
@ -229,13 +166,9 @@ IndexedDBFileStorage.prototype.get = function(sha256sum)
*/
IndexedDBFileStorage.prototype.set = function(sha256sum, data)
{
dbg_assert(this.db, "IndexedDBFileStorage set: Database is not initialised");
dbg_assert(sha256sum !== "", "IndexedDBFileStorage set: sha256sum should not be an empty string");
if(!this.db)
{
return this.fallback_storage.get(sha256sum);
}
return new Promise((resolve, reject) =>
{
const transaction = this.db.transaction(INDEXEDDB_STORAGE_STORE, "readwrite");
@ -247,3 +180,124 @@ IndexedDBFileStorage.prototype.set = function(sha256sum, data)
request.onsuccess = event => resolve();
});
};
/**
* @constructor
* @implements {FileStorageInterface}
* @param {string} baseurl
*/
function ServerMemoryFileStorage(baseurl)
{
dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty");
this.empty_storage = new MemoryFileStorage();
this.baseurl = baseurl;
}
/**
* @private
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
ServerMemoryFileStorage.prototype.load_from_server = function(sha256sum)
{
return new Promise((resolve, reject) =>
{
v86util.load_file(this.baseurl + sha256sum, { done: buffer =>
{
const data = new Uint8Array(buffer);
this.set(sha256sum, data).then(() => resolve(data));
}});
});
};
/**
* @param {string} sha256sum
* @return {Uint8Array}
*/
ServerMemoryFileStorage.prototype.get = async function(sha256sum) // jshint ignore:line
{
const data = await this.empty_storage.get(sha256sum); // jshint ignore:line
if(!data)
{
return await this.load_from_server(sha256sum); // jshint ignore:line
}
return data;
}; // jshint ignore:line
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
*/
ServerMemoryFileStorage.prototype.set = async function(sha256sum, data) // jshint ignore:line
{
await this.empty_storage.set(sha256sum, data); // jshint ignore:line
}; // jshint ignore:line
/**
* @constructor
* @implements {FileStorageInterface}
* @param {string} baseurl
*/
function ServerIndexedDBFileStorage(baseurl)
{
dbg_assert(baseurl, "ServerIndexedDBFileStorage: baseurl should not be empty");
this.empty_storage = new IndexedDBFileStorage();
this.baseurl = baseurl;
}
/**
* @param {string} baseurl
* @return {IndexedDBFileStorage}
*/
ServerIndexedDBFileStorage.try_create = async function(baseurl) // jshint ignore:line
{
if(typeof window === "undefined" || !window.indexedDB)
{
throw new Error("IndexedDB is not available");
}
const file_storage = new ServerIndexedDBFileStorage(baseurl);
await file_storage.empty_storage.init(); // jshint ignore:line
return file_storage;
}; // jshint ignore:line
/**
* @private
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
ServerIndexedDBFileStorage.prototype.load_from_server = function(sha256sum)
{
return new Promise((resolve, reject) =>
{
v86util.load_file(this.baseurl + sha256sum, { done: buffer =>
{
const data = new Uint8Array(buffer);
this.set(sha256sum, data).then(() => resolve(data));
}});
});
};
/**
* @param {string} sha256sum
* @return {Uint8Array}
*/
ServerIndexedDBFileStorage.prototype.get = async function(sha256sum) // jshint ignore:line
{
const data = await this.empty_storage.get(sha256sum); // jshint ignore:line
if(!data)
{
return await this.load_from_server(sha256sum); // jshint ignore:line
}
return data;
}; // jshint ignore:line
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
*/
ServerIndexedDBFileStorage.prototype.set = async function(sha256sum, data) // jshint ignore:line
{
await this.empty_storage.set(sha256sum, data); // jshint ignore:line
}; // jshint ignore:line

View file

@ -218,7 +218,7 @@ function V86Starter(options)
});
}
V86Starter.prototype.continue_init = function(emulator, options)
V86Starter.prototype.continue_init = async function(emulator, options) // jshint ignore:line
{
this.bus.register("emulator-stopped", function()
{
@ -448,11 +448,20 @@ V86Starter.prototype.continue_init = function(emulator, options)
var fs_url = options["filesystem"]["basefs"];
var base_url = options["filesystem"]["baseurl"];
const file_storage = typeof indexedDB === "undefined" ?
new MemoryFileStorage(base_url) :
new IndexedDBFileStorage(base_url);
this.fs9p = new FS(file_storage);
settings.fs9p = this.fs9p;
const IdealFileStorage = base_url ? ServerIndexedDBFileStorage : IndexedDBFileStorage;
const FallbackFileStorage = base_url ? ServerMemoryFileStorage : MemoryFileStorage;
let file_storage;
try
{
file_storage = await IdealFileStorage.try_create(base_url); // jshint ignore:line
}
catch(e)
{
dbg_log("Initializing IndexedDBFileStorage failed due to Error: " + e);
dbg_log("Falling back to MemoryFileStorage instead.");
file_storage = new FallbackFileStorage(base_url);
}
settings.fs9p = this.fs9p = new FS(file_storage);
if(fs_url)
{
@ -605,7 +614,7 @@ V86Starter.prototype.continue_init = function(emulator, options)
this.emulator_bus.send("emulator-loaded");
}
}
};
}; // jshint ignore:line
V86Starter.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
{
@ -1046,11 +1055,21 @@ V86Starter.prototype.serial0_send = function(data)
* @param {function(Object)=} callback
* @export
*/
V86Starter.prototype.mount_fs = function(path, baseurl, basefs, callback)
V86Starter.prototype.mount_fs = async function(path, baseurl, basefs, callback) // jshint ignore:line
{
const file_storage = typeof indexedDB === "undefined" ?
new MemoryFileStorage(baseurl) :
new IndexedDBFileStorage(baseurl);
const IdealFileStorage = baseurl ? ServerIndexedDBFileStorage : IndexedDBFileStorage;
const FallbackFileStorage = baseurl ? ServerMemoryFileStorage : MemoryFileStorage;
let file_storage;
try
{
file_storage = await IdealFileStorage.try_create(baseurl); // jshint ignore:line
}
catch(e)
{
dbg_log("Initializing IndexedDBFileStorage failed due to Error: " + e);
dbg_log("Falling back to MemoryFileStorage instead.");
file_storage = new FallbackFileStorage(baseurl);
}
const newfs = new FS(file_storage, this.fs9p.qidcounter);
const mount = () =>
{
@ -1086,7 +1105,7 @@ V86Starter.prototype.mount_fs = function(path, baseurl, basefs, callback)
{
mount();
}
};
}; // jshint ignore:line
/**
* Write to a file in the 9p filesystem. Nothing happens if no filesystem has