<commit_before>/**
 * indexed db adapter
 * ===
 * - originally authored by Vivian Li
 *
 */

Lawnchair.adapter('indexed-db', (function(){

  function fail(e, i) { console.error('error in indexed-db adapter!', e, i); }

  var STORE_NAME = 'lawnchair';

  // update the STORE_VERSION when the schema used by this adapter changes
  // (for example, if you change the STORE_NAME above)
  var STORE_VERSION = 2;

  var getIDB = function() {
    return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
  };
  var getIDBTransaction = function() {
      return window.IDBTransaction || window.webkitIDBTransaction ||
          window.mozIDBTransaction || window.oIDBTransaction ||
          window.msIDBTransaction;
  };
  var getIDBKeyRange = function() {
      return window.IDBKeyRange || window.webkitIDBKeyRange ||
          window.mozIDBKeyRange || window.oIDBKeyRange ||
          window.msIDBKeyRange;
  };
  var getIDBDatabaseException = function() {
      return window.IDBDatabaseException || window.webkitIDBDatabaseException ||
          window.mozIDBDatabaseException || window.oIDBDatabaseException ||
          window.msIDBDatabaseException;
  };
  var useAutoIncrement = function() {
      // using preliminary mozilla implementation which doesn't support
      // auto-generated keys.  Neither do some webkit implementations.
      return !!window.indexedDB;
  };

  // see https://groups.google.com/a/chromium.org/forum/?fromgroups#!topic/chromium-html5/OhsoAQLj7kc
  var READ_WRITE = (getIDBTransaction() &&
                    'READ_WRITE' in getIDBTransaction()) ?
    getIDBTransaction().READ_WRITE : 'readwrite';

  return {

    valid: function() { return !!getIDB(); },

    init:function(options, callback) {
        this.idb = getIDB();
        this.waiting = [];
        this.useAutoIncrement = useAutoIncrement();
        var request = this.idb.open(this.name, STORE_VERSION);
        var self = this;
        var cb = self.fn(self.name, callback);
        var win = function() {
            // manually clean up event handlers on request; this helps on chrome
            request.onupgradeneeded = request.onsuccess = request.error = null;
            return cb.call(self, self);
        };

        var upgrade = function(from, to) {
            // don't try to migrate dbs, just recreate
            try {
                self.db.deleteObjectStore('teststore'); // old adapter
            } catch (e1) { /* ignore */ }
            try {
                self.db.deleteObjectStore(STORE_NAME);
            } catch (e2) { /* ignore */ }

            // ok, create object store.
            var params = {};
            if (self.useAutoIncrement) { params.autoIncrement = true; }
            self.db.createObjectStore(STORE_NAME, params);
            self.store = true;
        };
        request.onupgradeneeded = function(event) {
            self.db = request.result;
            self.transaction = request.transaction;
            upgrade(event.oldVersion, event.newVersion);
            // will end up in onsuccess callback
        };
        request.onsuccess = function(event) {
           self.db = request.result;

            if(self.db.version != (''+STORE_VERSION)) {
              // DEPRECATED API: modern implementations will fire the
              // upgradeneeded event instead.
              var oldVersion = self.db.version;
              var setVrequest = self.db.setVersion(''+STORE_VERSION);
              // onsuccess is the only place we can create Object Stores
              setVrequest.onsuccess = function(event) {
                  var transaction = setVrequest.result;
                  setVrequest.onsuccess = setVrequest.onerror = null;
                  // can't upgrade w/o versionchange transaction.
                  upgrade(oldVersion, STORE_VERSION);
                  transaction.oncomplete = function() {
                      for (var i = 0; i < self.waiting.length; i++) {
                          self.waiting[i].call(self);
                      }
                      self.waiting = [];
                      win();
                  };
              };
              setVrequest.onerror = function(e) {
                  setVrequest.onsuccess = setVrequest.onerror = null;
                  console.log("Failed to create objectstore " + e);
                  fail(e);
              };
            } else {
                self.store = true;
                for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                }
                self.waiting = [];
                win();
            }
        }
        request.onerror = function(ev) {
            if (request.errorCode === getIDBDatabaseException().VERSION_ERR) {
                // xxx blow it away
                self.idb.deleteDatabase(self.name);
                // try it again.
                return self.init(options, callback);
            }
            console.error('Failed to open database');
        };
    },

    save:function(obj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.save(obj, callback);
            });
            return;
         }

         var self = this, request;
         var win  = function (e) {
             // manually clean up event handlers; helps free memory on chrome.
             request.onsuccess = request.onerror = null;
             if (callback) { obj.key = e.target.result; self.lambda(callback).call(self, obj) }
         };

         var trans = this.db.transaction(STORE_NAME, READ_WRITE);
         var store = trans.objectStore(STORE_NAME);
         if (obj.key) {
             request = store.put(obj, obj.key);
         } else if (this.useAutoIncrement) {
             request = store.put(obj); // use autoIncrementing key.
         } else {
             request = store.put(obj, this.uuid()); // use randomly-generated key
         }

         request.onsuccess = win;
         request.onerror = fail;

         return this;
    },

    batch: function (objs, callback) {

        var results = []
        ,   done = objs.length
        ,   self = this

        var putOne = function(i) {
            self.save(objs[i], function(obj) {
                results[i] = obj;
                if ((--done) > 0) { return; }
                if (callback) {
                    self.lambda(callback).call(self, results);
                }
            });
        };

        for (var i = 0, l = objs.length; i < l; i++)
            putOne(i);

        return this
    },

    get:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.get(key, callback);
            });
            return;
        }

        var self = this;
        var win  = function (e) { if (callback) { self.lambda(callback).call(self, e.target.result) }};

        if (!this.isArray(key)){
            var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).get(key);

            req.onsuccess = function(event) {
                req.onsuccess = req.onerror = null;
                win(event);
            };
            req.onerror = function(event) {
                console.log("Failed to find " + key);
                req.onsuccess = req.onerror = null;
                fail(event);
            };

        } else {

            // note: these are hosted.
            var results = []
            ,   done = key.length
            ,   keys = key

            var getOne = function(i) {
                self.get(keys[i], function(obj) {
                    results[i] = obj;
                    if ((--done) > 0) { return; }
                    if (callback) {
                        self.lambda(callback).call(self, results);
                    }
                });
            };
            for (var i = 0, l = keys.length; i < l; i++)
                getOne(i);
        }

        return this;
    },

    exists:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.exists(key, callback);
            });
            return;
        }

        var self = this;

        var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).openCursor(getIDBKeyRange().only(key));

        req.onsuccess = function(event) {
            req.onsuccess = req.onerror = null;
            // exists iff req.result is not null
            // XXX but firefox returns undefined instead, sigh XXX
            var undef;
            self.lambda(callback).call(self, event.target.result !== null &&
                                             event.target.result !== undef);
        };
        req.onerror = function(event) {
            req.onsuccess = req.onerror = null;
            console.log("Failed to test for " + key);
            fail(event);
        };

        return this;
    },

    all:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.all(callback);
            });
            return;
        }
        var cb = this.fn(this.name, callback) || undefined;
        var self = this;
        var objectStore = this.db.transaction(STORE_NAME).objectStore(STORE_NAME);
        var toReturn = [];
        objectStore.openCursor().onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               toReturn.push(cursor.value);
               cursor['continue']();
          }
          else {
              if (cb) cb.call(self, toReturn);
          }
        };
        return this;
    },

    remove:function(keyOrObj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.remove(keyOrObj, callback);
            });
            return;
        }
        if (typeof keyOrObj == "object") {
            keyOrObj = keyOrObj.key;
        }
        var self = this, request;
        var win  = function () {
            request.onsuccess = request.onerror = null;
            if (callback) self.lambda(callback).call(self)
        };

        request = this.db.transaction(STORE_NAME, READ_WRITE).objectStore(STORE_NAME)['delete'](keyOrObj);
        request.onsuccess = win;
        request.onerror = fail;
        return this;
    },

    nuke:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.nuke(callback);
            });
            return;
        }

        var self = this
        ,   win  = callback ? function() { self.lambda(callback).call(self) } : function(){};

        try {
            this.db
                .transaction(STORE_NAME, READ_WRITE)
                .objectStore(STORE_NAME).clear().onsuccess = win;

        } catch(e) {
            fail();
        }
        return this;
    }

  };

})());
<commit_msg>IndexedDB: Implement the keys() method in the adapter.<commit_after>/**
 * indexed db adapter
 * ===
 * - originally authored by Vivian Li
 *
 */

