Remoto - VFS: objectRegistry.js Source File
Remoto - VFS
objectRegistry.js
Go to the documentation of this file.
1 
2 
3 //class for registering items that will receive messages from the server
4 
5 define( [
6  'remoto!stdlib:js/include/utils.js',
7  'include/modal/modal',
8  ],
9  function(utils,modal)
10  {
11  'use strict';
12 
28  var _objectRegistry;
29 
30  function objectRegistry()
31  {
32  this._objects = {};
33  this._reads = {};
34  this._metadatas = {};
35  //this._noresubscribe = {};
36  }
37 
38  objectRegistry.prototype = {
39 
48  reset: function()
49  {
50  for (var i in this._objects)
51  for (var j=0;j<this._objects[i].length;j++)
52  if (this._objects[i][j].detach)
53  this._objects[i][j].detach();
54 
55  this._objects = {};
56  this._reads = {};
57  this._metadatas = {};
58  //this._noresubscribe = {};
59 
60  _definitions = {};
61  _loaders = {};
62  },
63 
72  printRegistry: function()
73  {
74  console.log( "objects", this._objects );
75  console.log( "definitions", _definitions );
76  console.log( "loaders", _loaders );
77  console.log( "reads", this._reads );
78  console.log( "metadata", this._metadatas );
79  },
80 
94  registerObject: function(id,o,nosubscribe)//,noresubscribe)
95  {
96  //console.log(arguments);
97 
98  var forceSubscribe = false;
99 
100  id = utils.cleanPath(id);
101 
102  if (!this._objects[id])
103  { this._objects[id] = [];
104  // forceSubscribe = true;
105  }
106 
107  if (!this._objects[id].includes(o))
108  this._objects[id].push(o);
109 
110  // console.log(this._objects[id]);
111 
112  // if (noresubscribe) //should be manually added by a class when needed
113  // this._noresubscribe[id] = true;
114 
115  if (!nosubscribe || forceSubscribe)
116  if (this.pathAddedCallback)
117  this.pathAddedCallback([id]);
118  },
119 
133  unregisterObject: function(id,o,silent,nounsubscribe,now)
134  {
135  var forceUnsubscribe = false;
136 
137  id = utils.cleanPath(id);
138 
139  if (!this._objects[id])
140  { //console.dir(this._objects);
141  if (!silent)
142  console.warn("Registered object with id \""+id+"\" does not exist.",o);
143  return;
144  }
145 
146  var i = this._objects[id].indexOf(o);
147 
148  if (i>-1)
149  {
150  var b = this._objects[id].splice(i,1);
151  //b.destroy();
152  }
153  // else
154  // throw "Registered matching object with id="+id+" was not found.";
155 
156  if (this._objects[id].length === 0)
157  { delete this._objects[id];
158  //delete this._noresubscribe[id]; //would have been manually added by a class when registered
159  // forceUnsubscribe = true;
160 
161  // console.log(this._objects[id]);
162 
163  }//?
164 
165  if (!nounsubscribe || forceUnsubscribe)
166  if (this.pathRemovedCallback)
167  {
168  this.pathRemovedCallback([id],now);
169  //console.trace("path removed: "+id);
170  }
171  },
172 
187  readPath: function(id,o)
188  {
189  var requested = true;
190 
191  id = utils.cleanPath(id);
192 
193  if (!this._reads[id])
194  { this._reads[id] = [];
195  requested = false;
196  }
197 
198  this._reads[id].push(o);
199 
200  if (!requested)
201  if (this.pathReadCallback)
202  this.pathReadCallback([id]);
203  },
204 
222  {
223  var requested = true;
224 
225  id = utils.cleanPath(id);
226 
227  if (!this._reads[id])
228  { this._reads[id] = [];
229  requested = false;
230  }
231 
232  this._reads[id].push(o);
233 
234  if (!requested)
235  if (this.pathReadSingleCallback)
236  this.pathReadSingleCallback(id,metadata);
237  },
238 
239 
254  metadataPath: function(id,o)
255  {
256  var requested = true;
257 
258  id = utils.cleanPath(id);
259 
260  if (!this._metadatas[id])
261  { this._metadatas[id] = [];
262  requested = false;
263  }
264 
265  this._metadatas[id].push(o);
266 
267  if (!requested)
268  if (this.pathMetadataCallback)
269  this.pathMetadataCallback([id]);
270  },
271 
283  isRegistered: function(id,object)
284  {
285  if (this._objects[id])
286  if (this._objects[id].indexOf(object) > -1)
287  return true;
288 
289  return false;
290 
291  //return (this._objects[id] ? true : false);
292  },
293 
294  /*
295  fetchObject: function(id,firstOnly)
296  {
297  if (this._objects[id] && this._objects[id].length)
298  return firstOnly ? this._objects[id][0] : this._objects[id];
299  else
300  return firstOnly ? null : [];
301  },
302  */
303 
304  pathAddedCallback: null,
305  pathRemovedCallback: null,
306  pathReadCallback: null,
307  pathMetadataCallback: null,
308 
318  pathList: function()
319  {
320  //return Object.keys(this._objects).concat( Object.keys(this._reads ) );
321 
322  //https://medium.com/@alvaro.saburido/set-theory-for-arrays-in-es6-eb2f20a61848
323  var l = Object.keys(this._objects);
324  // var nr = Object.keys(this._noresubscribe);
325  // let d = l.filter(x => !nr.includes(x));
326  // return d;
327  return l;
328  },
329 
330  // isSubscribedTo: function(path)
331  // {
332  // return this._objects.hasOwnProperty(path);
333  // },
334 
348  applyDiff: function(id,diff,user,except)
349  {
350  id = utils.cleanPath(id);
351 
352  //console.log(data);
353  //console.log(this._objects);
354 
355  var o = this._objects[id];
356  o = o ? o.slice(0) : null; //copy the array, because the diff may change it
357 
358  // try
359  {
360  if (o)
361  { //for (var i=0;i<o.length;i++)
362  // if("startDiff" in o[i])
363  // if (o[i] !== except)
364  // o[i].startDiff();
365 
366  for (var i=0;i<o.length;i++)
367  if ("applyDiff" in o[i])
368  {
369  //console.log(o[i]);
370  if (o[i] !== except)
371  {
372  if (diff !== null && Object.keys(diff).length == 1 && ("mounted" in diff))
373  {
374  if ("mounted" in o[i])
375  o[i].mounted(diff.mounted)
376  }
377  else
378  // try
379  {
380  o[i].applyDiff(diff,user);
381  }
382  // catch(err)
383  // {
384  // console.error("objectRegistry applyDiff exception (1):"+err);
385  // console.log(o[i]);
386  // }
387  }
388  // else
389  // console.log("skipped except in objectRegistry : "+o.length);
390  }
391  else
392  { console.error("Object in registry doesn't have an applyDiff function!",id,o[i]);
393  console.trace();
394  }
395 
396  //for (var i=0;i<o.length;i++)
397  // if ("stopDiff" in o[i])
398  // if (o[i] !== except)
399  // o[i].stopDiff();
400 
401  return true;
402  }
403  else
404  {
405  //console.trace("Non-registered path: "+id,arguments);
406  //console.log(arguments);
407  //console.log(this._objects);
408  }
409  }
410  // catch(err)
411  // {
412  // console.error("objectRegistry applyDiff exception (2):"+err);
413  // }
414 
415  return false;
416  },
417 
430  applySubscription: function(id,data,metadata)
431  {
432  //console.log(id);
433  id = utils.cleanPath(id);
434  var o1 = this._objects[id];
435  var o = o1 ? o1.slice(0) : null; //copy the array, because the subscription may change it
436 
437  var context = metadata.context = metadata.context || {};
438  context.sourcefile = utils.fileNameFromPath( id, true );
439  context.sourcepath = id;
440  context.sourcedir = utils.dirNameFromPath( id );
441 
442  if (o)
443  {
444  for (var i=0;i<o.length;i++)
445  {
446  if ("applySubscription" in o[i])
447  {
448  //if (!o[i]._subscribed)
449  o[i].applySubscription(data,metadata);
450  //else
451  // console.error("Object at "+id+"/"+i+" was already subscribed.");
452  }
453  else if ("applyDiff" in o[i])
454  o[i].applyDiff(data,null);
455  else
456  { console.error("Object in registry doesn't have an applySubscription or applyDiff function!");
457  console.trace();
458  }
459  }
460 
461  return true;
462  }
463  else
464  {
465  console.error("Non-registered path: "+id,arguments);
466  //console.log(arguments);
467  //console.log(this._objects);
468  }
469 
470  return false;
471  },
472 
485  applyRead: function(id,data,metadata)
486  {
487  //console.log("applyRead",arguments);
488 
489  id = utils.cleanPath(id);
490  var o = this._reads[id];
491  delete this._reads[id];
492 
493  if (o)
494  {
495  for (var i=0;i<o.length;i++)
496  {
497  if ("applyRead" in o[i])
498  {
499  o[i].applyRead(id,data,metadata);
500  }
501  else
502  { console.error("Object in registry doesn't have an applyRead function!");
503  console.trace();
504  }
505  }
506 
507  return true;
508  }
509  else
510  {
511  console.error("Non-registered path: "+id,arguments);
512  //console.log(arguments);
513  //console.log(this._objects);
514  }
515 
516  return false;
517  },
518 
530  applyMetadata: function(id,metadata)
531  {
532  //console.trace("apply metadata",arguments);
533 
534  id = utils.cleanPath(id);
535  var o = this._metadatas[id];
536  delete this._metadatas[id];
537 
538  if (o)
539  {
540  for (var i=0;i<o.length;i++)
541  {
542  if ("applyMetadata" in o[i])
543  {
544  o[i].applyMetadata(id,metadata);
545  }
546  else
547  { console.error("Object in registry doesn't have an applyMetadata function!");
548  console.trace();
549  }
550  }
551 
552  return true;
553  }
554  else
555  {
556  console.error("Non-registered metadata path: "+id,arguments,o);
557  //console.log(arguments);
558  //console.log(this._objects);
559  }
560 
561  console.trace("returning false");
562  return false;
563  },
564 
576  applyRequestLock: function(id,data)
577  {
578  id = utils.cleanPath(id);
579  var o = this._objects[id];
580 
581  if (o)
582  {
583  for (var i=0;i<o.length;i++)
584  {
585  if ("applyRequestLock" in o[i])
586  {
587  o[i].applyRequestLock(data);
588  }
589  else
590  { console.error("Object in registry doesn't have an applyRequestLock function!");
591  console.trace();
592  }
593  }
594 
595  return true;
596  }
597  else
598  {
599  console.error("Non-registered path: "+id,arguments);
600  //console.log(arguments);
601  //console.log(this._objects);
602  }
603 
604  return false;
605  },
606 
618  applyReleaseLock: function(id,data)
619  {
620  id = utils.cleanPath(id);
621  var o = this._objects[id];
622 
623  if (o)
624  {
625  for (var i=0;i<o.length;i++)
626  {
627  if ("applyReleaseLock" in o[i])
628  {
629  o[i].applyReleaseLock(data);
630  }
631  else
632  { console.error("Object in registry doesn't have an applyReleaseLock function!");
633  console.trace();
634  }
635  }
636 
637  return true;
638  }
639  else
640  {
641  console.error("Non-registered path: "+id,arguments);
642  //console.log(arguments);
643  //console.log(this._objects);
644  }
645 
646  return false;
647  },
648 
661  applyRequestError: function(command,id,reason)
662  {
663  //console.warn("objectregistry applyRequestError",arguments,this._objects);
664 
665  id = utils.cleanPath(id);
666  var o;
667  var ok = false;
668 
669  switch (command)
670  {
671  case "subscribe":
672  case "unsubscribe": o = this._objects[id]; break;
673 
674  case "read": o = this._reads[id]; break;
675 
676  case "metadata": o = this._metadatas[id]; break;
677 
678  default: console.error("unsupported applyRequestError command: "+command);
679  break;
680  };
681 
682  if (o)
683  {
684  for (var i=0;i<o.length;i++)
685  {
686  if ("applyRequestError" in o[i])
687  {
688  o[i].applyRequestError(command,id,reason);
689  }
690  else
691  { console.error("Object '"+id+"' in registry doesn't have an applyRequestError function!",o[i]);
692  //console.log(o[i]);
693  //console.trace();
694  var commandUP = command.charAt(0).toUpperCase() + command.slice(1);
695  modal.alert( reason, null, commandUP+" Error" );
696  }
697  }
698 
699  ok = true;
700  }
701 
702  //clean up the bad things
703  switch (command)
704  {
705  case "subscribe":
706  case "unsubscribe": delete this._objects[id]; break;
707 
708  case "read": delete this._reads[id]; break;
709 
710  case "metadata": delete this._metadatas[id]; break;
711  };
712 
713  return ok;
714  },
715 
729  applyRequestSuccess: function(command,id,data,metadata)
730  {
731  id = utils.cleanPath(id);
732  var o = this._objects[id];
733 
734  if (o)
735  {
736  for (var i=0;i<o.length;i++)
737  {
738  if ("applyRequestSuccess" in o[i])
739  {
740  o[i].applyRequestSuccess(command,id,data,metadata);
741  }
742  else
743  { console.error("Object '"+id+"' in registry doesn't have an applyRequestSuccess function!");
744  console.log(o[i]);
745  modal.alert( "no applyRequestSuccess function!", null, command+" success error" );
746  }
747  }
748 
749  return true;
750  }
751  else
752  {
753  if (command != "rm")
754  {
755  console.error("applyRequestSuccess Non-registered path: "+id,arguments);
756  console.log(this._objects);
757  }
758  else
759  return true;
760  }
761 
762  return false;
763  },
764 
776  find: function(id,type)
777  {
778  var o = this._objects[id];
779  //console.log(o);
780 
781  if (o)
782  {
783  if (!type) //don't care about type
784  return o[0];
785 
786  for (var i=0;i<o.length;i++)
787  {
788  if (o[i] instanceof type) //generally return a pane
789  return o[i];
790 
791  if (o[i]._object instanceof type) //return a more complex object
792  return o[i]._object;
793  }
794  }
795 
796  return null;
797  },
798  }
799 
820  function extendDefinition( extender, base, strict )
821  {
822  base = base || {};
823 
824  if (typeof base == "string")
825  return extender;
826 
827  //if (typeof extender == 'object' && extender !== null)
828  if (!utils.isArray(extender) && typeof extender == 'object' && extender !== null)
829  {
830  if (strict) //FIXME: this is a sloppy workaround
831  {
832  //if (base !== null && extender !== null)
833  if (('value' in base) && !('value' in extender))
834  {
835  //console.log(extender,base);
836  //base['value'] = extender;
837  base['value'] = extendDefinition( extender, base['value'] );
838  return base;
839  }
840  }
841 
842  for (var a in extender)
843  {
844  if (strict) //strict mode will not allow the extender to add fields to the base
845  { if (!(a in base))
846  { //console.log("skipping "+a+" because it's not in the schema.");
847  continue;
848  }
849 
850  //console.log(extender,base);
851  }
852 
853  if (typeof base[a] == 'object')
854  base[a] = extendDefinition( extender[a], base[a], strict );
855  //base[a] = extendDefinition( extender[a], base[a] ); //FIXME: only use strict on the base extension?
856  else
857  base[a] = extender[a];
858  }
859  }
860  else
861  { if ('value' in base)
862  base["value"] = extender; //case of a value being promoted to the value field of an aggregate object
863  else
864  return extender; //or the case of a new value being introduced
865  }
866 
867  return base;
868  }
869 
870  var _definitions = {}; //these are global to the objectRegistry
871  var _loaders = {};
872 
890  function objectLoader(basePath,strict)
891  {
892  basePath = basePath || "";
893 
894  this._basePath = utils.cleanPath( basePath );
895  this._strict = !!strict;
896 
897  //this._definitions = {};
898  //this._loaders = {};
899  }
900 
901  objectLoader.prototype = {
902 
911  destroy: function()
912  {
913  //for (var p in this._loaders)
914  // this._loaders[p].destroy();
915 
916  //this._definitions = null;
917  //this._loaders = null;
918  },
919 
933  fetchDefinition: function(path, data, callback)
934  {
935  if (!path)
936  { if (callback)
937  return callback( this.applyDefinition(path,data) );
938  else
939  return;
940  }
941 
942  path = this._basePath + "/" + path;
944  data = data || {};
945 
946  if (!(path in _definitions))
947  this.loadDefinition( path, data, callback );
948  else
949  { //console.error("already loaded "+path);
950  if (callback)
951  return callback( this.applyDefinition(path,data) );
952  }
953  },
954 
967  loadDefinition: function(path, data, callback)
968  {
970 
971  //if (this._loaders[path])
972  if (!(path in _loaders))
973  _loaders[path] = new objectLoaderLoader( this, path, data, callback );
974  else
975  _loaders[path].enqueue( data, callback );
976  },
977 
988  loadedDefinition: function(path, data)
989  {
991 
992  //console.log("Loaded definition: "+path,data);
993  _definitions[ path ] = data;
994  },
995 
1007  applyDefinition: function( path, data )
1008  {
1009  path = utils.cleanPath(path);
1010 
1011  var base = utils.copyObject( _definitions[path] );
1012  return extendDefinition( data, base, this._strict );
1013  },
1014 
1026  applyValues: function( values, definition )
1027  {
1028  var t,b;
1029 
1030  if (definition === null)
1031  return values;
1032 
1033  //if (typeof values === 'object')
1034  if (!utils.isArray(values) && typeof values == 'object')
1035  { for (var a in values)
1036  {
1037  var v = values[a];
1038  var aa = a.split('.');
1039 
1040  if (aa.length > 1)
1041  {
1042  t = v;
1043  a = aa.shift();
1044  b = aa.join('.');
1045  v = {};
1046  v[b] = t;
1047  }
1048 
1049  if (!(a in definition) && ('value' in definition) && (typeof definition['value'] == 'object'))
1050  {
1051  if (a.split('.',2)[0] in definition['value'])
1052  v = values;
1054  definition['value'] = this.applyValues( v, definition['value'] );
1055  }
1056  else if (typeof definition[a] == 'object')
1057  definition[a] = this.applyValues( v, definition[a] );
1058  else
1059  definition[a] = v;
1060  }
1061  }
1062  else
1063  { if ('value' in definition)
1064  definition['value'] = values;
1065  else
1066  return values;
1067  }
1068 
1069  return definition;
1070  },
1071  }
1072 
1092  function objectLoaderLoader(factory, path, data, callback)
1093  {
1094  path = utils.cleanPath(path);
1095 
1096  this._factory = factory;
1097  this._path = path;
1098  this._callbacks = [ { data:data, callback:callback } ];
1099  this._subscribed = false;
1100 
1101  //console.error(path);
1102  //_objectRegistry.registerObject( path, this );
1103  //console.log(this._path);
1104 
1105  _objectRegistry.readPath( path, this );
1106 
1107  this._timeout = setTimeout( this.timeout.bind(this), 60000 ); //timeout in one minute
1108  }
1109 
1110  objectLoaderLoader.prototype = {
1111 
1120  destroy: function()
1121  {
1122  //console.log("destroying objectLoaderLoader: "+this._path);
1123 
1124  //_objectRegistry.unregisterObject( this._path, this );
1125 
1126  if (this._timeout)
1127  { clearTimeout( this._timeout );
1128  this._timeout = null;
1129  }
1130 
1131 
1132  delete _loaders[this._path];
1133 
1134  this._factory = null;
1135  this._callbacks = [];
1136  },
1137 
1146  timeout: function()
1147  {
1148  console.warn( "Timeout on "+this._path );
1149  console.log(this);
1150  },
1151 
1164  applyRead: function(path,data,metadata)
1165  {
1166  return this.applySubscription(data,metadata);
1167  },
1168 
1179  applySubscription: function(data,metadata)
1180  {
1181  if (this._subscribed)
1182  { console.warn("already subscribed: "+this._path)
1183  return;
1184  }
1185 
1186  this._subscribed = true;
1187 
1188  if (!this._callbacks.length)
1189  { console.error("objectLoaderLoader has no callbacks... don't know what to do with the subscription!");
1190  return;
1191  }
1192 
1193  var base = data.base;
1194 
1195  if (base)
1196  this._factory.fetchDefinition( base, data, this.loaded.bind(this) );
1197  else
1198  this.loaded(data);
1199  },
1200 
1211  enqueue: function(data,callback)
1212  {
1213  this._callbacks.push( { data:data, callback:callback } );
1214  },
1215 
1225  loaded: function(data)
1226  {
1227  this._factory.loadedDefinition( this._path, data );
1228 
1229  // this.unqueueCallback(); //use this to let the interface breathe during a long load.
1230  // return;
1231 
1232  for (var i=0;i<this._callbacks.length;i++)
1233  { var c = this._callbacks[i];
1234  if (c.callback)
1235  c.callback( this._factory.applyDefinition( this._path, c.data) );
1236  }
1237 
1238  this.destroy();
1240  /*
1241  var THIS = this;
1242  setTimeout( function() {
1243  console.log(_definitions);
1244  console.log(_loaders);
1245  }, 100 );
1246  */
1247  },
1248 
1257  unqueueCallback: function()
1258  {
1259  if (this._callbacks.length)
1260  {
1261  var c = this._callbacks.shift();
1262  if (c.callback)
1263  c.callback( this._factory.applyDefinition( this._path, c.data ) );
1264  }
1265 
1266  if (this._callbacks.length)
1267  //setTimeout( this.unqueueCallback.bind(this),0 );
1268  requestAnimationFrame( this.unqueueCallback.bind(this) );
1269  else
1270  this.destroy();
1271  }
1272  }
1274  _objectRegistry = new objectRegistry();
1275  _objectRegistry.objectLoader = objectLoader;
1276  _objectRegistry.extendDefinition = extendDefinition;
1277 
1278  return _objectRegistry;
1279  }
1280 );
1281 
applySubscription(data, metadata)
getter id
returns the number of milliseconds since midnight January 1, 1970 UTC
extendDefinition(extender, base, strict)
setter user
a setter DOCME
setter type
a setter DOCME
loadDefinition(path, data, callback)
Create an objectLoaderLoader for a requested path.
applyValues(values, definition)
fetchDefinition(path, data, callback)
Read data from a VFS path, and apply the loaded contents to a JSON object as a diff.
objectLoader(basePath, strict)
loadedDefinition(path, data)
applyDefinition(path, data)
objectLoaderLoader(factory, path, data, callback)
enqueue(data, callback)
isRegistered(id, object)
metadataPath(id, o)
Request metadata for a given path.
find(id, type)
Find a registered object, optionally by type.
applyRequestError(command, id, reason)
readPath(id, o)
Request a read on a given path.
applyMetadata(id, metadata)
readSinglePath(id, o, metadata)
Request a read on a given path, and send metadata.
printRegistry()
Print the contents of the registry to the console.
applyReleaseLock(id, data)
applyRead(id, data, metadata)
registerObject(id, o, nosubscribe)
Register an object in the registry, and call the pathAddedCallback.
applyRequestLock(id, data)
unregisterObject(id, o, silent, nounsubscribe, now)
Unregister an object from the registry.
detach()
Detach the tab's children.
pathAddedCallback()
DOCME.
pathRemovedCallback()
DOCME.
getter path
a getter DOCME
applyRequestSuccess()
If an adminstrator has deleted his own preferences, this function will be called on success.
applyDiff(diff, user)
Utility functions for javascript clients.
cleanPath(path)
Clean and normalize a resource path.
fileNameFromPath(p, orDirIfNeeded)
Retrieve the last part of a file path.
copyObject(source, dest)
Create a deep copy of a JSON object, extending the destination object recursively.
isArray(a)
Determine if an object is a javascript array.
dirNameFromPath(p)
Retrieve the directory portion of a path.
metadata(paths)