UNPKG

@d3x0r/sack-gui

Version:

SACK abstraction library exposed to JS to provide low level system services.

1,193 lines (1,075 loc) 38.8 kB
const _debug_osr = false; // object storage remote ( general debugging added in forked copy) var dangling = []; var objectRefs = 0; var currentContainer = null; var preloadStorage = null; //let remote = null; let sack = null; let unique = 0; const _debug_ll = false; // /low level message receive const _debug_put = false; const _debug_get = false; const _debug_get_ll = false; // after read before parse const _debug_stringify = false; const _debug_danging = false; const _debug_map = false; // reloading related object debugging let util; import( "util" ).then( u=>{ util = u decoder = new u.TextDecoder(); } ); //import util from "util"; //import process from "process"; //process.stdout.write( "blah?" ); try { JSOX.updateContext(); }catch(err) { console.log( "Let me guess.... no update?", err ); } const this_ = this || globalThis; this_.ObjectStorage = ObjectStorage; console.log( "Updating this object to include ObjectStorage?", this_ ); //this.storage = new ObjectStorage(); function ObjectStorage( sack_, ws ) { const sack = sack_; this.sack = sack_; this.ws = ws; ws.storage = this; //console.log( "Initialize as object storage?", this ); //const newStorage = await connect( remote ); const newStorage = this; const requests = new Map(); // associates object data with storage data for later put(obj) to re-use the same informations. function objectStorageContainer(o,opts) { if( !( this instanceof objectStorageContainer || Object.getPrototypeOf( this ).constructor.name === "objectStorageContainer" ) ) return new newStorage.objectStorageContainer(o,opts); if( "undefined" !== typeof o ) { // this callback is used often from JSOX, and all parameters are undefined.... // content of the container is filed in later instead of in the constructor. //console.log( "making a new storage container:-----------------------------------------------------", o ); //console.trace( "Something creating a new container..", o, opts ); try { if( "string" === typeof o ){ // still, if the proir didn't resolve, need to resolve this.. let existing = newStorage.cachedContainer.get( o ); if( existing ) { o = existing.data; if( existing.resolve ) return ; } } var resolve = o && !opts; this.data = o || {}; // reviving we get null opts. if( !o && opts && opts.ref ) { console.trace( "ref loading is deprecated."); //resolve = opts.ref; } currentContainer = this; Object.defineProperty( this, "encoding", { writable:true, value:false } ); /* this.data = { //nonce : opts?opts.sign?sack.SaltyRNG.sign( JSOX.stringify(o), 3, 3 ):null:null, data : o } */ if( opts && opts.sign ) { var v = this_.sack.SaltyRNG.verify( JSOX.stringify(o), this.data.nonce, 3, 3 ); //console.log( "TEST:", v ); Object.defineProperty( this, "id", { value:v.key } ); v.key = this.data.nonce; this.data.nonce = v; } else { if( opts && opts.id ) { _debug_get && console.log( "SOMETHING", opts ); Object.defineProperty( this, "id", { value:opts.id } ); _debug_get && console.log( "Conditioning THis with a ID:", opts.id, this.id ); } } }catch(err) { console.log( "Uncaught exception:", err ); } } //console.log( "Container:", this ); } objectStorageContainer.prototype.getStore = function() { return newStorage; } objectStorageContainer.getStore = function() { return newStorage; } objectStorageContainer.prototype.map = async function( opts ) { const pending = this.dangling; _debug_get && console.log( "pending:", this, this.pending ); if( pending && pending.length ) { opts = opts || { depth:0, paths:[] }; const rootMap = this; return new Promise( (res,rej)=>{ let waiting = 0; if( ( "paths" in opts ) && opts.paths.length ){ let nPath = 0; const handled = []; while(nPath < opts.paths.length ){ var path = opts.paths[nPath++]; var obj = this.data; for( var step of path ){ if( obj ) obj = obj[step]; else break; } if( obj ) { for( let load of pending ){ if( load.d.p === obj ){ //console.log( "This should get marked resolved maybe?") handled.push( load ); loadPending( load ); obj = null; break; } } if( obj ) console.log( "Failed to find promise?"); } else console.log( "Failed to find path in object:", this.data, path ); for( let h of handled ) { var idx = pending.findIndex( p=>p===h ); if( idx >= 0 ) pending.splice( idx, 1 ); else console.log( "resolved load was not found???" ); } } } else{ // load everything that's pending on this object. for( let load of pending ) { { const existing = newStorage.cachedContainer.get( load.d.id ); if( existing ) { if( load.d.res ) load.d.res( existing.data ); else {load.d.p.then( o2=>{ if( existing.data!==o2) throw new Error( "resolved and loaded object mismatch"); return o2 }) return load.d.p; } } } loadPending(load); } pending.length = 0; } if( !waiting ){ console.log( "Nothing scheduled to really wait, go ahead and resolve"); res( rootMap.data ); } function loadPending(load) { waiting++; newStorage.get( {id:load.d.id}).then( (obj)=>{ if( load.d.res ) load.d.res(obj); // result with real value. else { load.d.p.then( o2=>{if( obj!==o2) throw new Error( "resolved and loaded object mismatch");return o2}) return load.d.p; } const exist = newStorage.stored.get( obj ); const objc = newStorage.cachedContainer.get( exist ); // resolving this promis on load.d will set this. //load.d.r.o[load.d.r.f] = obj; if( opts && opts.depth ) { objc.map( {depth:opts.depth-1} ).then( (objc)=>{ waiting--; if( !waiting ) { //console.log( "1map is resolving with : ", a, rootMap.data ); res( rootMap.data ); } }); } else { waiting--; if( !waiting ) { //console.log( "2map is resolving with : ", rootMap.data ); res( rootMap.data ); } } }) } }) } console.log( "Nothing dangling found on object"); return this.data; // this function is async, just return. } objectStorageContainer.prototype.createIndex = function( storage, fieldName, opts ) { if( !fieldName ) throw new Error( "Must specify an object field to index" ); var path; if( !fieldName.isArray() ) { if( typeof(fieldName) != "String") throw new Error( "Index field name must be a string, or an array." ); path = fieldName.split('.' ); }else path = fieldName; var referringObject = null; var end = path.reduce( (acc,val)=>{ referringObject = acc; if( acc ) { let tmp = acc[val]; if( tmp === undefined ) if( val < (path.length-1) ) { // automatically build object path if( typeof(path[val+1]) === "String" ) acc[val] = tmp = {}; else acc[val] = tmp = []; } acc = tmp; } return acc; }, this.data ); if( end ) { if( !end.isArray() ){ throw new Error( "Indexes can only be applied to arrays."); } }else if( referringObject ){ // automatically assign a value end = referringObject[path[path.length-1]] = []; }else { throw new Error( "Path to index could not be found") } } var mapping = false; preloadStorage = null; newStorage.objectStorageContainer = objectStorageContainer; newStorage.cached = new Map(); newStorage.cachedContainer = new Map(); newStorage.stored = new WeakMap(); // objects which have alredy been through put()/get() newStorage.decoding = []; newStorage.pending = []; newStorage.setupStringifier = setupStringifier; newStorage.stringifier = JSOX.stringifier(); newStorage.parser = null; // this will be filled when .get() is called. newStorage.currentParser = null; newStorage.root = null; // this gets filled when the root file system is enabled. newStorage.encoders = []; newStorage.decoders = []; newStorage.remotes = [newStorage]; function objectToJSOX( stringifier ){ // see if we alread stored this... (or are currently storing this.) (back references container) //console.log( "THIS GOT CALLED?", this, Object.getPrototypeOf( this ) ); var exist = newStorage.stored.get( this ); if( exist ) { var obj = newStorage.cachedContainer.get( exist ); //console.log( "Object stored independantly... RECOVERED:", exist, obj.encoding ); if( obj.encoding ) return this; else { //console.log( "Why is this an ~or and not ~os?"); return '~or"'+exist+'"'; } } else { if( this instanceof objectStorageContainer || Object.getPrototypeOf( this ).constructor.name === "objectStorageContainer" ) { //console.log( "THIS SHOULD ALREADY BE IN THE STORAGE!", this, newStorage.stored.get( this.data ) ); _debug_put && console.log( "Storing object:", this.data, this.id ); newStorage.stored.set( this.data, this.id ); // maybe update the ID though. //return thsi{data:this.data}; } } //console.log( "not a container..."); return this; } function setupStringifier( stringifier ) { _debug_stringify && console.log( "Stringifier SETUP - add ~os" ); stringifier.setDefaultObjectToJSOX( objectToJSOX ); stringifier.registerToJSOX( "~os", objectStorageContainer, objectToJSOX ); stringifier.store = newStorage; for( let f of newStorage.encoders ) stringifier.registerToJSOX( f.tag, f.p, f.f ) ; } setupStringifier( newStorage.stringifier ); // // options = { // depth : maximum distance to load... // } // const defaultOpts = {depth:0}; newStorage.map = function( o, opts ) { //newStorage.mapping = true; //this.parser. if( !opts ) opts = defaultOpts; //if( opts && opts.depth === 0 ) return; _debug_map && console.log( "External API invoked map...", o, newStorage.stored ); const rootId = newStorage.stored.get( o ); if( !rootId ) { console.log( "Object was not stored, cannot map" ); return Promise.resolve( o ); } return newStorage.cachedContainer.get( rootId ).map( opts ); } newStorage.handleMessage = handleMessage newStorage. remote = { get(opts){ //console.log( "this get?" ); return new Promise( (res,rej)=>{ const msg = {op:"get", id:unique++, opts:opts, res:res,rej:rej }; ws.send( msg ); requests.set( msg.id, msg ); } ) }, put(obj, rid,res,rej){ const msg = {op:"put", id:unique++, data:obj, rid:rid, res:res,rej:rej }; ws.send( msg ); requests.set( msg.id, msg ); }, handleMessage : handleMessage } function handleMessage( msg ) { _debug_ll && console.log( "Storage Remote Handler:", msg ); try { if( msg.op === "GET" ) { const req = requests.get( msg.id ); requests.delete(msg.id); //console.log( "Found request?", req ); _debug_get && console.log( "result:", msg ); const data = (msg.data && ( msg.data instanceof ArrayBuffer || Object.getPrototypeOf( msg.data ).constructor.name === "ArrayBuffer" ))?decoder.decode( msg.data ) : msg.data; //if( _debug_get && console.log( "Resolving request?", req, data, decoder.decode(msg.data) ); if( req ) req.res( data ); return true; } if( msg.op === "Get" ) { const req = requests.get( msg.id ); requests.delete(msg.id); _debug_get && console.log( "Rejecting get reply..", msg.err ); if( req ) req.rej( msg.err ); return true; } if( msg.op === "PUT" ) { const req = requests.get( msg.id ); requests.delete(msg.id); if( req && req.res ) { // sometimes it's write-only? _debug_put && console.log( "Something",req ); req.res( msg.r ); } return true; } if( msg.op === "Put" ) { const req = requests.get( msg.id ); requests.delete(msg.id); if( req ) { _debug_put && console.log( "Req:", req ); req.rej( msg.err ); } return true; } }catch(err){ console.log( "something message error:", err ); } return false; } return newStorage; } ObjectStorage.prototype.getContainer = function( obj, options ) { var container = this_.stored.get( obj ); var storage; if( container ) { container = this_.cachedContainer.get( container ); return container; } if( !options.exist ) { //console.log( "Getting a new container...", container.id ); container = new objectStorageContainer(obj,options); console.log( "Manufacture new container.", object, container.id ); this_.stored.set( obj, container.id ); this_.cached.set( container.id, container.data ); this_.cachedContainer.set( container.id, container ); return container; } } ObjectStorage.prototype.remove = function( opts ) { if( "string" === typeof opts ) { var container = this.cached.get( opts ); if( !container ) throw new Error( "This is not a tracked object." ); this.delete( opts ); } else if( "object" === typeof opts ) { var container = this.stored.get( opts ); if( !container ) throw new Error( "This is not a tracked object." ); this.delete( container); } } ObjectStorage.prototype.addEncoders = function(encoderList) { const this_ = this; encoderList.forEach( f=>{ this_.encoders.push(f); this_.stringifier.registerToJSOX( f.tag, f.p, f.f ) ; }); } ObjectStorage.prototype.addDecoders = function(encoderList) { const this_ = this; encoderList.forEach( f=>{ this_.decoders.push(f); if( this.parser ) this.parser.fromJSOX( f.tag, f.p, f.f ); }); } ObjectStorage.prototype.getCurrentParseRef = function() { if( this.currentParser ){ return this.currentParser.getCurrentRef(); } return null; } ObjectStorage.prototype.stringify = function( obj ) { const containerId = this.stored.get( obj ); if( containerId ) { const container = this.cachedContainer.get( containerId ); const stringifier = this.stringifier; if( container ) { container.encoding = true; const storage = stringifier.stringify( container ); container.encoding = false; return storage; } } return stringifier.stringify( container ); } // this hides the original 'put' ObjectStorage.prototype.put = function( obj, opts ) { const this_ = this; if( currentContainer && currentContainer.data === this ) return Promise.resolve( currentContainer.id ); return new Promise( function(res,rej){ var container = this_.stored.get( obj ); if( !container && opts && opts.id ) { const oldObj = this_.cached.get( opts.id ); if( oldObj ) container = this_.stored.get( oldObj ); console.log( "new object, old ID, ..." ); } _debug_osr && console.log( "Server Put found object?", container, obj, opts ); if( container ) { container = this_.cachedContainer.get( container ); if( obj !== container.data ) { console.log( "Overwrite old data with new?", container.data, obj ); coontainer.data = obj; } if( !container.nonce ) { // make sure every item that is in an index // has been written... var stringifier; if( opts && opts.extraEncoders ) { _debug_stringify && console.log( "New stringifier because of optional encoders" ); stringifier = JSOX.stringifier(); this_.setupStringifier( stringifier ); opts.extraEncoders.forEach( f=>{ stringifier.registerToJSOX( f.tag, f.p, f.f ) }); }else { _debug_stringify && console.log( "use stroage stringifier" ); stringifier = this_.stringifier; } container.encoding = true; storage = stringifier.stringify( container ); _debug_stringify && console.log( "Stringified container into:", storage, container ); container.encoding = false; if( !container.id || container.id === "null" ) { console.trace( "0) Container has no ID or is nUll", container ); } _debug_osr && console.log( "WRite:", container, storage ); for( var remote of this_.remotes ) { remote.remote.put( storage, container.id, res, rej ); res = null; rej = null; } //this_.writeRaw( container.id, storage ); //return res( container.id ); } else { throw new Error( "record is signed, cannot put" ); } } if( opts && opts.id ) { var stringifier; if( opts.extraEncoders ) { stringifier = JSOX.stringifier(); this_.setupStringifier( stringifier ); opts.extraEncoders.forEach( f=>{ stringifier.registerToJSOX( f.tag, f.p, f.f ) }); }else { stringifier = this_.stringifier; } // raw object; no encoding to set. storage = stringifier.stringify( obj ); _debug_stringify && console.log( "Stringified object into ", storage, obj ); if( !opts.id || opts.id === "null" ) { console.trace( "Container has no ID or is nUll", container ); } _debug_osr && console.trace( "WRite:", opts, storage ); for( var remote of this_.remotes ) { try { remote.remote.put( storage, opts.id, res, rej ) }catch(err){console.log( "ERR:",err)} res = null; rej = null; } //this_.writeRaw( opts.id, storage ); //res( opts.id ); } else if( !opts || !opts.id ) { _debug_osr && console.log( "New bare object, create a container...", opts ); if( !opts ) opts = { id : this_.sack.id() } else opts.id = this_.sack.id(); container = new this_.objectStorageContainer(obj,opts); //console.log( "New container looks like... ", container.id, container ); //console.log( "saving stored container.id", obj, container.id ); //this.stored.delete( obj ); _debug_put && console.log( "Created new container for object(2)", obj ); this_.stored.set( obj, container.id ); this_.cached.set( container.id, container.data ); this_.cachedContainer.set( container.id, container ); var stringifier; if( opts.extraEncoders ) { _debug_stringify && console.log( "Need a custom stringifier because of options" ); stringifier = JSOX.stringifier(); this_.setupStringifier( stringifier ); opts.extraEncoders.forEach( f=>{ stringifier.registerToJSOX( f.tag, f.p, f.f ) }); }else { _debug_stringify && console.log( "Using this_ stringifier" ); stringifier = this_.stringifier; } container.encoding = true; storage = stringifier.stringify( container ); _debug_stringify && console.log( "Sringified container(2) into", storage, container ); container.encoding = false; if( !container.id || container.id === "null" ) { console.trace( "Container has no ID or is nUll", container ); } _debug_osr && console.trace( "Outut container to storage... ", container, storage ); try { for( var remote of this_.remotes ) { remote.remote.put( storage, container.id, res, rej ); res = null; rej = null; } //this_.writeRaw( container.id, storage ); }catch(err) { console.log( "error WRITE RAW?", this_ ) } //console.log( "OUTPUT:", storage ); //res( container.id ); } }) } /* ObjectStorage.prototype.update( objId, obj ) { var container = new this.objectStorageContainer(JSOX.stringify(obj),sign); this.stored.set( obj, container.id ); this.cached.set( container.id, container ); return container.id; } */ const updatedPrototypes = new WeakMap(); var currentReadId ; const ackObjects = []; ObjectStorage.prototype.get = function( opts ) { //this.parser. const os = this; const extraDecoders = opts && opts.extraDecoders || null; _debug_stringify && console.log( "This is the big get... "); if( "string" === typeof opts ) { opts = { id:opts }; } if( !opts ){ console.trace( "Must specify options ", opts); return null; } { const priorLoad = os.cachedContainer.get( opts.id ); if( priorLoad ) return Promise.resolve( priorLoad.data ); } const priorDecode = this.decoding.find( d=>d.id === opts.id ); if( priorDecode ){ return new Promise( (res,rej)=>{ priorDecode.res = res; priorDecode.rej = rej; }).then( (obj)=>{ let deleteId = -1; for( let n = 0; n < this.decoding.length; n++ ) { if( decode === opts ){ deleteId = n; break; } } if( deleteId >= 0 ) this.decoding.splice( deleteId, 1 ); }) } if( !this.parser ){ this.parser = JSOX.begin(reviveObject); this.parser.fromJSOX( "~os", this.objectStorageContainer, reviveContainer ); // I don't know ahead of time which this is. this.parser.fromJSOX( "~or", objectStorageContainerRef, reviveContainerRef ); // I don't know ahead of time which this is. for( let f of this.decoders ) this.parser.fromJSOX( f.tag, f.p, f.f ); } function reviveObject(o ) { // this callback updates from time to time so.... // this is JSOX parse callback, called every time there is a object revived. // this will be the next object to satisfy ack. _debug_get_ll && console.log( "Resolved buffer (pending):", ackObjects.length, o, o.dangling ); const ackObject = ackObjects.shift(); if( ackObject ) { //console.log( "ACK:", ackObject ); ackObject.cb(o,ackObject.id); // this is internal callback ackObject.res( o ); // this is final return to waiter. } } function objectStorageContainerRef( s ) { _debug_get && console.log( "Container ref:", s ); try { const existing = os.cachedContainer.get(s); const here = os.getCurrentParseRef(); //console.log( "Conainer ref, this will resolve in-place") this.d = {id:s,p:null,res:null,rej:null}; if( !existing ) { this.d.p = new Promise( (res,rej)=>{ this.d.res = res; this.d.rej = rej; }).then( (obj)=>{ _debug_get && console.log( "(DOES THIS HAPPEN?)OBJ REPLACE OBJECT WITH:", here, obj ) return (here.o[here.f] = obj) }); } else { this.d.p = Promise.resolve( existing.data ); // this will still have to be swapped. } dangling.push( this ); _debug_get && console.log( "adding a dangling object...", dangling ); objectRefs++; } catch(err) { console.log( "Init failed:", err)} } function reviveContainer( field, val ) { //console.trace( "Revival of a container's field:", this, field, val ); if( !field ) { // finished. _debug_get && console.log( "Final container revive:", objectRefs, this.id ); if( objectRefs ) { Object.defineProperty( this, "dangling", { value:dangling } ); dangling = []; objectRefs = 0; } _debug_get && console.log( "CURRENT READID:",currentReadId ); Object.defineProperty( this, "id", { value:currentReadId } ); return this; } else { { const this_ = this; // new value isn't anything special; just set the value. //console.log( "This sort of thing... val is just a thing - like a key part identifier...; but that should have been a container."); if( val instanceof Promise || Object.getPrototypeOf( val ).constructor.name === "Promise" ) { console.log( "Value is a promise, and needs resolution.") var dangle = dangling.find( d=>d.d.p===val ); if( dangle ) dangle.d.n = field; this_.data[field] = val return val.then( (val)=>{ console.log( "(DOESN'T HAPPEN NOW?) THIS SHOULD BE WHAT REPLACES THE VALUE", field, val ); this_.data[field] = val }); } // a custom type might want something else... if( field === "data" ) return this.data = val; return this.data[field] = val; } // this is a sub-field of this object to revive... //console.log( "Field:", field, " is data?", val) } } function reviveContainerRef( field, val ) { //console.trace( "Revival of a container reference:", this, field, val ); if( !field ) { const existing = os.cachedContainer.get( this.d.id ); if( existing ){ // even better, don't even store the reference, return the real console.log( "So, just return with the real object to assign. (and remove fom dangling)"); const id = dangling.find( d=>d.d === this.d ); objectRefs--; if( id >= 0 ) dangling.slice( id, 1 ); return existing.data; } // finished. _debug_get && console.log( "This promise is hung, this reference has..." ); return this.d.p; // set actual field to the promise } else { // this is a sub-field of this object to revive... //console.log( "Field:", field, " is data?", val) return this[field] = val; } } let parser = this.parser; if( extraDecoders ) { // uses a specific parser for this instance... parser = JSOX.begin(reviveObject ); parser.fromJSOX( "~os", this.objectStorageContainer, reviveContainer ); // I don't know ahead of time which this is. parser.fromJSOX( "~or", objectStorageContainerRef, reviveContainerRef ); // I don't know ahead of time which this is. //console.log( "this has no decoders? ", this ); // allow extra to override default. if( this.decoders && this.decoders.length ) for( let f of this.decoders ) { parser.fromJSOX( f.tag, f.p, f.f ) }; if( extraDecoders.length ) { for( let f of extraDecoders ) { parser.fromJSOX( f.tag, f.p, f.f ); } } //console.log( "Created a parser for revival..." ); } this.decoding.push( opts ); this.currentParser = parser; //console.log( "(get)Read Key:", os ); const p = new Promise( function(res,rej) { //console.log( "doing read? (decodes json using a parser...", opts, parser, os ); const priorReadId = currentReadId; try { if( !opts.id ) { console.trace( "NO ID PASSED TO LOAD", opts ); } //console.log( "LOADING : ", opts.id ); for( var remote of os.remotes ) { _debug_get && console.log( "So for some remote... use ws.get" ); remote.remote.get( opts ).then( (obj)=>{ const p = new Promise( (res,rej)=>{ ackObjects.push( {res:res,rej:rej,cb:resultCallback,id:opts.id } ); }); currentReadId = opts.id; //_debug_get_ll && console.log( "RELOAD:", obj ); parser.write( obj ); return p } ).catch( err=>{ console.log( "remote 'get' Error:", err ); resultCallback( null ); } ); } //os.read( currentReadId = opts.id // , parser function resultCallback(obj,id){ // with a new parser, only a partial decode before revive again... //console.log( "Read resulted with an object:", obj ); let deleteId = -1; const extraResolutions = []; for( let n = 0; n < os.decoding.length; n++ ) { const decode = os.decoding[n]; if( decode === opts ) deleteId = n; else if( decode.id === opts.id ) { decode.res( obj ); } } if( deleteId >= 0 ) os.decoding.splice( deleteId, 1 ); var found; do { var found = os.pending.findIndex( pending=>{ console.log( "what is in pending?", pending ); return pending.id === key } ); if( found >= 0 ) { os.pending[found].ref.o[os.pending[found].ref.f] = obj.data; os.pending.splice( found, 1 ); } } while( found >= 0 ); if( obj && ( obj instanceof os.objectStorageContainer ) ){ //console.log( "Is a container...", currentReadId, obj.id, id ); //console.log( "GOTzz:", obj, obj.id, obj.data ); if( !("id" in obj )) Object.defineProperty( obj, "id", { value:id } ); else if( "undefined" === typeof obj.id ) throw new Error( "Object has no identity" );//Object.defineProperty( obj, "id", { value:id } ); //console.log( "Setting stored object:", obj.data , id, obj.id ); os.stored.set( obj.data, obj.id ); os.cachedContainer.set( obj.id, obj ); //currentReadId = priorReadId; for( let res in extraResolutions ) res.res(obj.data); _debug_get_ll && console.log( "calling resolve.1" ); res(obj.data); } else { //console.log( "Isn't a container..." ); //console.log( "Result object", obj ); currentReadId = priorReadId; for( let res in extraResolutions ) res.res(obj); _debug_get_ll && console.log( "calling resolve." ); res(obj) } } }catch(err) { currentReadId = priorReadId; rej(err); } } ); return p; } function fileEntry( d ) { this.name = null; this.id = null; // object identifier of this. this.contents = null; this.created = new Date(); this.updated = new Date(); if( d ) Object.defineProperty( this, "folder", {value:d} ); } fileEntry.prototype.getLength = function() { return this.data.length; } var failures = 0; fileEntry.prototype.read = function( from, len ) { const this_ = this; //console.trace( "READ RESULTING A PROMISE... PLEASE CATCH"); return new Promise( (res,rej)=>{ //console.log( "reading:", this ); if( this_.isFolder ) { this_.folder.volume.get( { id:this_.id } ) .catch( rej ) .then( (dir)=>{ //console.log( "Read directory, set folder property..." ); for( var file of dir.files ) Object.defineProperty( file, "folder", {value:dir} ); res(dir) } ); } else { //console.log( "folder volume:", this_.folder.volume ); //debugger; if( this_.id ) return this_.folder.volume.get( {id:this_.id} ).then( res ).catch( rej ); //console.log( "Rejecting, no ID, (no data)", this_ ); res( undefined ) // no data } } ); } fileEntry.prototype.write = function( o ) { if( this.id ) try { let first = null; if( "string" === typeof o ) { //console.log( "direct write of string data:", o ); for( var remote of this.folder.volume.remotes ) { let newsend = remote.remote.put( o, this.id, res, rej ); if( !first ) first = newsend; res = null; rej = null; } //this.folder.volume.writeRaw( this.id, o ); //return Promise.resolve( this.id ); return first; } else if( o instanceof ArrayBuffer || Object.getPrototypeOf(o).constructor.name === "ArrayBuffer" ) { //console.log( "Write raw buffer" ); for( var remote of this.folder.volume.remotes ) { let newsend = remote.remote.put( o, this.id,res, rej ); if( !first ) first = newsend; res = null; rej = null; } //this.folder.volume.writeRaw( this.id, o ); //return Promise.resolve( this.id ); return first; } else if( "object" === typeof o ) ;//console.log( "expected encode of object:", o ); } catch(err) { console.log( "Did the above do something?" ) } /* if( !("number"===typeof(len))) { if( "number"===typeof(at) ) { len = at; at = 0; } else { len = undefined; at = undefined; } } */ const this_ = this; //console.log( "Returning a promised volume put:", o, this ); if( this.id ) return this.folder.volume.put( o, this ); else return this.folder.volume.put( o, this ) .then( id=>{ //console.log( "Re-setting id?", this_, id ); this_.id=id; this_.folder.store(); return id; } ) .catch( (err)=>{ console.log( "Error doing put?", err ); }) } function fileDirectory( v, id ) { this.files = []; // cache chanes instead of flushing every time? //Object.defineProperty( this, "changed", {value:false, writable:true} ); try { if( v ) Object.defineProperty( this, "volume", {value:v} ); if( id ) Object.defineProperty( this, "id", { value:id } ); } catch(err) { console.log( "error:", err);} } fileDirectory.prototype.find = function( file ) { } fileDirectory.prototype.create = function( fileName ) { var file = this.files.find( (f)=>(f.name == fileName ) ); if( file ) { return null; } else { file = new fileEntry( this ); file.name = fileName; this.files.push(file); //this.changed = true; } } fileEntry.prototype.open = async function( fileName ) { if( this.root ) return file.root.open( fileName ); if( this.contents ) { return this.folder.volume.get( {id:file.contents } ) .then( async (dir)=>{ Object.defineProperty( file, "root", {value:dir} ); return dir; } ); } return this.folder.open( file.name ); } fileDirectory.prototype.open = async function( fileName ) { var file = this.files.find( (f)=>(f.name == fileName ) ); const _this = this; if( !file ) { file = new fileEntry( this ); file.name = fileName; this.files.push(file); this.store(); } return file; } function splitPath( path ) { return path.split( /[\\\/]/ ); } fileDirectory.prototype.store = function() { return this.volume.put( this, { id:this.id } ); } fileDirectory.prototype.has = async function( fileName ) { var parts = splitPath( fileName ); var part; var pathIndex = 0; var dir = this; async function getOnePath() { if( pathIndex >= path.length ) return true; if( !dir ) return false; part = path[pathIndex++]; var file = dir.files.find( (f)=>( f.name == part ) ); if( !file ) return false; if( file.root ) dir = file.root; else { if( file.contents ) { return dir.volume.get( {id:file.contents } ) .then( async (readdir)=>{ Object.defineProperty( file, "root", {value:readdir} ); dir = readdir; return getOnePath(); }); } else dir = null; } return getOnePath(); } return getOnePath(); } fileDirectory.prototype.folder = function( fileName ) { const _this = this; return new Promise( (res,rej)=>{ var path = splitPath( fileName ); var pathIndex = 0; var here = _this; function getOnePath() { let part = path[pathIndex++]; var file = here.files.find( (f)=>(f.name == part ) ); if( file ) { if( file.contents ) { _this.volume.get( {id:file.contents } ) .then( (dir)=>{ if( pathIndex < path.length ) { here = dir; return getOnePath(); } else res( dir ); } ); } else return Promise.reject( "Folder not found" ); }else { file = new fileEntry( this ); file.name = part; var newDir = new fileDirectory( _this.volume, file.contents ); Object.defineProperty( file, "root", {value:newDir} ); _this.files.push(file); newDir.store().then( (id)=>{ file.contents = id; _this.store().then( ()=>{ if( pathIndex < path.length ) { here = file.root; return getOnePath(); } res( file.root ); } ); } ); } } getOnePath(); } ); } var loading = null; ObjectStorage.prototype.getRoot = async function() { if( this.root ) return this.root; if( loading ) { //console.log( "Still loading?" ); return new Promise( (res,rej)=>{ loading.push( {res:res, rej:rej} ); } ); } this.addEncoders( [ { tag: "d", p:fileDirectory, f:null}, { tag: "f", p:fileEntry, f:null} ] ); this.addDecoders( [ {tag: "d", p:fileDirectory }, {tag: "f", p:fileEntry } ] ) var result = new fileDirectory( this, "?" ); var this_ = this; loading = []; //console.trace( "Who gets this promise for getting root?"); return new Promise( (resolve,reject)=>{ //console.log( "Getting root directory..", this_.ws, this_.ws.get ); return this_.get( { id:result.id } ) .catch( reject ) .then( (dir)=>{ //console.log( "get root directory got:", dir, "(WILL DEFINE FOLDER)" ); if( !dir ) { result.store() .then( function(id){ //console.log( "1) Assigning ID to directory", id ); Object.defineProperty( result, "id", { value:id } ); finishLoad( result ); } ) .catch( reject ); } // foreach file, set file.folder else{ for( var file of dir.files ) Object.defineProperty( file, "folder", {value:dir} ); Object.defineProperty( dir, "id", { value:result.id } ); finishLoad(dir); } function finishLoad(dir) { //console.log( "2) Assigning ID to directory(sub)", result.id ); Object.defineProperty( dir, "volume", {value:this_} ); this_.root = dir; //console.log( "Don't resolve with this yet?" ); resolve(dir) // first come, first resolved // notify anyone else that was asking for this... if( loading && loading.length ) for( var l of loading ) l.res(dir); loading = null; // dont' need this anymore. } } ); } ); } function handleMessage( msg_ ) { const msg = JSOX.parse( msg_ ); const storage = this; if( msg.op === "connected" ) { }else if( msg.op === "got" ) { }else if( msg.op === "pull" ) { } } ObjectStorage.connect = function(remote) { return new ObjectStorage( remote ) }