Lawnchair.adapter('indexed-db', (function(){

  function fail(e, i) { console.error('error in indexed-db adapter!', e, i); }

  var STORE_NAME = 'lawnchair';

  // update the STORE_VERSION when the schema used by this adapter changes
  // (for example, if you change the STORE_NAME above)
  var STORE_VERSION = 2;

  var getIDB = function() {
    return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
  };
  var getIDBTransaction = function() {
      return window.IDBTransaction || window.webkitIDBTransaction ||
          window.mozIDBTransaction || window.oIDBTransaction ||
          window.msIDBTransaction;
  };
  var getIDBKeyRange = function() {
      return window.IDBKeyRange || window.webkitIDBKeyRange ||
          window.mozIDBKeyRange || window.oIDBKeyRange ||
          window.msIDBKeyRange;
  };
  var getIDBDatabaseException = function() {
      return window.IDBDatabaseException || window.webkitIDBDatabaseException ||
          window.mozIDBDatabaseException || window.oIDBDatabaseException ||
          window.msIDBDatabaseException;
  };
  var useAutoIncrement = function() {
      // using preliminary mozilla implementation which doesn't support
      // auto-generated keys.  Neither do some webkit implementations.
      return !!window.indexedDB;
  };

  // see https://groups.google.com/a/chromium.org/forum/?fromgroups#!topic/chromium-html5/OhsoAQLj7kc
  var READ_WRITE = (getIDBTransaction() &&
                    'READ_WRITE' in getIDBTransaction()) ?
    getIDBTransaction().READ_WRITE : 'readwrite';

  return {

    valid: function() { return !!getIDB(); },

    init:function(options, callback) {
        this.idb = getIDB();
        this.waiting = [];
        this.useAutoIncrement = useAutoIncrement();
        var request = this.idb.open(this.name, STORE_VERSION);
        var self = this;
        var cb = self.fn(self.name, callback);
        var win = function() {
            // manually clean up event handlers on request; this helps on chrome
            request.onupgradeneeded = request.onsuccess = request.error = null;
            return cb.call(self, self);
        };

        var upgrade = function(from, to) {
            // don't try to migrate dbs, just recreate
            try {
                self.db.deleteObjectStore('teststore'); // old adapter
            } catch (e1) { /* ignore */ }
            try {
                self.db.deleteObjectStore(STORE_NAME);
            } catch (e2) { /* ignore */ }

            // ok, create object store.
            var params = {};
            if (self.useAutoIncrement) { params.autoIncrement = true; }
            self.db.createObjectStore(STORE_NAME, params);
            self.store = true;
        };
        request.onupgradeneeded = function(event) {
            self.db = request.result;
            self.transaction = request.transaction;
            upgrade(event.oldVersion, event.newVersion);
            // will end up in onsuccess callback
        };
        request.onsuccess = function(event) {
           self.db = request.result;

            if(self.db.version != (''+STORE_VERSION)) {
              // DEPRECATED API: modern implementations will fire the
              // upgradeneeded event instead.
              var oldVersion = self.db.version;
              var setVrequest = self.db.setVersion(''+STORE_VERSION);
              // onsuccess is the only place we can create Object Stores
              setVrequest.onsuccess = function(event) {
                  var transaction = setVrequest.result;
                  setVrequest.onsuccess = setVrequest.onerror = null;
                  // can't upgrade w/o versionchange transaction.
                  upgrade(oldVersion, STORE_VERSION);
                  transaction.oncomplete = function() {
                      for (var i = 0; i < self.waiting.length; i++) {
                          self.waiting[i].call(self);
                      }
                      self.waiting = [];
                      win();
                  };
              };
              setVrequest.onerror = function(e) {
                  setVrequest.onsuccess = setVrequest.onerror = null;
                  console.log("Failed to create objectstore " + e);
                  fail(e);
              };
            } else {
                self.store = true;
                for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                }
                self.waiting = [];
                win();
            }
        }
        request.onerror = function(ev) {
            if (request.errorCode === getIDBDatabaseException().VERSION_ERR) {
                // xxx blow it away
                self.idb.deleteDatabase(self.name);
                // try it again.
                return self.init(options, callback);
            }
            console.error('Failed to open database');
        };
    },

    save:function(obj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.save(obj, callback);
            });
            return;
         }

         var self = this, request;
         var win  = function (e) {
             // manually clean up event handlers; helps free memory on chrome.
             request.onsuccess = request.onerror = null;
             if (callback) { obj.key = e.target.result; self.lambda(callback).call(self, obj) }
         };

         var trans = this.db.transaction(STORE_NAME, READ_WRITE);
         var store = trans.objectStore(STORE_NAME);
         if (obj.key) {
             request = store.put(obj, obj.key);
         } else if (this.useAutoIncrement) {
             request = store.put(obj); // use autoIncrementing key.
         } else {
             request = store.put(obj, this.uuid()); // use randomly-generated key
         }

         request.onsuccess = win;
         request.onerror = fail;

         return this;
    },

    batch: function (objs, callback) {

        var results = []
        ,   done = objs.length
        ,   self = this

        var putOne = function(i) {
            self.save(objs[i], function(obj) {
                results[i] = obj;
                if ((--done) > 0) { return; }
                if (callback) {
                    self.lambda(callback).call(self, results);
                }
            });
        };

        for (var i = 0, l = objs.length; i < l; i++)
            putOne(i);

        return this
    },

    get:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.get(key, callback);
            });
            return;
        }

        var self = this;
        var win  = function (e) { if (callback) { self.lambda(callback).call(self, e.target.result) }};

        if (!this.isArray(key)){
            var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).get(key);

            req.onsuccess = function(event) {
                req.onsuccess = req.onerror = null;
                win(event);
            };
            req.onerror = function(event) {
                console.log("Failed to find " + key);
                req.onsuccess = req.onerror = null;
                fail(event);
            };

        } else {

            // note: these are hosted.
            var results = []
            ,   done = key.length
            ,   keys = key

            var getOne = function(i) {
                self.get(keys[i], function(obj) {
                    results[i] = obj;
                    if ((--done) > 0) { return; }
                    if (callback) {
                        self.lambda(callback).call(self, results);
                    }
                });
            };
            for (var i = 0, l = keys.length; i < l; i++)
                getOne(i);
        }

        return this;
    },

    exists:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.exists(key, callback);
            });
            return;
        }

        var self = this;

        var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).openCursor(getIDBKeyRange().only(key));

        req.onsuccess = function(event) {
            req.onsuccess = req.onerror = null;
            // exists iff req.result is not null
            // XXX but firefox returns undefined instead, sigh XXX
            var undef;
            self.lambda(callback).call(self, event.target.result !== null &&
                                             event.target.result !== undef);
        };
        req.onerror = function(event) {
            req.onsuccess = req.onerror = null;
            console.log("Failed to test for " + key);
            fail(event);
        };

        return this;
    },

    all:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.all(callback);
            });
            return;
        }
        var cb = this.fn(this.name, callback) || undefined;
        var self = this;
        var objectStore = this.db.transaction(STORE_NAME).objectStore(STORE_NAME);
        var toReturn = [];
        objectStore.openCursor().onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               toReturn.push(cursor.value);
               cursor['continue']();
          }
          else {
              if (cb) cb.call(self, toReturn);
          }
        };
        return this;
    },

    keys:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.keys(callback);
            });
            return;
        }
        var cb = this.fn(this.name, callback) || undefined;
        var self = this;
        var objectStore = this.db.transaction(STORE_NAME).objectStore(STORE_NAME);
        var toReturn = [];
        // in theory we could use openKeyCursor() here, but no one actually
        // supports it yet.
        objectStore.openCursor().onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               toReturn.push(cursor.key);
               cursor['continue']();
          }
          else {
              if (cb) cb.call(self, toReturn);
          }
        };
        return this;
    },

    remove:function(keyOrObj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.remove(keyOrObj, callback);
            });
            return;
        }
        if (typeof keyOrObj == "object") {
            keyOrObj = keyOrObj.key;
        }
        var self = this, request;
        var win  = function () {
            request.onsuccess = request.onerror = null;
            if (callback) self.lambda(callback).call(self)
        };

        request = this.db.transaction(STORE_NAME, READ_WRITE).objectStore(STORE_NAME)['delete'](keyOrObj);
        request.onsuccess = win;
        request.onerror = fail;
        return this;
    },

    nuke:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.nuke(callback);
            });
            return;
        }

        var self = this
        ,   win  = callback ? function() { self.lambda(callback).call(self) } : function(){};

        try {
            this.db
                .transaction(STORE_NAME, READ_WRITE)
                .objectStore(STORE_NAME).clear().onsuccess = win;

        } catch(e) {
            fail();
        }
        return this;
    }

  };

})());
<|endoftext|>


