v86/src/browser/filestorage.js
Ernest Wong a85dd50169 Filestorage: Improve assert coverage and robustness
Checks that sha256sum is also not undefrined nor null.
Avoids ReferenceError when window is not defined e.g. on NodeJS.
2020-08-30 19:37:15 -05:00

305 lines
8.6 KiB
JavaScript

"use strict";
const INDEXEDDB_STORAGE_VERSION = 1;
const INDEXEDDB_STORAGE_NAME = "IndexedDBFileStorage";
const INDEXEDDB_STORAGE_STORE = "Store";
const INDEXEDDB_STORAGE_KEYPATH = "sha256sum";
const INDEXEDDB_STORAGE_VALUEPATH = "data";
/** @interface */
function FileStorageInterface() {}
/**
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
FileStorageInterface.prototype.get = function(sha256sum) {};
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
* @return {!Promise}
*/
FileStorageInterface.prototype.set = function(sha256sum, buffer) {};
/**
* @constructor
* @implements {FileStorageInterface}
*/
function MemoryFileStorage()
{
/**
* From sha256sum to file data.
* @type {Map<string,Uint8Array>}
*/
this.filedata = new Map();
}
/**
* @param {string} sha256sum
* @return {Uint8Array}
*/
MemoryFileStorage.prototype.get = async function(sha256sum) // jshint ignore:line
{
dbg_assert(sha256sum, "MemoryFileStorage get: sha256sum should be a non-empty string");
return this.filedata.get(sha256sum);
}; // jshint ignore:line
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
*/
MemoryFileStorage.prototype.set = async function(sha256sum, buffer) // jshint ignore:line
{
dbg_assert(sha256sum, "MemoryFileStorage set: sha256sum should be a non-empty string");
this.filedata.set(sha256sum, buffer);
}; // jshint ignore:line
/**
* @constructor
* @implements {FileStorageInterface}
*/
function IndexedDBFileStorage()
{
dbg_assert(typeof window !== "undefined" && window.indexedDB,
"IndexedDBFileStorage - indexedDB not available.");
this.db = null;
}
IndexedDBFileStorage.try_create = async function() // jshint ignore:line
{
if(typeof window === "undefined" || !window.indexedDB)
{
throw new Error("IndexedDB is not available");
}
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 intiialized");
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);
};
open_request.onerror = event =>
{
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 =>
{
const db = open_request.result;
db.createObjectStore(INDEXEDDB_STORAGE_STORE, { keyPath: INDEXEDDB_STORAGE_KEYPATH });
};
open_request.onsuccess = event =>
{
this.db = open_request.result;
this.db.onabort = event =>
{
dbg_assert(false, "IndexedDBFileStorage: transaction aborted unexpectedly");
};
this.db.onclose = event =>
{
dbg_assert(false, "IndexedDBFileStorage: connection closed unexpectedly");
};
this.db.onerror = error =>
{
dbg_assert(false, "IndexedDBFileStorage: unexpected error: " + error);
};
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();
};
});
};
/**
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
IndexedDBFileStorage.prototype.get = function(sha256sum)
{
dbg_assert(this.db, "IndexedDBFileStorage get: Database is not initialized");
dbg_assert(sha256sum, "IndexedDBFileStorage get: sha256sum should be a non-empty string");
return new Promise((resolve, reject) =>
{
const transaction = this.db.transaction(INDEXEDDB_STORAGE_STORE, "readonly");
const store = transaction.objectStore(INDEXEDDB_STORAGE_STORE);
const request = store.get(sha256sum);
request.onsuccess = event =>
{
dbg_assert(!request.result || request.result.data instanceof Uint8Array,
"IndexedDBFileStorage get: invalid entry format: " + request.result);
if(request.result && request.result.data instanceof Uint8Array)
{
resolve(request.result.data);
}
else
{
resolve(null);
}
};
});
};
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
* @return {!Promise}
*/
IndexedDBFileStorage.prototype.set = function(sha256sum, data)
{
dbg_assert(this.db, "IndexedDBFileStorage set: Database is not initialized");
dbg_assert(sha256sum, "IndexedDBFileStorage set: sha256sum should be a non-empty string");
return new Promise((resolve, reject) =>
{
const transaction = this.db.transaction(INDEXEDDB_STORAGE_STORE, "readwrite");
const store = transaction.objectStore(INDEXEDDB_STORAGE_STORE);
const request = store.put({
[INDEXEDDB_STORAGE_KEYPATH]: sha256sum,
[INDEXEDDB_STORAGE_VALUEPATH]: 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