2021-08-12 23:19:39 +00:00
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
import { FileData, WorkingStore } from "../../workertypes";
|
2021-08-12 23:19:39 +00:00
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
// remote resource cache
|
|
|
|
var $$cache: WeakMap<object,FileData> = new WeakMap();
|
|
|
|
// file read/write interface
|
|
|
|
var $$store: WorkingStore;
|
|
|
|
// backing store for data
|
|
|
|
var $$data: {} = {};
|
2021-08-12 23:19:39 +00:00
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
export function $$setupFS(store: WorkingStore) {
|
|
|
|
$$store = store;
|
|
|
|
}
|
|
|
|
export function $$getData() {
|
|
|
|
return $$data;
|
|
|
|
}
|
|
|
|
export function $$loadData(data: {}) {
|
|
|
|
Object.assign($$data, data);
|
2021-08-12 23:19:39 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
// object that can load state from backing store
|
|
|
|
export interface Loadable {
|
2021-08-20 23:09:16 +00:00
|
|
|
// called during script, from io.data.load()
|
|
|
|
$$setstate?(newstate: {}) : void;
|
|
|
|
// called after script, from io.data.save()
|
2021-08-15 15:10:01 +00:00
|
|
|
$$getstate() : {};
|
2021-08-12 23:19:39 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
export namespace data {
|
2021-08-20 23:09:16 +00:00
|
|
|
export function load(object: Loadable, key: string): Loadable {
|
|
|
|
if (object == null) return object;
|
2021-08-15 15:10:01 +00:00
|
|
|
let override = $$data && $$data[key];
|
2021-08-20 23:09:16 +00:00
|
|
|
if (override && object.$$setstate) {
|
|
|
|
object.$$setstate(override);
|
|
|
|
} else if (override) {
|
|
|
|
Object.assign(object, override);
|
|
|
|
}
|
2021-08-15 15:10:01 +00:00
|
|
|
return object;
|
|
|
|
}
|
2021-08-20 23:09:16 +00:00
|
|
|
export function save(object: Loadable, key: string): Loadable {
|
2021-08-15 15:10:01 +00:00
|
|
|
if ($$data && object.$$getstate) {
|
|
|
|
$$data[key] = object.$$getstate();
|
|
|
|
}
|
|
|
|
return object;
|
2021-08-14 16:06:49 +00:00
|
|
|
}
|
2021-08-22 20:49:38 +00:00
|
|
|
export function get(key: string) {
|
|
|
|
return $$data && $$data[key];
|
|
|
|
}
|
|
|
|
export function set(key: string, value: object) {
|
|
|
|
if ($$data) {
|
|
|
|
$$data[key] = value;
|
|
|
|
}
|
|
|
|
}
|
2021-08-14 16:06:49 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
export class IOWaitError extends Error {
|
|
|
|
}
|
2021-08-14 16:06:49 +00:00
|
|
|
|
2021-08-12 23:19:39 +00:00
|
|
|
export function canonicalurl(url: string) : string {
|
|
|
|
// get raw resource URL for github
|
|
|
|
if (url.startsWith('https://github.com/')) {
|
|
|
|
let toks = url.split('/');
|
|
|
|
if (toks[5] === 'blob') {
|
|
|
|
return `https://raw.githubusercontent.com/${toks[3]}/${toks[4]}/${toks.slice(6).join('/')}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
export function clearcache() {
|
|
|
|
$$cache = new WeakMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
|
2021-08-12 23:19:39 +00:00
|
|
|
// TODO: only works in web worker
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
|
|
xhr.responseType = type === 'text' ? 'text' : 'arraybuffer';
|
|
|
|
xhr.open("GET", url, false); // synchronous request
|
|
|
|
xhr.send(null);
|
|
|
|
if (xhr.response != null && xhr.status == 200) {
|
2021-08-15 15:10:01 +00:00
|
|
|
if (type === 'text') {
|
|
|
|
return xhr.response as string;
|
2021-08-12 23:19:39 +00:00
|
|
|
} else {
|
2021-08-15 15:10:01 +00:00
|
|
|
return new Uint8Array(xhr.response);
|
2021-08-12 23:19:39 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error(`The resource at "${url}" responded with status code of ${xhr.status}.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 15:10:01 +00:00
|
|
|
export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
|
|
|
|
if (url.startsWith('http:') || url.startsWith('https:')) {
|
2021-08-18 20:44:13 +00:00
|
|
|
return fetchurl(url, type);
|
2021-08-15 15:10:01 +00:00
|
|
|
}
|
|
|
|
if ($$store) {
|
|
|
|
return $$store.getFileData(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: read files too
|
|
|
|
export function read(url: string, type?: 'binary' | 'text'): FileData {
|
|
|
|
url = canonicalurl(url);
|
|
|
|
// check cache
|
|
|
|
let cachekey = {url: url};
|
|
|
|
if ($$cache.has(cachekey)) {
|
|
|
|
return $$cache.get(cachekey);
|
|
|
|
}
|
|
|
|
let data = readnocache(url, type);
|
|
|
|
if (data == null) throw new Error(`Cannot find resource "${url}"`);
|
2021-08-18 20:44:13 +00:00
|
|
|
if (type === 'text' && typeof data !== 'string') throw new Error(`Resource "${url}" is not a string`);
|
|
|
|
if (type === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`);
|
2021-08-15 15:10:01 +00:00
|
|
|
$$cache.set(cachekey, data);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2021-08-14 16:06:49 +00:00
|
|
|
export function readbin(url: string): Uint8Array {
|
|
|
|
var data = read(url, 'binary');
|
2021-08-12 23:19:39 +00:00
|
|
|
if (data instanceof Uint8Array)
|
|
|
|
return data;
|
|
|
|
else
|
|
|
|
throw new Error(`The resource at "${url}" is not a binary file.`);
|
|
|
|
}
|
2021-08-15 15:10:01 +00:00
|
|
|
|
|
|
|
export function readlines(url: string) : string[] {
|
|
|
|
return (read(url, 'text') as string).split('\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function splitlines(text: string) : string[] {
|
|
|
|
return text.split(/\n|\r\n/g);
|
|
|
|
}
|
2021-08-20 23:09:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
// TODO: what if this isn't top level?
|
|
|
|
export class Mutable<T> implements Loadable {
|
|
|
|
value : T;
|
2021-08-22 20:49:38 +00:00
|
|
|
constructor(initial : T) {
|
2021-08-22 19:46:57 +00:00
|
|
|
this.value = initial;
|
2021-08-20 23:09:16 +00:00
|
|
|
}
|
|
|
|
$$setstate(newstate) {
|
|
|
|
this.value = newstate.value;
|
|
|
|
}
|
|
|
|
$$getstate() {
|
|
|
|
return { value: this.value };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-22 20:49:38 +00:00
|
|
|
export function mutable<T>(obj: object) : object {
|
2021-08-23 15:01:43 +00:00
|
|
|
Object.defineProperty(obj, '$$setstate', {
|
|
|
|
value: function(newstate) {
|
|
|
|
Object.assign(this, newstate);
|
|
|
|
},
|
|
|
|
enumerable: false
|
|
|
|
});
|
|
|
|
Object.defineProperty(obj, '$$getstate', {
|
|
|
|
value: function() {
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
enumerable: false
|
|
|
|
});
|
|
|
|
return obj;
|
2021-08-22 20:49:38 +00:00
|
|
|
}
|