<commit_before>/*globals requireJS*/
/*jshint node:true*/
/**
 * @author pmeijer / https://github.com/pmeijer
 */
'use strict';

var Q = require('q'),
    BlobClient = requireJS('common/blob/BlobClient'),
    Core = requireJS('common/core/core');

/**
 * Monitors given branch and starts, stops and updates registered addOns.
 *
 * @param {string} webGMESessionId
 * @param {EditorStorage} storage
 * @param {Project} project
 * @param {string} branchName
 * @param {object} gmeConfig
 * @param {object} mainLogger
 * @constructor
 */
function BranchMonitor(webGMESessionId, storage, project, branchName, mainLogger, gmeConfig) {
    var self = this,
        logger = mainLogger.fork('BranchMonitor:' + branchName),
        core = new Core(project, {globConf: gmeConfig, logger: logger.fork('core')}),
        startDeferred,
        stopDeferred;

    this.runningAddOns = [
        //{id: {string}, instance: {AddOnBase}}
    ];

    // State
    this.commitHash = '';
    this.rootHash = '';
    this.rootNode = null;
    this.commitMessage = '';

    this.stopRequested = false;
    this.startRequested = false;
    this.branchIsOpen = false;

    // Helper functions
    function getAddOn(id) {
        var addOnPath = 'addon/' + id + '/' + id + '/' + id,
            AddOn,
            error,
            addOn;

        logger.debug('requireJS addOn from path: ' + addOnPath);
        try {
            AddOn = requireJS(addOnPath);
        } catch (err) {
            error = err;
        }

        // This is weird, the second time requirejs simply returns with undefined.
        if (AddOn) {
            addOn = new AddOn(logger.fork(id), gmeConfig);
            return addOn;
        } else {
            return error || new Error('AddOn is not available from: ' + addOnPath);
        }
    }

    function getConfiguration() {
        var blobClient = new BlobClient({
            serverPort: gmeConfig.server.port,
            httpsecure: gmeConfig.server.https.enable,
            server: '127.0.0.1',
            webgmeclientsession: webGMESessionId
        });

        return {
            core: core,
            project: project,
            branchName: branchName,
            blobClient: blobClient
        };
    }

    function loadNewRoot(commitData, callback) {
        var deferred = Q.defer();
        self.rootHash = commitData.commitObject.root;
        self.commitHash = commitData.commitObject._id;
        logger.debug('loadNewRoot [commit/rootHash]', self.commitHash, self.rootHash);

        core.loadRoot(self.rootHash, function (err, root) {
            if (err) {
                deferred.reject(new Error(err));
                return;
            }

            self.rootNode = root;
            deferred.resolve(root);
        });

        return deferred.promise.nodeify(callback);
    }

    function updateRunningAddOns(commitObj, callback) {
        var deferred = new Q.defer(),
            counter = 0,
            limit = self.runningAddOns.length;

        function getUpdateCallback(addOn, counter) {
            return function (err, data) {
                if (err) {
                    // TODO: How to handle this?
                    logger.error('AddOn [' + addOn.id + '] returned error at init/update', err);
                }

                if (data.commitMessage) {
                    self.commitMessage += data.commitMessage;
                    logger.debug('AddOn [' + addOn.id + '] added to commitMessage', self.commitMessage);
                }

                if (data.notification) {
                    logger.warn('AddOn [' + addOn.id + '] sent not yet supported notification!', data.notification);
                }

                updateAddOn(self.runningAddOns[counter]);
            };
        }

        function updateAddOn(addOn) {
            if (counter === limit) {
                deferred.resolve();
                return;
            }
            counter += 1;
            if (addOn.instance.initialized === true) {
                logger.debug('updating addOn: ', addOn.id);
                addOn.instance.update(self.rootNode, commitObj, getUpdateCallback(addOn, counter));
            } else {
                addOn.instance.configure(getConfiguration());
                logger.debug('initializing addOn: ', addOn.id);
                addOn.instance._initialize(self.rootNode, commitObj, getUpdateCallback(addOn, counter));
            }
        }

        if (limit === 0) {
            logger.debug('There are no running addOns');
            deferred.resolve();
        } else {
            updateAddOn(self.runningAddOns[0]);
        }

        return deferred.promise.nodeify(callback);
    }

    function onUpdate(eventData, commitQueue, updateQueue, handlerCallback) {
        var commitData = eventData.commitData;

        if (eventData.local) {
            // This is when an addOn made changes and a commit was made below.
            logger.debug('AddOn made changes (local event data)');
            handlerCallback(null, self.stopRequested === false);
            return;
        }

        // loadNewRoot will set self.commitHash/rootHash/rootNode
        loadNewRoot(commitData)
            .then(function () {
                var runningAddOnsNew = [],
                    requiredAddOns = core.getRegistry(self.rootNode, 'usedAddOns'),
                    i,
                    j,
                    newAddOn,
                    wasRunning;

                if (typeof requiredAddOns === 'string') {
                    requiredAddOns = requiredAddOns.trim();
                    requiredAddOns = requiredAddOns ? requiredAddOns.split(' ') : [];
                } else {
                    requiredAddOns = [];
                }

                logger.debug('requiredAddOns:', requiredAddOns);

                for (i = 0; i < requiredAddOns.length; i += 1) {
                    wasRunning = false;
                    for (j = 0; j < self.runningAddOns.length; j += 1) {
                        //FIXME: This can raise an error that
                        if (self.runningAddOns[j].id === requiredAddOns[i]) {
                            runningAddOnsNew.push(self.runningAddOns[j]);
                            wasRunning = true;
                            break;
                        }
                    }
                    if (wasRunning === false) {
                        newAddOn = getAddOn(requiredAddOns[i]);
                        if (newAddOn instanceof Error) {
                            logger.error('Could not get addOn', newAddOn);
                        } else {
                            runningAddOnsNew.push({
                                id: requiredAddOns[i],
                                instance: newAddOn
                            });
                        }
                    }
                }

                self.runningAddOns = runningAddOnsNew;
                self.commitMessage = 'AddOns';

                return updateRunningAddOns(commitData.commitObject);
            })
            .then(function () {
                var persisted = core.persist(self.rootNode),
                    persistedObjects = Object.keys(persisted.objects);
                if (persistedObjects.length === 0) {
                    logger.debug('No changes made by addOns');
                } else {
                    // This will create an update event with local:true, see top of function.
                    logger.debug('Changes were made', self.commitMessage);
                    logger.debug('Number of persisted objects', persistedObjects.length);
                    project.makeCommit(branchName, [self.commitHash], persisted.rootHash,
                        persisted.objects, self.commitMessage,
                        function (err, result) {
                            if (err) {
                                logger.error('makeCommit', err);
                            } else {
                                logger.debug('makeCommit', result);
                            }
                        });
                }
                handlerCallback(null, self.stopRequested === false);
            })
            .catch(function (err) {
                logger.error('Fatal error', err);
                handlerCallback(err, false);
            });
    }

    function branchStatusHandler(branchStatus /*, commitQueue, updateQueue*/) {
        //TODO check how it should work
        logger.debug('New branchStatus', branchStatus);
    }

    // API functions
    /**
     * Opens up its branch and registers the onUpdate function.
     * @param [callback]
     * @returns {Promise}
     */
    this.start = function (callback) {
        if (self.startRequested === false) {
            startDeferred = Q.defer();
            self.startRequested = true;

            storage.openBranch(project.projectId, branchName, onUpdate, branchStatusHandler,
                function (err/*, latestCommitData*/) {
                    if (err) {
                        startDeferred.reject(err);
                        return;
                    }
                    self.branchIsOpen = true;
                    startDeferred.resolve();
                }
            );
        }

        return startDeferred.promise.nodeify(callback);
    };

    /**
     * Closes the open branch.
     * @param [callback]
     * @returns {Promise}
     */
    this.stop = function (callback) {
        if (self.stopRequested === false) {
            stopDeferred = Q.defer();
            self.stopRequested = true;

            storage.closeBranch(project.projectId, branchName, function (err) {
                self.branchIsOpen = false;
                if (err) {
                    stopDeferred.reject(err);
                } else {
                    stopDeferred.resolve();
                }
            });
        }

        return stopDeferred.promise.nodeify(callback);
    };

    this.queryAddOn = function (addOnId, commitHash, queryParams, callback) {
        var deferred = Q.defer();
        deferred.reject(new Error('queryAddOn not implemented yet!'));
        return deferred.promise.nodeify(callback);
    };
}

