UNPKG

@d3x0r/sack-gui

Version:

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

1,224 lines (1,089 loc) 36.2 kB
export default function(sack) { const _debug = false; const _debug_dangling = false; const _debug_output = _debug || false; const _debug_object_convert = _debug || false; // save original object. const _objectStorage = sack.ObjectStorage; const nativeVol = sack.Volume(); const remoteExtensionsSrc = nativeVol.read( __dirname+"/object-storage-remote.js" ); const remoteExtensions = remoteExtensionsSrc?remoteExtensionsSrc.toString():"// No COde Found"; const jsonRemoteExtensions = JSON.stringify( remoteExtensions ); var allDangling = new Map(); var dangling = []; var objectRefs = 0; var currentContainer = null; var preloadStorage = null; // storage thrown from main thread // manufacture a JS interface to _objectStorage. sack.ObjectStorageSync = ObjectStorageSync; function ObjectStorageSync(...args) { if( !( this instanceof sack.ObjectStorageSync ) ) { return new sack.ObjectStorageSync(...args); } const realStorage = this.realStorage = preloadStorage || new _objectStorage(...args); const newStorage = this; this.name = args[0]; //console.log( "Should be a new private thing:" ); // associates object data with storage data for later put(obj) to re-use the same informations. function objectStorageContainerSync(o,opts) { if( !this instanceof objectStorageContainerSync ) return new newStorage.objectStorageContainer(o,opts); _debug_object_convert && 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; } } //this.def = { // indexes : [], // dirty : false, //} 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( sack.JSOX.stringify(o), 3, 3 ):null:null, data : o } */ if( opts && opts.sign ) { var v = sack.SaltyRNG.verify( sack.JSOX.stringify(o), this.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 ) { Object.defineProperty( this, "id", { value:opts.id } ); } } }catch(err) { console.log( "Uncaught exception:", err ); } //console.log( "Container:", this ); } objectStorageContainerSync.prototype.getStore = function() { return newStorage; } objectStorageContainerSync.getStore = function() { return newStorage; } objectStorageContainerSync.prototype.map = function( opts ) { const dangling = this.dangling; /* this is a property set dynamically */ if( dangling && dangling.length ) { opts = opts || { depth:0, paths:[] }; const rootMap = this; let waiting = 0; console.trace( "Map Object called... ", dangling) 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 dangling ){ 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 = dangling.findIndex( p=>p===h ); if( idx >= 0 ) dangling.splice( idx, 1 ); else console.log( "resolved load was not found???" ); } } } else{ // load everything that's pending on this object. for( let load of dangling ) { { console.log( "Checking pending..." ); const existing = newStorage.cachedContainer.get( load.d.id ); if( existing ) { console.log( "Found it as existing, resolve it?", existing.data, load.d ); continue; /* if( load.d.res ) load.d.res( existing.data ); else { //return load.d.p; load.d.p.then( o2=>{ console.trace( "Promise was already resolved, add some more then?", o2, existing ); if( existing.data!==o2) throw new Error( "resolved and loaded object mismatch"); return o2 }) return load.d.p; } */ } } //console.log( "something:", load ); loadPending(load); } // wait until the promise resolves to delete this //dangling.length = 0; } if( !waiting ){ console.log( "Nothing scheduled to really wait, go ahead and resolve"); return rootMap.data; } function loadPending(load) { waiting++; newStorage.get( {id:load.d.id}).then( (obj)=>{ //console.log( "Storage requested:", load.d.id ); 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 : ", objc, rootMap.data ); res( rootMap.data ); } }); } else { waiting--; if( !waiting ) { //console.log( "2map is resolving with : ", rootMap.data ); res( rootMap.data ); } } }) } } _debug_dangling && console.log( "Nothing dangling found on object"); return this.data; // this function is async, just return. } objectStorageContainerSync.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; const indexList = this.def.indexes; 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 index = { data : end, name : fieldName, opts : opts, }; const storageIndex = storage.createIndex( this.id, index ); indexList.push( index ); } var mapping = false; preloadStorage = null; newStorage.objectStorageContainer = objectStorageContainerSync; 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 = sack.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 = []; function objectToJSOX( stringifier ){ if( this instanceof Promise ) { console.log( "This is still a pending object reference(?)", this ); } // see if we alread stored this... (or are currently storing this.) (back references container) _debug_object_convert && console.trace( "THIS GOT CALLED?", this, Object.getPrototypeOf( this ) ); var exist = newStorage.stored.get( this ); if( exist ) { var obj = newStorage.cachedContainer.get( exist ); _debug_object_convert && 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+'"'; } } return this; } function storageObjectToJSOX( stringifier ){ // see if we alread stored this... (or are currently storing this.) (back references container) _debug_object_convert && console.trace( "THIS GOT CALLED?", this, Object.getPrototypeOf( this ) ); var exist = newStorage.stored.get( this ); if( exist ) { var obj = newStorage.cachedContainer.get( exist ); _debug_object_convert && 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 objectStorageContainerSync ) { //console.log( "THIS SHOULD ALREADY BE IN THE STORAGE!", this, newStorage.stored.get( this.data ) ); // this is probably the final result when encoding this object. //newStorage.stored.set( this.data, this.id ); // maybe update the ID though. return {data:this.data}; // this object will be stringified, and have our type prefix prepended. } } //console.log( "not a container..."); return this; } function setupStringifier( stringifier ) { stringifier.setDefaultObjectToJSOX( objectToJSOX ); stringifier.registerToJSOX( "~os", objectStorageContainerSync, storageObjectToJSOX ); 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; //console.log( "External API invoked map..."); const rootId = newStorage.stored.get( o ); if( !rootId ) { console.log( "Object was not stored, cannot map" ); return o; } return newStorage.cachedContainer.get( rootId ).map( opts ); } newStorage.handleMessage = handleMessage return newStorage; function handleMessage( ws, msg ) { console.log( "Storage Remote Message:", msg ); if( msg.op === "connect" ) { ws.send( `{op:connected,code:${jsonRemoteExtensions}}` ); return true; } if( msg.op === "get" ) { realStorage.readRaw( currentReadId = msg.opts.id , (data)=>{ //console.log( "Read ID:", msg.opts.id, data ); ws.send( newStorage.stringifier.stringify( { op:"GET", id:msg.id, data:data } ) ); } ) return true; } if( msg.op === "put" ) { // want to get back a file ID if possible... // and/or use the data for encoding/salting/etc... which can determine the result ID. //console.log( "PUT THIGN:", msg ); realStorage.writeRaw( msg.rid, msg.data); ws.send( { op:"PUT", id:msg.id, r:msg.rid } ); return true; } return false; } } ObjectStorageSync.getRemoteFragment = function() { return remoteExtensions; } ObjectStorageSync.Thread = { post: _objectStorage.Thread.post, accept(cb) { _objectStorage.Thread.accept((a,b)=>{ // posted from core thread ( worker can't access disk itself? ) preloadStorage = b; cb(a, new sack.ObjectStorageSync(b) ); }); } } // define a class... to be handled by stringification ObjectStorageSync.prototype.defineClasss = function(a,b) { this.stringifier.defineClass(a,b); } ObjectStorageSync.prototype.scan = function( from ) { var fromTime = ( from.getTime() * 256 ); //this.loadSince( fromTime ); } ObjectStorageSync.prototype.getContainer = function( obj, options ) { var container = this_.stored.get( obj ); var storage; if( container ) { container = this_.cachedContainer.get( container ); return container; } //console.log( "Getting a new container...", container.id ); container = new objectStorageContainerSync(obj,options); this_.stored.set( obj, container.id ); this_.cached.set( container.id, container.data ); this_.cachedContainer.set( container.id, container ); } ObjectStorageSync.prototype.createIndex = function( id, index ){ } ObjectStorageSync.prototype.index = function( obj, fieldName, opts ) { var this_ = this; return new Promise( function(res,rej){ var container = this_.stored.get( obj ); //console.log( "Put found object?", container, obj, options ); if( container ) { container = this_.cachedContainer.get( container ); if( container.data.nonce ) { rej( new Error( "Sealed records cannot be modified" ) ); } } else { if( !opts.id ) { console.log( "Create index, creating new container", container.id ); container = new this.objectStorageContainer(obj,options); //console.log( "saving stored container.id", obj, container.id ); //this.stored.delete( obj ); this_.stored.set( obj, container.id ); this_.cached.set( container.id, container.data ); this_.cachedContainer.set( container.id, container ); } } container.createIndex( this_, fieldName, opts ); res(); }) } ObjectStorageSync.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); } } ObjectStorageSync.prototype.addEncoders = function(encoderList) { const this_ = this; encoderList.forEach( f=>{ this_.encoders.push(f); this_.stringifier.registerToJSOX( f.tag, f.p, f.f ) ; }); } ObjectStorageSync.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 ); }); } ObjectStorageSync.prototype.getCurrentParseRef = function() { if( this.currentParser ){ return this.currentParser.getCurrentRef(); } return null; } ObjectStorageSync.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' ObjectStorageSync.prototype.put = function( obj, opts ) { const this_ = this; if( currentContainer && currentContainer.data === this ) { saveObject( null, null ); _debug && console.log( "Returning same id; queued save to background...") return currentContainer.id; }else { } { if( "string" === typeof obj && opts.id ) { console.log( "SAVING A STRING OBJECT" ); // this isn't cached on this side. // we don't know the real object. console.log( "Saving a string object" ); this.realStorage.writeRaw( opts.id, obj ); return res?res( opts.id ):null; } var container = this_.stored.get( obj ); _debug && console.log( "Put found object?", container, obj, opts ); if( container ) { container = this_.cachedContainer.get( container ); if( !container.data.nonce ) { // make sure every item that is in an index // has been written... if( this_.def ) this_.def.indexes.forEach( index=>{ index.data.forEach( (item)=>{ this_.put( item ); }); }) var stringifier; if( opts && opts.extraEncoders ) { stringifier = sack.JSOX.stringifier(); this_.setupStringifier( stringifier ); opts.extraEncoders.forEach( f=>{ stringifier.registerToJSOX( f.tag, f.p, f.f ) }); }else { stringifier = this_.stringifier; } container.encoding = true; storage = stringifier.stringify( container ); container.encoding = false; if( !container.id || container.id === "null" ) { console.trace( "0) Container has no ID or is nUll", container ); } // _debug_output && console.trace( "WRite:", container.id, storage ); this_.realStorage.writeRaw( container.id, storage ); return res?res( container.id ):null; } else { throw new Error( "record is signed, cannot put" ); } } if( opts && opts.id ) { var stringifier; if( opts.extraEncoders ) { stringifier = sack.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 ); if( !opts.id || opts.id === "null" ) { console.trace( "Container has no ID or is nUll", container ); } _debug_output && console.trace( "WRite:", opts, storage ); this_.realStorage.writeRaw( opts.id, storage ); return opts.id; } else if( !opts || !opts.id ) { _debug && console.log( "New bare object, create a container...", opts ); if( !opts ) opts = { id : sack.id() } else opts.id = sack.id(); if( "object" === typeof obj ) { container = new this_.objectStorageContainer(obj,opts); //console.log( "New container looks like... ", container.id, container ); //console.log( "saving stored container.id", typeof obj, obj, container.id ); //this.stored.delete( 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 ) { stringifier = sack.JSOX.stringifier(); this_.setupStringifier( stringifier ); opts.extraEncoders.forEach( f=>{ stringifier.registerToJSOX( f.tag, f.p, f.f ) }); }else { stringifier = this_.stringifier; } container.encoding = true; storage = stringifier.stringify( container ); container.encoding = false; } else { container = new this_.objectStorageContainer(obj,opts); storage = obj; } if( !container.id || container.id === "null" ) { console.trace( "Container has no ID or is nUll", container ); } _debug && console.trace( "Outut container to storage... ", container, storage ); try { this_.realStorage.writeRaw( container.id, storage ); this_.cached.set( container.id, container.data ); this_.cachedContainer.set( container.id, container ); }catch(err) { console.log( "WRITE RAW?", this_ )} //console.log( "OUTPUT:", storage ); return container.id; } } } /* ObjectStorageSync.prototype.update( objId, obj ) { var container = new this.objectStorageContainer(sack.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 ; ObjectStorageSync.prototype.get = function( opts ) { //this.parser. const os = this; if( "string" === typeof opts ) { opts = { id:opts , extraDecoders : null }; } if( !opts ){ console.trace( "Must specify options ", opts); return null; } { const priorLoad = os.cachedContainer.get( opts.id ); if( priorLoad ) return 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 = sack.JSOX.begin(); //console.log( "ADDING ~os"); this.parser.fromJSOX( "~os", this.objectStorageContainer, reviveContainer ); // I don't know ahead of time which this is. this.parser.fromJSOX( "~or", objectStorageContainerSyncRef, 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 objectStorageContainerSyncRef( s ) { _debug_dangling && console.trace( "Container ref:", s ); try { const existing = os.cachedContainer.get(s); const here = os.getCurrentParseRef(); const thisDangling = dangling; //console.log( "Conainer ref, this will resolve in-place") this.d = {id:s,p:null,res:null,rej:null,refobj:null, reffield:null}; if( !existing ) { _debug_dangling && console.log( "PUSHING DANGLING REFERNCE", this ); const requests = allDangling.get( this.d.id ) || []; if( !requests.length ) allDangling.set( this.d.id, requests ); const p = this.d.p = new Promise( (res,rej)=>{ _debug_dangling && console.log( "setting up pending promise to resolve:", s ); this.d.res = res; this.d.rej = rej; }).then( (obj)=>{ _debug_dangling && console.log( "(DOES THIS HAPPEN?)OBJ REPLACE OBJECT WITH:", here, obj, thisDangling, s ) const dr = thisDangling.findIndex( d=> d.d.id === s ); if( dr >= 0 ) thisDangling.splice( dr, 1 ); else console.log( "FAILED TO FIND DANGLING REFERENCE" ); const dp = requests.findIndex( d=> d.d.p === p ); if( dp >= 0 ) { requests.splice( dr, 1 ); if( !requests.length ) { _debug_dangling && console.log( "This object fully resolved." ); allDangling.delete( s ); } } else console.log( "FAILED TO FIND ALLDANGLING REFERENCE" ); return (here.o[here.f] = obj) }).catch( (err)=>{ console.log( "CATCH UNCAUGHT PLEASE DO", err); }); requests.push( this ); dangling.push( this ); objectRefs++; } else { _debug_dangling && console.log( "NOT DANGLING; existing object already exists... " ); this.d.p = existing.data; // this will still have to be swapped. } } catch(err) { console.log( "Init failed:", err)} } function reviveContainer( field, val ) { if( !field ) { // finished. if( objectRefs ) { /* sets dangling property on container */ _debug_dangling && console.log( "Collapse dangling", dangling ); if( !this.dangling ) Object.defineProperty( this, "dangling", { value:dangling } ); else{ this.dangling.push.apply(this.dangling, dangling ) //console.log( "Added more to the dangling things..." ); } // resets to store new objects for next load. dangling = []; objectRefs = 0; } Object.defineProperty( this, "id", { value:currentReadId } ); const request = allDangling.get( currentReadId ); //console.log( "Revive container final pass... does this resolve?", this, request, allDangling, currentReadId ); if( request ) { for( let load of request ) { load = load.d; //console.log( "Checking dangling:", load, currentReadId ) //console.log( "Resolve pending promise.", load); load.res( this.data ); } } 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 ) { //_debug_dangling && 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)=>{ _debug_dangling && 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" ) { this.data = val; return undefined; } this.data[field] = val; return undefined; } } function reviveContainerRef( field, val ) { //console.trace( "Revival of a container reference:", this, field, val ); if( !field ) { const existing = os.cachedContainer.get( this.d.id ); //console.trace( "...", existing); 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; } return this.d.p; } else { return val; } } let parser = this.parser; if( opts.extraDecoders ) { parser = sack.JSOX.begin( ); //console.log( "Adding ~os handler"); parser.fromJSOX( "~os", this.objectStorageContainerSync, reviveContainer ); // I don't know ahead of time which this is. parser.fromJSOX( "~or", objectStorageContainerSyncRef, reviveContainerRef ); // I don't know ahead of time which this is. //console.log( "this has no decoders? ", this ); if( this.decoders && this.decoders.length ) this.decoders.forEach( f=>parser.fromJSOX( f.tag, f.p, f.f ) ); // allow extra to override default. if( opts && opts.extraDecoders && opts.extraDecoders.length ) { opts.extraDecoders.forEach( f=>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 ); //console.log( "doing read? (decodes json using a parser...", opts, parser, os ); const priorReadId = currentReadId; let result = null; try { //console.log( "LOADING : ", opts.id, parser.localFromProtoTypes ); os.realStorage.read( currentReadId = opts.id , parser, (obj,times)=>{ // with a new parser, only a partial decode before revive again... _debug && console.log( "Read resulted with an object:", obj, times ); 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.objectStorageContainerSync ) ){ //console.log( "GOTzz:", obj, obj.id, obj.data ); if( !("id" in obj )) Object.defineProperty( obj, "id", { value:currentReadId } ); os.stored.set( obj.data, obj.id ); os.cachedContainer.set( obj.id, obj ); currentReadId = priorReadId; for( let res in extraResolutions ) res.res(obj.data); result = obj.data; } else { currentReadId = priorReadId; for( let res in extraResolutions ) res.res(obj); //console.log( "RESOLVE WITH OBJECT NEED DATA?", ( obj instanceof os.objectStorageContainerSync ) ); result = obj; } } ); return result; }catch(err) { currentReadId = priorReadId; throw err; //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 ) { //console.trace( "READ RESULTING A PROMISE... PLEASE CATCH"); //console.log( "reading:", this ); if( this.isFolder ) { const dir = this.folder.volume.get( { id:this_.id } ); for( var file of dir.files ) Object.defineProperty( file, "folder", {value:dir} ); return dir; } else { if( this.id ) return this.folder.volume.get( {id:this.id} ); //console.log( "Rejecting, no ID, (no data)", this_ ); } } fileEntry.prototype.write = function( o ) { if( this.id ) try { if( "string" === typeof o ) { //console.log( "direct write of string data:", o ); this.folder.volume.realStorage.writeRaw( this.id, o ); return this.id; } else if( o instanceof ArrayBuffer ) { //console.log( "Write raw buffer" ); this.folder.volume.realStorage.writeRaw( this.id, o ); return this.id; } 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 ) { this_.folder.updated = new Date(); this_.folder.store(); return this.folder.volume.put( o, this ); } else { const id = this.folder.volume.put( o, this ); this_.id = id; this_.folder.updated = new Date(); this_.folder.store(); return id; } } 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 ) { console.log( "File already exists, not creating.", fileName, this.files ); return null; // can't creeate already exists. } else { file = new fileEntry( this ); file.name = fileName; this.files.push(file); this.store(); return file; //this.changed = true; } } fileEntry.prototype.open = function( fileName ) { if( this.root ) return file.root.open( fileName ); if( this.contents ) { return this.folder.volume.get( {id:file.contents } ) .then( (dir)=>{ Object.defineProperty( file, "root", {value:dir} ); return dir; } ); } return this.folder.open( file.name ); } fileDirectory.prototype.open = 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( /[\\\/]/ ); } const pendingStore = []; let lastStore = 0; function checkPendingStore() { if( lastStore ) { const now = Date.now(); if( (now-lastStore) > 100 ){ for( let p of pendingStore ) { p.volume.put( p, { id:p.id } ); } pendingStore.length = 0; lastStore = 0; } } if( pendingStore.length ) { setTimeout( checkPendingStore, 50 ); } } fileDirectory.prototype.store = function(force) { if( !force ) { if( !pendingStore.find( p=>p===this)) pendingStore.push( this ); if( !lastStore ) { checkPendingStore(); } lastStore = Date.now(); return undefined; } return this.volume.put( this, { id:this.id } ); } fileDirectory.prototype.remove = function( fileName ) { var parts = splitPath( fileName ); var part; var pathIndex = 0; var dir = this; function getOnePath() { if( pathIndex >= parts.length ) return false; if( !dir ) return false; part = parts[pathIndex++]; const fileId = dir.files.findIndex( (f)=>( f.name == part ) ); if( fileId >= 0 && pathIndex >= parts.length ) { const file = dir.files[fileId]; dir.volume.remove( file.id ); dir.files.splice( fileId, 1 ); //if( !dir.files.length ) { // dir.volume.remove( dir.id ); //} return true; } if( file.root ) dir = file.root; else { if( file.contents ) { return dir.volume.get( {id:file.contents } ) .then( (readdir)=>{ Object.defineProperty( file, "root", {value:readdir} ); dir = readdir; return getOnePath(); }); } else dir = null; } return getOnePath(); } return getOnePath(); } fileDirectory.prototype.has = function( fileName ) { var parts = splitPath( fileName ); var part; var pathIndex = 0; var dir = this; function getOnePath() { if( pathIndex >= parts.length ) return true; if( !dir ) return false; part = parts[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( (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; 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 ) { const dir = _this.volume.get( {id:file.contents } ) if( pathIndex < path.length ) { here = dir; return getOnePath(); } else return dir; } else throw new Error( "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); const id = newDir.store(); file.contents = id; _this.store(); if( pathIndex < path.length ) { here = file.root; return getOnePath(); } return ( file.root ); } } return getOnePath(); } var loading = null; ObjectStorageSync.prototype.getRoot = function() { if( this.root ) return this.root; if( loading ) { console.log( "!!!!! SHOULDN'T have time to load?" ); return undefined; } 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?"); //console.log( "Getting root directory.." ); const dir = this_.get( { id:result.id } ) //console.log( "get root directory got:", dir, "(WILL DEFINE FOLDER)" ); if( !dir ) { const id = result.store(true) Object.defineProperty( result, "id", { value:id } ); return finishLoad( result ); } // foreach file, set file.folder else{ for( var file of dir.files ) { //console.log( "File:", dir, file ); Object.defineProperty( file, "folder", {value:dir} ); } Object.defineProperty( dir, "id", { value:result.id } ); return 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?" ); // 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. return dir; } } }