module.exports = BranchMonitor;<commit_msg>#579 call _update and not update.<commit_after>/*globals requireJS*/
/*jshint node:true*/
/**
 * @author pmeijer / https://github.com/pmeijer
 */
'use strict';

var Q = require('q'),
    BlobClient = requireJS('common/blob/BlobClient'),
    Core = requireJS('common/core/core');

/**
 * Monitors given branch and starts, stops and updates registered addOns.
 *
 * @param {string} webGMESessionId
 * @param {EditorStorage} storage
 * @param {Project} project
 * @param {string} branchName
 * @param {object} gmeConfig
 * @param {object} mainLogger
 * @constructor
 */
function BranchMonitor(webGMESessionId, storage, project, branchName, mainLogger, gmeConfig) {
    var self = this,
        logger = mainLogger.fork('BranchMonitor:' + branchName),
        core = new Core(project, {globConf: gmeConfig, logger: logger.fork('core')}),
        startDeferred,
        stopDeferred;

    this.runningAddOns = [
        //{id: {string}, instance: {AddOnBase}}
    ];

    // State
    this.commitHash = '';
    this.rootHash = '';
    this.rootNode = null;
    this.commitMessage = '';

    this.stopRequested = false;
    this.startRequested = false;
    this.branchIsOpen = false;

    // Helper functions
    function getAddOn(id) {
        var addOnPath = 'addon/' + id + '/' + id + '/' + id,
            AddOn,
            error,
            addOn;

        logger.debug('requireJS addOn from path: ' + addOnPath);
        try {
            AddOn = requireJS(addOnPath);
        } catch (err) {
            error = err;
        }

        // This is weird, the second time requirejs simply returns with undefined.
        if (AddOn) {
            addOn = new AddOn(logger.fork(id), gmeConfig);
            return addOn;
        } else {
            return error || new Error('AddOn is not available from: ' + addOnPath);
        }
    }

    function getConfiguration() {
        var blobClient = new BlobClient({
            serverPort: gmeConfig.server.port,
            httpsecure: gmeConfig.server.https.enable,
            server: '127.0.0.1',
            webgmeclientsession: webGMESessionId
        });

        return {
            core: core,
            project: project,
            branchName: branchName,
            blobClient: blobClient
        };
    }

    function loadNewRoot(commitData, callback) {
        var deferred = Q.defer();
        self.rootHash = commitData.commitObject.root;
        self.commitHash = commitData.commitObject._id;
        logger.debug('loadNewRoot [commit/rootHash]', self.commitHash, self.rootHash);

        core.loadRoot(self.rootHash, function (err, root) {
            if (err) {
                deferred.reject(new Error(err));
                return;
            }

            self.rootNode = root;
            deferred.resolve(root);
        });

        return deferred.promise.nodeify(callback);
    }

    function updateRunningAddOns(commitObj, callback) {
        var deferred = new Q.defer(),
            counter = 0,
            limit = self.runningAddOns.length;

        function getUpdateCallback(addOn, counter) {
            return function (err, data) {
                if (err) {
                    // TODO: How to handle this?
                    logger.error('AddOn [' + addOn.id + '] returned error at init/update', err);
                }

                if (data.commitMessage) {
                    self.commitMessage += data.commitMessage;
                    logger.debug('AddOn [' + addOn.id + '] added to commitMessage', self.commitMessage);
                }

                if (data.notification) {
                    logger.warn('AddOn [' + addOn.id + '] sent not yet supported notification!', data.notification);
                }

                updateAddOn(self.runningAddOns[counter]);
            };
        }

        function updateAddOn(addOn) {
            if (counter === limit) {
                deferred.resolve();
                return;
            }
            counter += 1;
            if (addOn.instance.initialized === true) {
                logger.debug('updating addOn: ', addOn.id);
                addOn.instance._update(self.rootNode, commitObj, getUpdateCallback(addOn, counter));
            } else {
                addOn.instance.configure(getConfiguration());
                logger.debug('initializing addOn: ', addOn.id);
                addOn.instance._initialize(self.rootNode, commitObj, getUpdateCallback(addOn, counter));
            }
        }

        if (limit === 0) {
            logger.debug('There are no running addOns');
            deferred.resolve();
        } else {
            updateAddOn(self.runningAddOns[0]);
        }

        return deferred.promise.nodeify(callback);
    }

    function onUpdate(eventData, commitQueue, updateQueue, handlerCallback) {
        var commitData = eventData.commitData;

        if (eventData.local) {
            // This is when an addOn made changes and a commit was made below.
            logger.debug('AddOn made changes (local event data)');
            handlerCallback(null, self.stopRequested === false);
            return;
        }

        // loadNewRoot will set self.commitHash/rootHash/rootNode
        loadNewRoot(commitData)
            .then(function () {
                var runningAddOnsNew = [],
                    requiredAddOns = core.getRegistry(self.rootNode, 'usedAddOns'),
                    i,
                    j,
                    newAddOn,
                    wasRunning;

                if (typeof requiredAddOns === 'string') {
                    requiredAddOns = requiredAddOns.trim();
                    requiredAddOns = requiredAddOns ? requiredAddOns.split(' ') : [];
                } else {
                    requiredAddOns = [];
                }

                logger.debug('requiredAddOns:', requiredAddOns);

                for (i = 0; i < requiredAddOns.length; i += 1) {
                    wasRunning = false;
                    for (j = 0; j < self.runningAddOns.length; j += 1) {
                        //FIXME: This can raise an error that
                        if (self.runningAddOns[j].id === requiredAddOns[i]) {
                            runningAddOnsNew.push(self.runningAddOns[j]);
                            wasRunning = true;
                            break;
                        }
                    }
                    if (wasRunning === false) {
                        newAddOn = getAddOn(requiredAddOns[i]);
                        if (newAddOn instanceof Error) {
                            logger.error('Could not get addOn', newAddOn);
                        } else {
                            runningAddOnsNew.push({
                                id: requiredAddOns[i],
                                instance: newAddOn
                            });
                        }
                    }
                }

                self.runningAddOns = runningAddOnsNew;
                self.commitMessage = 'AddOns';

                return updateRunningAddOns(commitData.commitObject);
            })
            .then(function () {
                var persisted = core.persist(self.rootNode),
                    persistedObjects = Object.keys(persisted.objects);
                if (persistedObjects.length === 0) {
                    logger.debug('No changes made by addOns');
                } else {
                    // This will create an update event with local:true, see top of function.
                    logger.debug('Changes were made', self.commitMessage);
                    logger.debug('Number of persisted objects', persistedObjects.length);
                    project.makeCommit(branchName, [self.commitHash], persisted.rootHash,
                        persisted.objects, self.commitMessage,
                        function (err, result) {
                            if (err) {
                                logger.error('makeCommit', err);
                            } else {
                                logger.debug('makeCommit', result);
                            }
                        });
                }
                handlerCallback(null, self.stopRequested === false);
            })
            .catch(function (err) {
                logger.error('Fatal error', err);
                handlerCallback(err, false);
            });
    }

    function branchStatusHandler(branchStatus /*, commitQueue, updateQueue*/) {
        //TODO check how it should work
        logger.debug('New branchStatus', branchStatus);
    }

    // API functions
    /**
     * Opens up its branch and registers the onUpdate function.
     * @param [callback]
     * @returns {Promise}
     */
    this.start = function (callback) {
        if (self.startRequested === false) {
            startDeferred = Q.defer();
            self.startRequested = true;

            storage.openBranch(project.projectId, branchName, onUpdate, branchStatusHandler,
                function (err/*, latestCommitData*/) {
                    if (err) {
                        startDeferred.reject(err);
                        return;
                    }
                    self.branchIsOpen = true;
                    startDeferred.resolve();
                }
            );
        }

        return startDeferred.promise.nodeify(callback);
    };

    /**
     * Closes the open branch.
     * @param [callback]
     * @returns {Promise}
     */
    this.stop = function (callback) {
        if (self.stopRequested === false) {
            stopDeferred = Q.defer();
            self.stopRequested = true;

            storage.closeBranch(project.projectId, branchName, function (err) {
                self.branchIsOpen = false;
                if (err) {
                    stopDeferred.reject(err);
                } else {
                    stopDeferred.resolve();
                }
            });
        }

        return stopDeferred.promise.nodeify(callback);
    };

    this.queryAddOn = function (addOnId, commitHash, queryParams, callback) {
        var deferred = Q.defer();
        deferred.reject(new Error('queryAddOn not implemented yet!'));
        return deferred.promise.nodeify(callback);
    };
}

module.exports = BranchMonitor;<|endoftext|>


<commit_before>(function(root, factory) {
  'use strict';

  var angular = (root && root.angular) || (window && window.angular);
  if(typeof define === 'function' && define.amd) {                    // AMD
    define(['localforage'], function(localforage) {
      return factory(angular, localforage);
    });
  } else if(typeof exports === 'object' || typeof global === 'object') {
    module.exports = factory(angular, require('localforage')); // Node/Browserify
  } else {
    return factory(angular, root.localforage);                        // Browser
  }
})(this, function(angular, localforage, undefined) {
  'use strict';

  var angularLocalForage = angular.module('LocalForageModule', ['ng']);
  angularLocalForage.provider('$localForage', function() {
    var lfInstances = {},
      defaultConfig = {
        name: 'lf'
      },
    // Send signals for each of the following actions ?
      notify = {
        setItem: false,
        removeItem: false
      },
      watchers = {};

    // Setter for notification config, itemSet & itemRemove should be booleans
    this.setNotify = function(itemSet, itemRemove) {
      notify = {
        setItem: itemSet,
        removeItem: itemRemove
      };
    };

    this.config = function(config) {
      if(!angular.isObject(config)) {
        throw new Error('The config parameter should be an object');
      }
      angular.extend(defaultConfig, config);
    };

    this.$get = ['$rootScope', '$q', '$parse', function($rootScope, $q, $parse) {
      var LocalForageInstance = function LocalForageInstance(params) {
        if(angular.isDefined(params)) {
          this._localforage = localforage.createInstance(params);
        } else {
          this._localforage = localforage;
          localforage.config(defaultConfig);
        }
      };

      LocalForageInstance.prototype.createInstance = function createInstance(config) {
        if(angular.isObject(config)) { // create new instance
          config = angular.extend({}, defaultConfig, config);
          if(angular.isDefined(lfInstances[config.name])) {
            throw new Error('A localForage instance with the name ' + config.name + ' is already defined.');
          }

          lfInstances[config.name] = new LocalForageInstance(config);
          return lfInstances[config.name];
        } else {
          throw new Error('The parameter should be a config object.')
        }
      };

      LocalForageInstance.prototype.instance = function instance(name) {
        if(angular.isUndefined(name)) {
          return lfInstances[defaultConfig.name];
        } else if(angular.isString(name)) {
          if(angular.isDefined(lfInstances[name])) {
            return lfInstances[name];
          } else {
            throw new Error('No localForage instance of that name exists.')
          }
        } else {
          throw new Error('The parameter should be a string.')
        }
      };

      // Setter for the storage driver
      LocalForageInstance.prototype.setDriver = function setDriver(driver) {
        return this._localforage.setDriver(driver);
      };

      // Getter for the storage driver
      LocalForageInstance.prototype.driver = function driver() {
        return this._localforage.driver();
      };

      // Directly adds a value to storage
      LocalForageInstance.prototype.setItem = function setItem(key, value) {
        // throw error on undefined key, we allow undefined value because... why not ?
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to set");
        }

        var self = this;

        if(angular.isArray(key)) {
          if(!angular.isArray(value)) {
            throw new Error('If you set an array of keys, the values should be an array too');
          }

          var promises = [];
          angular.forEach(key, function(k, index) {
            promises.push(self.setItem(k, value[index]))
          });

          return $q.all(promises);
        } else {
          var deferred = $q.defer(),
            args = arguments,
            localCopy = typeof Blob !== 'undefined' && typeof ArrayBuffer !== 'undefined' && (value instanceof Blob || value instanceof ArrayBuffer) ? value : angular.copy(value);

          //avoid $promises attributes from value objects, if present.
          if(angular.isObject(localCopy) && angular.isDefined(localCopy.$promise)) {
            delete localCopy.$promise; //delete attribut from object structure.
          }

          self._localforage.setItem(self.prefix() + key, localCopy).then(function success() {
            if(notify.setItem) {
              $rootScope.$broadcast('LocalForageModule.setItem', {
                key: key,
                newvalue: localCopy,
                driver: self.driver()
              });
            }
            deferred.resolve(localCopy);
          }, function error(data) {
            self.onError(data, args, self.setItem, deferred);
          });

          return deferred.promise;
        }
      };

      // Directly get a value from storage
      LocalForageInstance.prototype.getItem = function getItem(key, rejectOnNull) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to get");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this,
          promise;

        if(angular.isArray(key)) {
          var res = [],
            found = 0;
          promise = self._localforage.iterate(function(value, k) {
            var index = key.indexOf(self.prefix() + k);
            if(index > -1) {
              res[index] = value;
              found++;
            }
            if(found === key.length) {
              return res;
            }
          }).then(function() {
            var shouldResolve = true;
            for (var i = 0; i < key.length; i++) {
              if (angular.isUndefined(res[i])) {
                res[i] = null;
                shouldResolve = false;
              }
            }
            if (shouldResolve || !rejectOnNull) {
              deferred.resolve(res);
            } else {
              deferred.reject(res);
            }
          });
        } else {
          promise = self._localforage.getItem(self.prefix() + key).then(function(item) {
            if (rejectOnNull && item === null) {
              deferred.reject(item);
            } else {
              deferred.resolve(item);
            }
          });
        }

        promise.then(null, function error(data) {
          self.onError(data, args, self.getItem, deferred);
        });

        return deferred.promise;
      };

      // Iterate over all the values in storage
      LocalForageInstance.prototype.iterate = function iterate(callback) {
        // throw error on undefined key
        if(angular.isUndefined(callback)) {
          throw new Error("You must define a callback to iterate");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.iterate(callback).then(function success(item) {
          deferred.resolve(item);
        }, function error(data) {
          self.onError(data, args, self.iterate, deferred);
        });

        return deferred.promise;
      };

      // Remove an item from storage
      LocalForageInstance.prototype.removeItem = function removeItem(key) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to remove");
        }

        var self = this;

        if(angular.isArray(key)) {
          var promises = [];
          angular.forEach(key, function(k, index) {
            promises.push(self.removeItem(k));
          });

          return $q.all(promises);
        } else {
          var deferred = $q.defer(),
            args = arguments;

          self._localforage.removeItem(self.prefix() + key).then(function success() {
            if(notify.removeItem) {
              $rootScope.$broadcast('LocalForageModule.removeItem', {key: key, driver: self.driver()});
            }
            deferred.resolve();
          }, function error(data) {
            self.onError(data, args, self.removeItem, deferred);
          });

          return deferred.promise;
        }
      };

      // Get an item and removes it from storage
      LocalForageInstance.prototype.pull = function pull(key) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to pull");
        }

        var self = this,
          deferred = $q.defer(),
          onError = function error(err) {
            deferred.reject(err);
          };

        self.getItem(key).then(function success(value) {
          self.removeItem(key).then(function success() {
            deferred.resolve(value);
          }, onError);
        }, onError);

        return deferred.promise;
      };

      // Remove all data for this app from storage
      LocalForageInstance.prototype.clear = function clear() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.clear().then(function success(keys) {
          deferred.resolve();
        }, function error(data) {
          self.onError(data, args, self.clear, deferred);
        });
        return deferred.promise;
      };

      // Return the key for item at position n
      LocalForageInstance.prototype.key = function key(n) {
        // throw error on undefined n
        if(angular.isUndefined(n)) {
          throw new Error("You must define a position to get for the key function");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.key(n).then(function success(key) {
          deferred.resolve(key);
        }, function error(data) {
          self.onError(data, args, self.key, deferred);
        });
        return deferred.promise;
      };

      var keys = function keys() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.keys().then(function success(keyList) {
          if(defaultConfig.oldPrefix && self.driver() === 'localStorageWrapper') {
            var tempKeyList = [];
            for(var i = 0, len = keyList.length; i < len; i++) {
              tempKeyList.push(keyList[i].substr(self.prefix().length, keyList[i].length));
            }
            keyList = tempKeyList;
          }
          deferred.resolve(keyList);
        }, function error(data) {
          self.onError(data, args, self.keys, deferred);
        });
        return deferred.promise;
      };

      // Return the list of keys stored for this application
      LocalForageInstance.prototype.keys = keys;

      // deprecated
      LocalForageInstance.prototype.getKeys = keys;

      // Returns the number of keys in this storage
      LocalForageInstance.prototype.length = function() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.length().then(function success(length) {
          deferred.resolve(length);
        }, function error(data) {
          self.onError(data, args, length, deferred);
        });
        return deferred.promise;
      };

      /**
       * Bind - let's you directly bind a LocalForage value to a $scope variable
       * @param {Angular $scope} $scope - the current scope you want the variable available in
       * @param {String/Object} opts - the key name of the variable you are binding OR an object with the key and custom options like default value or instance name
       * Here are the available options you can set:
       * * key: the key used in storage and in the scope (if scopeKey isn't defined)
       * * defaultValue: the default value
       * * name: name of the instance that should store the data
       * * scopeKey: the key used in the scope
       * @returns {*} - returns whatever the stored value is
       */
      LocalForageInstance.prototype.bind = function bind($scope, opts) {
        if(angular.isString(opts)) {
          opts = {
            key: opts
          }
        } else if(!angular.isObject(opts) || angular.isUndefined(opts.key)) {
          throw new Error("You must define a key to bind");
        }

        var defaultOpts = {
          defaultValue: '',
          name: defaultConfig.name
        };

        // If no defined options we use defaults otherwise extend defaults
        opts = angular.extend({}, defaultOpts, opts);

        var self = lfInstances[opts.name];

        if(angular.isUndefined(self)) {
          throw new Error("You must use the name of an existing instance");
        }

        // Set the storeName key for the LocalForage entry
        // use user defined in specified
        var scopeKey = opts.scopeKey || opts.key,
          model = $parse(scopeKey);

        return self.getItem(opts.key).then(function(item) {
          if (item !== null) { // If it does exist assign it to the $scope value
            model.assign($scope, item);
          } else if(!angular.isUndefined(opts.defaultValue)) { // If a value doesn't already exist store it as is
            model.assign($scope, opts.defaultValue);
            self.setItem(opts.key, opts.defaultValue);
          }

          // Register a listener for changes on the $scope value
          // to update the localForage value
          if(angular.isDefined(watchers[opts.key])) {
            watchers[opts.key]();
          }

          watchers[opts.key] = $scope.$watch(scopeKey, function(val) {
            if(angular.isDefined(val)) {
              self.setItem(opts.key, val);
            }
          }, true);
          return item;
        });
      };

      /**
       * Unbind - let's you unbind a variable from localForage while removing the value from both
       * the localForage and the local variable and sets it to null
       * @param {String/Object} opts - the key name of the variable you are unbinding OR an object with the key and custom options like default value or instance name
       * Here are the available options you can set:
       * * key: the key used in storage and in the scope (if scopeKey isn't defined)
       * * name: name of the instance that should store the data
       * * scopeKey: the key used in the scope
       */
      LocalForageInstance.prototype.unbind = function unbind($scope, opts) {
        if(angular.isString(opts)) {
          opts = {
            key: opts
          }
        } else if(!angular.isObject(opts) || angular.isUndefined(opts.key)) {
          throw new Error("You must define a key to unbind");
        }

        var defaultOpts = {
          scopeKey: opts.key,
          name: defaultConfig.name
        };

        // If no defined options we use defaults otherwise extend defaults
        opts = angular.extend({}, defaultOpts, opts);

        var self = lfInstances[opts.name];

        if(angular.isUndefined(self)) {
          throw new Error("You must use the name of an existing instance");
        }

        $parse(opts.scopeKey).assign($scope, null);
        if(angular.isDefined(watchers[opts.key])) {
          watchers[opts.key](); // unwatch
          delete watchers[opts.key];
        }
        return self.removeItem(opts.key);
      };

      LocalForageInstance.prototype.prefix = function() {
        return this.driver() === 'localStorageWrapper' && defaultConfig.oldPrefix ? this._localforage.config().name + '.' : '';
      };

      // Handling errors
      LocalForageInstance.prototype.onError = function(err, args, fct, deferred) {
        // test for private browsing errors in Firefox & Safari
        if(((angular.isObject(err) && err.name ? err.name === 'InvalidStateError' : (angular.isString(err) && err === 'InvalidStateError')) && this.driver() === 'asyncStorage')
          || (angular.isObject(err) && err.code && err.code === 5)) {
          var self = this;

          self.setDriver('localStorageWrapper').then(function() {
            fct.apply(self, args).then(function(item) {
              deferred.resolve(item);
            }, function(data) {
              deferred.reject(data);
            });
          }, function() {
            deferred.reject(err);
          });
        } else {
          deferred.reject(err);
        }
      };

      lfInstances[defaultConfig.name] = new LocalForageInstance();
      return lfInstances[defaultConfig.name];
    }]
  });

  angularLocalForage.directive('localForage', ['$localForage', function($localForage) {
    return {
      restrict: 'A',
      link: function($scope, $element, $attrs) {
        var opts = $scope.$eval($attrs.localForage);
        if(angular.isObject(opts) && angular.isDefined(opts.key)) {
          $localForage.bind($scope, opts);
        } else {
          $localForage.bind($scope, $attrs.localForage);
        }
      }
    }
  }]);

  return angularLocalForage.name;
});

<commit_msg>fix: Check-in src changes for NW.js<commit_after>(function(root, factory) {
  'use strict';

  var angular = (root && root.angular) || (window && window.angular);
  if(typeof define === 'function' && define.amd) {                    // AMD
    define(['localforage'], function(localforage) {
      return factory(angular, localforage);
    });
  } else if(typeof exports === 'object' || typeof global === 'object') {
      if(typeof module === 'undefined') {
          global.module.exports = factory(angular, require('localforage')); // NW.js
      } else {
          module.exports = factory(angular, require('localforage')); // Node/Browserify
      }
  } else {
    return factory(angular, root.localforage);                        // Browser
  }
})(this, function(angular, localforage, undefined) {
  'use strict';

  var angularLocalForage = angular.module('LocalForageModule', ['ng']);
  angularLocalForage.provider('$localForage', function() {
    var lfInstances = {},
      defaultConfig = {
        name: 'lf'
      },
    // Send signals for each of the following actions ?
      notify = {
        setItem: false,
        removeItem: false
      },
      watchers = {};

    // Setter for notification config, itemSet & itemRemove should be booleans
    this.setNotify = function(itemSet, itemRemove) {
      notify = {
        setItem: itemSet,
        removeItem: itemRemove
      };
    };

    this.config = function(config) {
      if(!angular.isObject(config)) {
        throw new Error('The config parameter should be an object');
      }
      angular.extend(defaultConfig, config);
    };

    this.$get = ['$rootScope', '$q', '$parse', function($rootScope, $q, $parse) {
      var LocalForageInstance = function LocalForageInstance(params) {
        if(angular.isDefined(params)) {
          this._localforage = localforage.createInstance(params);
        } else {
          this._localforage = localforage;
          localforage.config(defaultConfig);
        }
      };

      LocalForageInstance.prototype.createInstance = function createInstance(config) {
        if(angular.isObject(config)) { // create new instance
          config = angular.extend({}, defaultConfig, config);
          if(angular.isDefined(lfInstances[config.name])) {
            throw new Error('A localForage instance with the name ' + config.name + ' is already defined.');
          }

          lfInstances[config.name] = new LocalForageInstance(config);
          return lfInstances[config.name];
        } else {
          throw new Error('The parameter should be a config object.')
        }
      };

      LocalForageInstance.prototype.instance = function instance(name) {
        if(angular.isUndefined(name)) {
          return lfInstances[defaultConfig.name];
        } else if(angular.isString(name)) {
          if(angular.isDefined(lfInstances[name])) {
            return lfInstances[name];
          } else {
            throw new Error('No localForage instance of that name exists.')
          }
        } else {
          throw new Error('The parameter should be a string.')
        }
      };

      // Setter for the storage driver
      LocalForageInstance.prototype.setDriver = function setDriver(driver) {
        return this._localforage.setDriver(driver);
      };

      // Getter for the storage driver
      LocalForageInstance.prototype.driver = function driver() {
        return this._localforage.driver();
      };

      // Directly adds a value to storage
      LocalForageInstance.prototype.setItem = function setItem(key, value) {
        // throw error on undefined key, we allow undefined value because... why not ?
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to set");
        }

        var self = this;

        if(angular.isArray(key)) {
          if(!angular.isArray(value)) {
            throw new Error('If you set an array of keys, the values should be an array too');
          }

          var promises = [];
          angular.forEach(key, function(k, index) {
            promises.push(self.setItem(k, value[index]))
          });

          return $q.all(promises);
        } else {
          var deferred = $q.defer(),
            args = arguments,
            localCopy = typeof Blob !== 'undefined' && typeof ArrayBuffer !== 'undefined' && (value instanceof Blob || value instanceof ArrayBuffer) ? value : angular.copy(value);

          //avoid $promises attributes from value objects, if present.
          if(angular.isObject(localCopy) && angular.isDefined(localCopy.$promise)) {
            delete localCopy.$promise; //delete attribut from object structure.
          }

          self._localforage.setItem(self.prefix() + key, localCopy).then(function success() {
            if(notify.setItem) {
              $rootScope.$broadcast('LocalForageModule.setItem', {
                key: key,
                newvalue: localCopy,
                driver: self.driver()
              });
            }
            deferred.resolve(localCopy);
          }, function error(data) {
            self.onError(data, args, self.setItem, deferred);
          });

          return deferred.promise;
        }
      };

      // Directly get a value from storage
      LocalForageInstance.prototype.getItem = function getItem(key, rejectOnNull) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to get");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this,
          promise;

        if(angular.isArray(key)) {
          var res = [],
            found = 0;
          promise = self._localforage.iterate(function(value, k) {
            var index = key.indexOf(self.prefix() + k);
            if(index > -1) {
              res[index] = value;
              found++;
            }
            if(found === key.length) {
              return res;
            }
          }).then(function() {
            var shouldResolve = true;
            for (var i = 0; i < key.length; i++) {
              if (angular.isUndefined(res[i])) {
                res[i] = null;
                shouldResolve = false;
              }
            }
            if (shouldResolve || !rejectOnNull) {
              deferred.resolve(res);
            } else {
              deferred.reject(res);
            }
          });
        } else {
          promise = self._localforage.getItem(self.prefix() + key).then(function(item) {
            if (rejectOnNull && item === null) {
              deferred.reject(item);
            } else {
              deferred.resolve(item);
            }
          });
        }

        promise.then(null, function error(data) {
          self.onError(data, args, self.getItem, deferred);
        });

        return deferred.promise;
      };

      // Iterate over all the values in storage
      LocalForageInstance.prototype.iterate = function iterate(callback) {
        // throw error on undefined key
        if(angular.isUndefined(callback)) {
          throw new Error("You must define a callback to iterate");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.iterate(callback).then(function success(item) {
          deferred.resolve(item);
        }, function error(data) {
          self.onError(data, args, self.iterate, deferred);
        });

        return deferred.promise;
      };

      // Remove an item from storage
      LocalForageInstance.prototype.removeItem = function removeItem(key) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to remove");
        }

        var self = this;

        if(angular.isArray(key)) {
          var promises = [];
          angular.forEach(key, function(k, index) {
            promises.push(self.removeItem(k));
          });

          return $q.all(promises);
        } else {
          var deferred = $q.defer(),
            args = arguments;

          self._localforage.removeItem(self.prefix() + key).then(function success() {
            if(notify.removeItem) {
              $rootScope.$broadcast('LocalForageModule.removeItem', {key: key, driver: self.driver()});
            }
            deferred.resolve();
          }, function error(data) {
            self.onError(data, args, self.removeItem, deferred);
          });

          return deferred.promise;
        }
      };

      // Get an item and removes it from storage
      LocalForageInstance.prototype.pull = function pull(key) {
        // throw error on undefined key
        if(angular.isUndefined(key)) {
          throw new Error("You must define a key to pull");
        }

        var self = this,
          deferred = $q.defer(),
          onError = function error(err) {
            deferred.reject(err);
          };

        self.getItem(key).then(function success(value) {
          self.removeItem(key).then(function success() {
            deferred.resolve(value);
          }, onError);
        }, onError);

        return deferred.promise;
      };

      // Remove all data for this app from storage
      LocalForageInstance.prototype.clear = function clear() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.clear().then(function success(keys) {
          deferred.resolve();
        }, function error(data) {
          self.onError(data, args, self.clear, deferred);
        });
        return deferred.promise;
      };

      // Return the key for item at position n
      LocalForageInstance.prototype.key = function key(n) {
        // throw error on undefined n
        if(angular.isUndefined(n)) {
          throw new Error("You must define a position to get for the key function");
        }

        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.key(n).then(function success(key) {
          deferred.resolve(key);
        }, function error(data) {
          self.onError(data, args, self.key, deferred);
        });
        return deferred.promise;
      };

      var keys = function keys() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.keys().then(function success(keyList) {
          if(defaultConfig.oldPrefix && self.driver() === 'localStorageWrapper') {
            var tempKeyList = [];
            for(var i = 0, len = keyList.length; i < len; i++) {
              tempKeyList.push(keyList[i].substr(self.prefix().length, keyList[i].length));
            }
            keyList = tempKeyList;
          }
          deferred.resolve(keyList);
        }, function error(data) {
          self.onError(data, args, self.keys, deferred);
        });
        return deferred.promise;
      };

      // Return the list of keys stored for this application
      LocalForageInstance.prototype.keys = keys;

      // deprecated
      LocalForageInstance.prototype.getKeys = keys;

      // Returns the number of keys in this storage
      LocalForageInstance.prototype.length = function() {
        var deferred = $q.defer(),
          args = arguments,
          self = this;

        self._localforage.length().then(function success(length) {
          deferred.resolve(length);
        }, function error(data) {
          self.onError(data, args, length, deferred);
        });
        return deferred.promise;
      };

      /**
       * Bind - let's you directly bind a LocalForage value to a $scope variable
       * @param {Angular $scope} $scope - the current scope you want the variable available in
       * @param {String/Object} opts - the key name of the variable you are binding OR an object with the key and custom options like default value or instance name
       * Here are the available options you can set:
       * * key: the key used in storage and in the scope (if scopeKey isn't defined)
       * * defaultValue: the default value
       * * name: name of the instance that should store the data
       * * scopeKey: the key used in the scope
       * @returns {*} - returns whatever the stored value is
       */
      LocalForageInstance.prototype.bind = function bind($scope, opts) {
        if(angular.isString(opts)) {
          opts = {
            key: opts
          }
        } else if(!angular.isObject(opts) || angular.isUndefined(opts.key)) {
          throw new Error("You must define a key to bind");
        }

        var defaultOpts = {
          defaultValue: '',
          name: defaultConfig.name
        };

        // If no defined options we use defaults otherwise extend defaults
        opts = angular.extend({}, defaultOpts, opts);

        var self = lfInstances[opts.name];

        if(angular.isUndefined(self)) {
          throw new Error("You must use the name of an existing instance");
        }

        // Set the storeName key for the LocalForage entry
        // use user defined in specified
        var scopeKey = opts.scopeKey || opts.key,
          model = $parse(scopeKey);

        return self.getItem(opts.key).then(function(item) {
          if (item !== null) { // If it does exist assign it to the $scope value
            model.assign($scope, item);
          } else if(!angular.isUndefined(opts.defaultValue)) { // If a value doesn't already exist store it as is
            model.assign($scope, opts.defaultValue);
            self.setItem(opts.key, opts.defaultValue);
          }

          // Register a listener for changes on the $scope value
          // to update the localForage value
          if(angular.isDefined(watchers[opts.key])) {
            watchers[opts.key]();
          }

          watchers[opts.key] = $scope.$watch(scopeKey, function(val) {
            if(angular.isDefined(val)) {
              self.setItem(opts.key, val);
            }
          }, true);
          return item;
        });
      };

      /**
       * Unbind - let's you unbind a variable from localForage while removing the value from both
       * the localForage and the local variable and sets it to null
       * @param {String/Object} opts - the key name of the variable you are unbinding OR an object with the key and custom options like default value or instance name
       * Here are the available options you can set:
       * * key: the key used in storage and in the scope (if scopeKey isn't defined)
       * * name: name of the instance that should store the data
       * * scopeKey: the key used in the scope
       */
      LocalForageInstance.prototype.unbind = function unbind($scope, opts) {
        if(angular.isString(opts)) {
          opts = {
            key: opts
          }
        } else if(!angular.isObject(opts) || angular.isUndefined(opts.key)) {
          throw new Error("You must define a key to unbind");
        }

        var defaultOpts = {
          scopeKey: opts.key,
          name: defaultConfig.name
        };

        // If no defined options we use defaults otherwise extend defaults
        opts = angular.extend({}, defaultOpts, opts);

        var self = lfInstances[opts.name];

        if(angular.isUndefined(self)) {
          throw new Error("You must use the name of an existing instance");
        }

        $parse(opts.scopeKey).assign($scope, null);
        if(angular.isDefined(watchers[opts.key])) {
          watchers[opts.key](); // unwatch
          delete watchers[opts.key];
        }
        return self.removeItem(opts.key);
      };

      LocalForageInstance.prototype.prefix = function() {
        return this.driver() === 'localStorageWrapper' && defaultConfig.oldPrefix ? this._localforage.config().name + '.' : '';
      };

      // Handling errors
      LocalForageInstance.prototype.onError = function(err, args, fct, deferred) {
        // test for private browsing errors in Firefox & Safari
        if(((angular.isObject(err) && err.name ? err.name === 'InvalidStateError' : (angular.isString(err) && err === 'InvalidStateError')) && this.driver() === 'asyncStorage')
          || (angular.isObject(err) && err.code && err.code === 5)) {
          var self = this;

          self.setDriver('localStorageWrapper').then(function() {
            fct.apply(self, args).then(function(item) {
              deferred.resolve(item);
            }, function(data) {
              deferred.reject(data);
            });
          }, function() {
            deferred.reject(err);
          });
        } else {
          deferred.reject(err);
        }
      };

      lfInstances[defaultConfig.name] = new LocalForageInstance();
      return lfInstances[defaultConfig.name];
    }]
  });

  angularLocalForage.directive('localForage', ['$localForage', function($localForage) {
    return {
      restrict: 'A',
      link: function($scope, $element, $attrs) {
        var opts = $scope.$eval($attrs.localForage);
        if(angular.isObject(opts) && angular.isDefined(opts.key)) {
          $localForage.bind($scope, opts);
        } else {
          $localForage.bind($scope, $attrs.localForage);
        }
      }
    }
  }]);

  return angularLocalForage.name;
});

<|endoftext|>


<commit_before>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "sensorId": {
            "type": "string"
        },
        "date": {
            "type": "string",
            "format": "date-time"
        },
        "source": {
            "type": "string",
            "enum": [
                "forecast",
                "reading",
                "reference"
            ]
        },
        "measurements": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "value": {
                        "type": "number"
                    },
                    "unitOfMeasurement": {
                        "type": "string"
                    }
                },
                "required": [
                    "type",
                    "value",
                    "unitOfMeasurement"
                ],
                "additionalProperties": false
            }
        }
    },
    "required": [
        "sensorId",
        "date",
        "measurements"
    ],
    "additionalProperties": false
};
<commit_msg>Modify json schema<commit_after>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "sensorId": {
            "type": "string"
        },
        "date": {
            "type": "string",
            "format": "date-time"
        },
        "source": {
            "type": "string",
            "enum": [
                "forecast",
                "reading",
                "reference"
            ]
        },
        "measurements": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "value": {
                        "type": "number"
                    },
                    "unitOfMeasurement": {
                        "type": "string"
                    }
                },
                "required": [
                    "type",
                    "value",
                    "unitOfMeasurement"
                ],
                "additionalProperties": true
            }
        }
    },
    "required": [
        "sensorId",
        "date",
        "measurements"
    ],
    "additionalProperties": true
};
<|endoftext|>