<commit_before>var fs     = require("fs");
var http   = require("http");
var os     = require("os");
var Stream = require("stream");
var URL    = require("url");
var util   = require("util");

// The starting point is everything that works in any environment (browser OR
// Node.js)
var Lazy = require("./lazy.js");

var isHarmonySupported = require('./util.js').isHarmonySupported;
if (isHarmonySupported()) {
  require('./experimental/lazy.es6.js');
}

/**
 * @constructor
 */
function StreamedSequence(stream) {
  this.stream = stream;
}

StreamedSequence.prototype = new Lazy.StreamLikeSequence();

StreamedSequence.prototype.openStream = function(callback) {
  this.stream.resume();
  callback(this.stream);
};

/**
 * Handles every chunk of data in this sequence.
 *
 * @param {function(string):*} fn The function to call on each chunk of data as
 *     it's read from the stream. Return false from the function to stop reading
 *     the stream.
 */
StreamedSequence.prototype.each = function(fn) {
  var cancelled = false;

  var handle = new Lazy.AsyncHandle(function cancel() { cancelled = true; });

  this.openStream(function(stream) {
    if (stream.setEncoding) {
      stream.setEncoding(this.encoding || 'utf8');
    }

    var listener = function(e) {
      try {
        if (cancelled || fn(e) === false) {
          stream.removeListener("data", listener);
          handle._resolve(false);
        }
      } catch (e) {
        handle._reject(e);
      }
    };

    stream.on("data", listener);

    stream.on("end", function() {
      handle._resolve(true);
    });
  });

  return handle;
};

/**
 * Creates a {@link Sequence} of lines as they are read from a file.
 *
 * @return {Sequence} A sequence comprising the lines in the underlying file, as
 *     they are read.
 */
StreamedSequence.prototype.lines = function() {
  return this.split(os.EOL || "\n");
};

function FileStreamSequence(path, encoding) {
  this.path = path;
  this.encoding = encoding;
}

FileStreamSequence.prototype = new StreamedSequence();

FileStreamSequence.prototype.openStream = function(callback) {
  var stream = fs.createReadStream(this.path, { autoClose: true });
  callback(stream);
};

/**
 * Creates a {@link Sequence} from a file stream, whose elements are chunks of
 * data as the stream is read. This is an {@link AsyncSequence}, so methods such
 * as {@link Sequence#reduce} return an {@link AsyncHandle} rather than a value.
 *
 * @param {string} path A path to a file.
 * @param {string} encoding The text encoding of the file (e.g., "utf-8").
 * @return {Sequence} The streamed sequence.
 */
Lazy.readFile = function(path, encoding) {
  return new FileStreamSequence(path, encoding);
};

function HttpStreamSequence(url, encoding) {
  this.url = url;
  this.encoding = encoding;
}

HttpStreamSequence.prototype = new StreamedSequence();

HttpStreamSequence.prototype.openStream = function(callback) {
  http.get(URL.parse(this.url), callback);
};

/**
 * Creates a {@link Sequence} from an HTTP stream, whose elements are chunks of
 * data as the stream is read. This sequence works asynchronously, so
 * synchronous methods such as {@code indexOf}, {@code any}, and {@code toArray}
 * won't work.
 *
 * @param {string} url The URL for the HTTP request.
 * @return {Sequence} The streamed sequence.
 */
Lazy.makeHttpRequest = function(url) {
  return new HttpStreamSequence(url);
};

if (typeof Stream.Readable !== "undefined") {
  Lazy.Sequence.prototype.toStream = function toStream(options) {
    return new LazyStream(this, options);
  };

  Lazy.Sequence.prototype.pipe = function pipe(destination) {
    this.toStream().pipe(destination);
  };

  function LazyStream(sequence, options) {
    options = Lazy(options || {})
      .extend({ objectMode: true })
      .toObject();

    Stream.Readable.call(this, options);

    this.sequence = sequence;
    this.started  = false;

    // Find delimiter on a (parent) sequence object if set
    while (sequence) {
      if (sequence.delimiter) {
        this.delimiter = sequence.delimiter;
        break;
      }
      sequence = sequence.parent;
    }
  }

  util.inherits(LazyStream, Stream.Readable);

  LazyStream.prototype._read = function() {
    var self = this;

    if (!this.started) {
      var handle = this.sequence.each(function(line, i) {
        if (self.delimiter != null) {
          line += self.delimiter;
        }
        return self.push(line, i);
      });
      if (handle instanceof Lazy.AsyncHandle) {
        handle.onComplete(function() {
          self.push(null);
        });
      }
      this.started = true;
    }
  };
}

/*
 * Add support for `Lazy(Stream)`.
 */
Lazy.extensions || (Lazy.extensions = []);

Lazy.extensions.push(function(source) {
  if (source instanceof Stream) {
    return new StreamedSequence(source);
  }
});

module.exports = Lazy;
<commit_msg>comment silliness<commit_after>var fs     = require("fs");
var http   = require("http");
var os     = require("os");
var Stream = require("stream");
var URL    = require("url");
var util   = require("util");

// The starting point is everything that works in any environment (browser OR
// Node.js).
var Lazy = require("./lazy.js");

// Check if ES6 features are supported and, if so, include ES6-specific Lazy.js
// features.
var isHarmonySupported = require('./util.js').isHarmonySupported;
if (isHarmonySupported()) {
  require('./experimental/lazy.es6.js');
}

/**
 * @constructor
 */
function StreamedSequence(stream) {
  this.stream = stream;
}

StreamedSequence.prototype = new Lazy.StreamLikeSequence();

StreamedSequence.prototype.openStream = function(callback) {
  this.stream.resume();
  callback(this.stream);
};

/**
 * Handles every chunk of data in this sequence.
 *
 * @param {function(string):*} fn The function to call on each chunk of data as
 *     it's read from the stream. Return false from the function to stop reading
 *     the stream.
 */
StreamedSequence.prototype.each = function(fn) {
  var cancelled = false;

  var handle = new Lazy.AsyncHandle(function cancel() { cancelled = true; });

  this.openStream(function(stream) {
    if (stream.setEncoding) {
      stream.setEncoding(this.encoding || 'utf8');
    }

    var listener = function(e) {
      try {
        if (cancelled || fn(e) === false) {
          stream.removeListener("data", listener);
          handle._resolve(false);
        }
      } catch (e) {
        handle._reject(e);
      }
    };

    stream.on("data", listener);

    stream.on("end", function() {
      handle._resolve(true);
    });
  });

  return handle;
};

/**
 * Creates a {@link Sequence} of lines as they are read from a file.
 *
 * @return {Sequence} A sequence comprising the lines in the underlying file, as
 *     they are read.
 */
StreamedSequence.prototype.lines = function() {
  return this.split(os.EOL || "\n");
};

function FileStreamSequence(path, encoding) {
  this.path = path;
  this.encoding = encoding;
}

FileStreamSequence.prototype = new StreamedSequence();

FileStreamSequence.prototype.openStream = function(callback) {
  var stream = fs.createReadStream(this.path, { autoClose: true });
  callback(stream);
};

/**
 * Creates a {@link Sequence} from a file stream, whose elements are chunks of
 * data as the stream is read. This is an {@link AsyncSequence}, so methods such
 * as {@link Sequence#reduce} return an {@link AsyncHandle} rather than a value.
 *
 * @param {string} path A path to a file.
 * @param {string} encoding The text encoding of the file (e.g., "utf-8").
 * @return {Sequence} The streamed sequence.
 */
Lazy.readFile = function(path, encoding) {
  return new FileStreamSequence(path, encoding);
};

function HttpStreamSequence(url, encoding) {
  this.url = url;
  this.encoding = encoding;
}

HttpStreamSequence.prototype = new StreamedSequence();

HttpStreamSequence.prototype.openStream = function(callback) {
  http.get(URL.parse(this.url), callback);
};

/**
 * Creates a {@link Sequence} from an HTTP stream, whose elements are chunks of
 * data as the stream is read. This sequence works asynchronously, so
 * synchronous methods such as {@code indexOf}, {@code any}, and {@code toArray}
 * won't work.
 *
 * @param {string} url The URL for the HTTP request.
 * @return {Sequence} The streamed sequence.
 */
Lazy.makeHttpRequest = function(url) {
  return new HttpStreamSequence(url);
};

if (typeof Stream.Readable !== "undefined") {
  Lazy.Sequence.prototype.toStream = function toStream(options) {
    return new LazyStream(this, options);
  };

  Lazy.Sequence.prototype.pipe = function pipe(destination) {
    this.toStream().pipe(destination);
  };

  function LazyStream(sequence, options) {
    options = Lazy(options || {})
      .extend({ objectMode: true })
      .toObject();

    Stream.Readable.call(this, options);

    this.sequence = sequence;
    this.started  = false;

    // Find delimiter on a (parent) sequence object if set
    while (sequence) {
      if (sequence.delimiter) {
        this.delimiter = sequence.delimiter;
        break;
      }
      sequence = sequence.parent;
    }
  }

  util.inherits(LazyStream, Stream.Readable);

  LazyStream.prototype._read = function() {
    var self = this;

    if (!this.started) {
      var handle = this.sequence.each(function(line, i) {
        if (self.delimiter != null) {
          line += self.delimiter;
        }
        return self.push(line, i);
      });
      if (handle instanceof Lazy.AsyncHandle) {
        handle.onComplete(function() {
          self.push(null);
        });
      }
      this.started = true;
    }
  };
}

/*
 * Add support for `Lazy(Stream)`.
 */
Lazy.extensions || (Lazy.extensions = []);

Lazy.extensions.push(function(source) {
  if (source instanceof Stream) {
    return new StreamedSequence(source);
  }
});

module.exports = Lazy;
<|endoftext|>


<commit_before>/* Source: https://github.com/pieroxy/lz-string */
/* Licensed under the WTFPL */
var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToBase64:function(o){if(null==o)return"";var r=i._compress(o,6,function(o){return n.charAt(o)});switch(r.length%4){default:case 0:return r;case 1:return r+"===";case 2:return r+"==";case 3:return r+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(e){return o(n,r.charAt(e))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compressToUint8Array:function(o){for(var r=i.compress(o),n=new Uint8Array(2*r.length),e=0,t=r.length;t>e;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();
//todo the below is probably not necessary
"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);
<commit_msg>Remove LZString.js<commit_after><|endoftext|>


<commit_before>'use strict';

module.exports = {
    EVENTS: {
        CONNECTED: 'connected',
        CLOSED: 'closed',
        NOTIFICATION: 'notification'
    },
    KINDS: {
        REQUEST: 'req',
        RESPONSE: 'resp',
        NOTIFICATION: 'notification'
    },
    REQUESTS: [
        '.GetClock',
        '.ams.cm.AgentRequestConversation',
        '.ams.aam.SubscribeExConversations',
        '.ams.cm.UpdateConversationField',
        '.ams.ms.PublishEvent',
        '.ams.routing.UpdateRingState',
        '.ams.routing.SubscribeRoutingTasks',
        '.ams.routing.UpdateRoutingTaskSubscription',
        '.ams.userprofile.GetUserProfile',
        '.ams.routing.SetAgentState',
        '.ams.routing.SubscribeAgentsState',
        'ms.SubscribeMessagingEvents',
    ]
};
<commit_msg>fixes #53 adding UnsubscribeExConversations back<commit_after>'use strict';

module.exports = {
    EVENTS: {
        CONNECTED: 'connected',
        CLOSED: 'closed',
        NOTIFICATION: 'notification'
    },
    KINDS: {
        REQUEST: 'req',
        RESPONSE: 'resp',
        NOTIFICATION: 'notification'
    },
    REQUESTS: [
        '.GetClock',
        '.ams.cm.AgentRequestConversation',
        '.ams.aam.SubscribeExConversations',
        '.ams.aam.UnsubscribeExConversations',
        '.ams.cm.UpdateConversationField',
        '.ams.ms.PublishEvent',
        '.ams.routing.UpdateRingState',
        '.ams.routing.SubscribeRoutingTasks',
        '.ams.routing.UpdateRoutingTaskSubscription',
        '.ams.userprofile.GetUserProfile',
        '.ams.routing.SetAgentState',
        '.ams.routing.SubscribeAgentsState',
        'ms.SubscribeMessagingEvents',
    ]
};
<|endoftext|>


<commit_before>/**
 * Provides a multi-level Map implementation that mirrors the Map API except for exclusion of `forEach`.
 *
 * Each method takes a variable list of parameters. By not including any parameters when invoking methods of
 * `MultiLevelMap` the base Map is the target. A list of comma separated parameters will index into the backing
 * multi-level map structure.
 *
 * Errors will be thrown for methods that require a minimum number of keys including:
 * `delete(1), get(1), has(1), set(2)`. In particular set requires the actual value being set in addition to any
 * number of keys. Another caveat of `set()` is that if at any level of indexed keys a value is already set for the
 * given key index an Error will be thrown due to the pre-existing value not being a Map.
 *
 * @example
 * const map = new MultiMap();
 *
 * map.set('key1', 'key2', 1);  // creates a 2nd level Map indexed by 'key1' with value '1' indexed by 'key2'.
 * map.get('key1'); // returns the 2nd level Map.
 * map.get('key1', 'key2'); // returns '1'; 'key1' indexes into the 2nd level map with 'key2'.
 * map.has('key1', 'key2'); // is true.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
 */
export default class MultiMap
{
   /**
    * Initializes the MultiLevelMap
    */
   constructor()
   {
      this._internalMap = new Map();
      Object.freeze(this);
   }

   /**
    * The `clear()` method removes all elements from a Map object. If no keys are provided then the base map is cleared.
    * Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
    */
   clear(...keys)
   {
      // Clear the entire internal map if no keys are provided.
      if (keys.length === 0)
      {
         this._internalMap.clear();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            map.clear();
         }
      }
   }

   /**
    * The `delete()` method removes the specified element from a Map object. If one key is provided then the base map
    * is the target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete
    */
   delete(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('delete - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.delete(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];
            returnValue = map.delete(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `entries()` method returns a new Iterator object that contains the [key, value] pairs for each element in the
    * Map object in insertion order. If one key is provided then the base map is the target. Subsequent keys will
    * attempt to index into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is
    * returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   entries(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.entries();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.entries();
         }
      }

      return returnValue;
   }

   /**
    * The `get()` method returns a specified element from a Map object. If one key is provided then the base map is the
    * target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap. If no indexed Map is
    * found then `undefined` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {*}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
    */
   get(...keys)
   {
      let returnValue = undefined;

      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('get - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.get(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];
            returnValue = map.get(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `has()` method returns a boolean indicating whether an element with the specified key exists or not. If one
    * key is provided then the base map is the target. Subsequent keys will attempt to index into additional levels of
    * the MultiLevelMap. If no indexed Map is found then `false` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
    */
   has(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('has - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.has(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];

            returnValue = map.has(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `isMap()` method returns a boolean indicating whether specified sequence of keys resolves to an existing Map.
    * If one key is provided then the base map is the target. Subsequent keys will attempt to index into additional
    * levels of the MultiLevelMap. If no indexed Map is found then `false` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    */
   isMap(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         returnValue = true;
      }
      else if (keysLength > 0)
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);
         returnValue = map !== null && map instanceof Map;
      }

      return returnValue;
   }

   /**
    * The `keys()` method returns a new Iterator object that contains the keys for each element in the Map object in
    * insertion order. If one key is provided then the base map is the target. Subsequent keys will attempt to index
    * into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   keys(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.keys();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.keys();
         }
      }

      return returnValue;
   }

   /**
    * The `set()` method adds a new element with a specified key and value to a Map object. If one key is provided then
    * the base map is the target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    * New Maps are automatically created at each level for the given key parameters. The target Map that the value
    * is added to is returned.
    *
    * @param {*}  params - A variable list of keys to index subsequent levels of the MultiLevelMap with the last entry
    *                      being the value to be set / added.
    *
    * @returns {Map|undefined}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set
    */
   set(...params)
   {
      let returnValue = undefined;
      const keysLength = params.length;

      if (keysLength === 0)
      {
         throw new Error('set - no params specified; needs 2 minimum.');
      }
      else if (keysLength === 1)
      {
         throw new Error('set - only 1 param specified; needs 2 minimum.');
      }
      else if (keysLength === 2)
      {
         returnValue = this._internalMap.set(params[0], params[1]);
      }
      else if (keysLength > 2)
      {
         const mapKeys = params.slice(0, keysLength - 2);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap, true);

         if (map !== null)
         {
            const indexKey = params[keysLength - 2];
            const value = params[keysLength - 1];

            returnValue = map.set(indexKey, value);
         }
      }

      return returnValue;
   }

   /**
    * The `size()` method returns an integer representing how many entries the Map object has. If no key is provided
    * then the base map is the target. Subsequent keys will attempt to index into additional levels of the
    * MultiLevelMap. If no indexed Map is found then `0` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {number}
    */
   size(...keys)
   {
      let returnValue = 0;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         returnValue = this._internalMap.size;
      }
      else if (keysLength > 0)
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.size;
         }
      }

      return returnValue;
   }

   /**
    * The `values()` method returns a new Iterator object that contains the values for each element in the Map object in
    * insertion order. If one key is provided then the base map is the target. Subsequent keys will attempt to index
    * into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   values(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.values();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.values();
         }
      }

      return returnValue;
   }
}

// Private utility methods ------------------------------------------------------------------------------------------

const s_EMPTY_ITERATOR = { next: () => { return { done: true }; } };
Object.freeze(s_EMPTY_ITERATOR);

/**
 * Walks the given map to find the nested child map given the keys array.
 *
 * @param {Array}    keys - The keys to resolve in the given map.
 * @param {Map}      map - The map to search.
 * @param {boolean}  create - Create intermediate maps
 * @returns {Map|null}
 */
const s_FIND_CHILD_MAP = (keys, map, create = false) =>
{
   let childMap = map;

   for (let cntr = 0, length = keys.length; cntr < length; cntr++)
   {
      const key = keys[cntr];
      const nextMap = childMap.get(key);

      if (nextMap instanceof Map)
      {
         childMap = nextMap;
      }
      else if (create)
      {
         // Child map already has a value for this key.
         if (childMap.has(key))
         {
            throw new Error(
             `Could not create child Map as a value already exists for '${key}' in keys: ${JSON.stringify(keys)}.`);
         }

         const newMap = new Map();
         childMap.set(key, newMap);
         childMap = newMap;
      }
      else
      {
         return null;
      }
   }

   return childMap;
};<commit_msg> Initial commit<commit_after>/**
 * Provides a multi-level Map implementation that mirrors the Map API except for exclusion of `forEach`.
 *
 * Each method takes a variable list of parameters. By not including any parameters when invoking methods of
 * `MultiLevelMap` the base Map is the target. A list of comma separated parameters will index into the backing
 * multi-level map structure.
 *
 * Errors will be thrown for methods that require a minimum number of keys including:
 * `delete(1), get(1), has(1), set(2)`. In particular set requires the actual value being set in addition to any
 * number of keys. Another caveat of `set()` is that if at any level of indexed keys a value is already set for the
 * given key index an Error will be thrown due to the pre-existing value not being a Map.
 *
 * @example
 * const map = new MultiMap();
 *
 * map.set('key1', 'key2', 1);  // creates a 2nd level Map indexed by 'key1' with value '1' indexed by 'key2'.
 * map.get('key1'); // returns the 2nd level Map.
 * map.get('key1', 'key2'); // returns '1'; 'key1' indexes into the 2nd level map with 'key2'.
 * map.has('key1', 'key2'); // is true.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
 */
export default class MultiMap
{
   /**
    * Initializes the MultiMap
    */
   constructor()
   {
      this._internalMap = new Map();
      Object.freeze(this);
   }

   /**
    * The `clear()` method removes all elements from a Map object. If no keys are provided then the base map is cleared.
    * Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
    */
   clear(...keys)
   {
      // Clear the entire internal map if no keys are provided.
      if (keys.length === 0)
      {
         this._internalMap.clear();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            map.clear();
         }
      }
   }

   /**
    * The `delete()` method removes the specified element from a Map object. If one key is provided then the base map
    * is the target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete
    */
   delete(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('delete - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.delete(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];
            returnValue = map.delete(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `entries()` method returns a new Iterator object that contains the [key, value] pairs for each element in the
    * Map object in insertion order. If one key is provided then the base map is the target. Subsequent keys will
    * attempt to index into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is
    * returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   entries(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.entries();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.entries();
         }
      }

      return returnValue;
   }

   /**
    * The `get()` method returns a specified element from a Map object. If one key is provided then the base map is the
    * target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap. If no indexed Map is
    * found then `undefined` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {*}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
    */
   get(...keys)
   {
      let returnValue = undefined;

      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('get - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.get(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];
            returnValue = map.get(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `has()` method returns a boolean indicating whether an element with the specified key exists or not. If one
    * key is provided then the base map is the target. Subsequent keys will attempt to index into additional levels of
    * the MultiLevelMap. If no indexed Map is found then `false` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
    */
   has(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         throw new Error('has - no keys specified.');
      }
      else if (keysLength === 1)
      {
         returnValue = this._internalMap.has(keys[0]);
      }
      else if (keysLength > 1)
      {
         const mapKeys = keys.slice(0, keysLength - 1);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap);

         if (map !== null)
         {
            const indexKey = keys[keysLength - 1];

            returnValue = map.has(indexKey);
         }
      }

      return returnValue;
   }

   /**
    * The `isMap()` method returns a boolean indicating whether specified sequence of keys resolves to an existing Map.
    * If one key is provided then the base map is the target. Subsequent keys will attempt to index into additional
    * levels of the MultiLevelMap. If no indexed Map is found then `false` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {boolean}
    */
   isMap(...keys)
   {
      let returnValue = false;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         returnValue = true;
      }
      else if (keysLength > 0)
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);
         returnValue = map !== null && map instanceof Map;
      }

      return returnValue;
   }

   /**
    * The `keys()` method returns a new Iterator object that contains the keys for each element in the Map object in
    * insertion order. If one key is provided then the base map is the target. Subsequent keys will attempt to index
    * into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   keys(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.keys();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.keys();
         }
      }

      return returnValue;
   }

   /**
    * The `set()` method adds a new element with a specified key and value to a Map object. If one key is provided then
    * the base map is the target. Subsequent keys will attempt to index into additional levels of the MultiLevelMap.
    * New Maps are automatically created at each level for the given key parameters. The target Map that the value
    * is added to is returned.
    *
    * @param {*}  params - A variable list of keys to index subsequent levels of the MultiLevelMap with the last entry
    *                      being the value to be set / added.
    *
    * @returns {Map|undefined}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set
    */
   set(...params)
   {
      let returnValue = undefined;
      const keysLength = params.length;

      if (keysLength === 0)
      {
         throw new Error('set - no params specified; needs 2 minimum.');
      }
      else if (keysLength === 1)
      {
         throw new Error('set - only 1 param specified; needs 2 minimum.');
      }
      else if (keysLength === 2)
      {
         returnValue = this._internalMap.set(params[0], params[1]);
      }
      else if (keysLength > 2)
      {
         const mapKeys = params.slice(0, keysLength - 2);
         const map = s_FIND_CHILD_MAP(mapKeys, this._internalMap, true);

         if (map !== null)
         {
            const indexKey = params[keysLength - 2];
            const value = params[keysLength - 1];

            returnValue = map.set(indexKey, value);
         }
      }

      return returnValue;
   }

   /**
    * The `size()` method returns an integer representing how many entries the Map object has. If no key is provided
    * then the base map is the target. Subsequent keys will attempt to index into additional levels of the
    * MultiLevelMap. If no indexed Map is found then `0` is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {number}
    */
   size(...keys)
   {
      let returnValue = 0;
      const keysLength = keys.length;

      if (keysLength === 0)
      {
         returnValue = this._internalMap.size;
      }
      else if (keysLength > 0)
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.size;
         }
      }

      return returnValue;
   }

   /**
    * The `values()` method returns a new Iterator object that contains the values for each element in the Map object in
    * insertion order. If one key is provided then the base map is the target. Subsequent keys will attempt to index
    * into additional levels of the MultiLevelMap. If no Map is found then an empty iterator is returned.
    *
    * @param {*}  keys - A variable list of keys to index subsequent levels of the MultiLevelMap.
    * @returns {Iterator}
    *
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
    */
   values(...keys)
   {
      let returnValue = s_EMPTY_ITERATOR;

      if (keys.length === 0)
      {
         returnValue = this._internalMap.values();
      }
      else
      {
         const map = s_FIND_CHILD_MAP(keys, this._internalMap);

         if (map !== null)
         {
            returnValue = map.values();
         }
      }

      return returnValue;
   }
}

// Private utility methods ------------------------------------------------------------------------------------------

const s_EMPTY_ITERATOR = { next: () => { return { done: true }; } };
Object.freeze(s_EMPTY_ITERATOR);

/**
 * Walks the given map to find the nested child map given the keys array.
 *
 * @param {Array}    keys - The keys to resolve in the given map.
 * @param {Map}      map - The map to search.
 * @param {boolean}  create - Create intermediate maps
 * @returns {Map|null}
 */
const s_FIND_CHILD_MAP = (keys, map, create = false) =>
{
   let childMap = map;

   for (let cntr = 0, length = keys.length; cntr < length; cntr++)
   {
      const key = keys[cntr];
      const nextMap = childMap.get(key);

      if (nextMap instanceof Map)
      {
         childMap = nextMap;
      }
      else if (create)
      {
         // Child map already has a value for this key.
         if (childMap.has(key))
         {
            throw new Error(
             `Could not create child Map as a value already exists for '${key}' in keys: ${JSON.stringify(keys)}.`);
         }

         const newMap = new Map();
         childMap.set(key, newMap);
         childMap = newMap;
      }
      else
      {
         return null;
      }
   }

   return childMap;
};<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var Table = require('./Table');
var Query = require('./Query');
var Scan = require('./Scan');
var errors = require('./errors');

var debug = require('debug')('dynamoose:model');

function Model(obj) {
  this.$__.isNew = true;

  for(var key in obj) {
    this[key] = obj[key];
  }
}

Model.compile = function compile (name, schema, options, base) {
  debug('compiling NewModel %s', name);

  var table = new Table(name, schema, options, base);

  /*jshint validthis: true */
  function NewModel (obj) {
    Model.call(this, obj);
  }

  util.inherits(NewModel, Model);

  // minimize what is restricted
  NewModel.prototype.$__ = {
    table: table,
    base: base,
    name: name,
    schema: schema,
    options: options
  };
  NewModel.$__ = NewModel.prototype.$__;

  NewModel.get = function (key, options, next) {
    Model.get(NewModel, key, options, next);
  };

  NewModel.update = function (key, update, options, next) {
    Model.update(NewModel, key, update, options, next);
  };

  NewModel.delete = function (key, options, next) {
    Model.delete(NewModel, key, options, next);
  };

  NewModel.query = function (query, options, next) {
    return Model.query(NewModel, query, options, next);
  };

  NewModel.queryOne = function (query, options, next) {
    return Model.queryOne(NewModel, query, options, next);
  };

  NewModel.scan = function (filter, options, next) {
    return Model.scan(NewModel, filter, options, next);
  };

  NewModel.create = function (obj, options, next) {
    return Model.create(NewModel, obj, options, next);
  };

  table.init(function (err) {
    if(err) {
      throw err;
    }
  });

  return NewModel;
};

Model.prototype.put = function(options, next) {
  debug('put', this);
  options = options || {};
  if(options.overwrite === null || options.overwrite === undefined) {
    options.overwrite = true;
  }
  if(typeof options === 'function') {
    next = options;
    options = {};
  }
  var schema = this.$__.schema;
  var item = {
    TableName: this.$__.name,
    Item: schema.toDynamo(this)
  };
  if(options.overwrite) {
    item.Expected = {};
    item.Expected[schema.hashKey.name] = {Exists: false};
    if(schema.rangeKey) {
      item.Expected[schema.rangeKey.name] = {Exists: false};
    }
  }

  debug('putItem', item);
  this.$__.base.ddb().putItem(item, function(err) {
    next(err);
  });
};

Model.prototype.save = Model.prototype.put;

Model.create = function(NewModel, obj, options, next) {
  options = options || {};
  if(options.overwrite === null || options.overwrite === undefined) {
    options.overwrite = false;
  }

  if(typeof options === 'function') {
    next = options;
    options = {};
  }
  var model = new NewModel(obj);
  model.save(options, function (err) {
    if(err) {
      debug('Error creating new model - obj: %j', obj, err);
      return next(err);
    }
    return next(null, model);
  });
};

Model.get = function(NewModel, key, options, next) {
  debug('Get %j', key);
  if(key === null || key === undefined) {
    return next(new errors.ModelError('Key required to get item'));
  }
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: ' + schema.rangeKey.name));
  }

  var getReq = {
    TableName: NewModel.$__.name,
    Key: {}
  };

  getReq.Key[hashKeyName] = schema.hashKey.toDynamo(key[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    getReq.Key[rangeKeyName] = schema.rangeKey.toDynamo(key[rangeKeyName]);
  }

  if(options.attributes) {
    getReq.AttributesToGet = options.attributes;
  }

  if(options.consistent) {
    getReq.ConsistentRead = true;
  }

  debug('getItem', getReq);
  NewModel.$__.base.ddb().getItem(getReq, function(err, data) {
    if(err) {
      debug('Error returned by getItem', err);
      return next(err);
    }
    debug('getItem response', data);

    if(!Object.keys(data).length) {
      return next();
    }

    var model = new NewModel();
    model.$__.isNew = false;
    schema.parseDynamo(model, data.Item);

    debug('getItem parsed model', model);

    next(null, model);
  });
};

// NewModel.update({id: 123},
// {
//   $PUT: {a: 1, b: 2},
//   $DELETE: {c: 'a'},
//   $ADD: {count: 1}
// });
// NewModel.update({id: 123}, { $PUT: {a: 1, b: 2} });
// NewModel.update({id: 123}, {a: 1, b: 2} ); // Defaults to put (same as above)
Model.update = function(NewModel, key, update, options, next) {
  debug('Get %j', key);
  if(key === null || key === undefined) {
    return next(new errors.ModelError('Key required to get item'));
  }
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: ' + schema.rangeKey.name));
  }

  var updateReq = {
    TableName: NewModel.$__.name,
    Key: {},
    AttributeUpdates: {}
  };

  updateReq.Key[hashKeyName] = schema.hashKey.toDynamo(key[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    updateReq.Key[rangeKeyName] = schema.rangeKey.toDynamo(key[rangeKeyName]);
  }

  if(update.$PUT || (!update.$PUT && !update.$DELETE && !update.$ADD)) {
    var updatePUT = update;
    if(update.$PUT) {
      updatePUT = update.$PUT;
    }
    for(var putItem in updatePUT) {
      var putAttr = schema.attributes[putItem];
      if(putAttr) {
        updateReq.AttributeUpdates[putItem] = {
          Action: 'PUT',
          Value: putAttr.toDynamo(updatePUT[putItem])
        };
      }
    }
  }

  if(update.$DELETE) {
    for(var deleteItem in update.$DELETE) {
      var deleteAttr = schema.attributes[deleteItem];
      if(deleteAttr) {
        var delVal = update.$DELETE[deleteItem];
        if(delVal !== null && delVal !== undefined) {
          updateReq.AttributeUpdates[deleteItem] = {
            Action: 'DELETE',
            Value: deleteAttr.toDynamo(delVal)
          };
        } else {
          updateReq.AttributeUpdates[deleteItem] = {
            Action: 'DELETE'
          };

        }
      }
    }
  }

  if(update.$ADD) {
    for(var addItem in update.$ADD) {
      var addAttr = schema.attributes[addItem];
      if(addAttr) {
        updateReq.AttributeUpdates[addItem] = {
          Action: 'ADD',
          Value: addAttr.toDynamo(update.$ADD[addItem])
        };
      }
    }
  }

  debug('updateItem', updateReq);
  NewModel.$__.base.ddb().updateItem(updateReq, function(err, data) {
    if(err) {
      debug('Error returned by updateItem', err);
      return next(err);
    }
    debug('updateItem response', data);

    if(!Object.keys(data).length) {
      return next();
    }

    var model = new NewModel();
    model.$__.isNew = false;
    schema.parseDynamo(model, data.Item);

    debug('updateItem parsed model', model);

    next(null, model);
  });
};

Model.delete = function(NewModel, key, options, next) {

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: %s', schema.hashKey.name));
  }

  var model = new NewModel(key);
  model.delete(options, next);
};

Model.prototype.delete = function(options, next) {
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = this.$__.schema;

  var hashKeyName = schema.hashKey.name;

  if(this[hashKeyName] === null || this[hashKeyName] === undefined) {
    return next(new errors.ModelError('Hash key required: %s', hashKeyName));
  }

  if(schema.rangeKey &&
    (this[schema.rangeKey.name] === null || this[schema.rangeKey.name] === undefined)) {
    return next(new errors.ModelError('Range key required: %s', schema.hashKey.name));
  }

  var getDelete = {
    TableName: this.$__.name,
    Key: {}
  };

  getDelete.Key[hashKeyName] = schema.hashKey.toDynamo(this[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    getDelete.Key[rangeKeyName] = schema.rangeKey.toDynamo(this[rangeKeyName]);
  }

  if(options.update) {
    getDelete.ReturnValues = 'ALL_OLD';
  }

  debug('deleteItem', getDelete);
  this.$__.base.ddb().deleteItem(getDelete, function(err, data) {
    if(err) {
      debug('Error returned by deleteItem', err);
      return next(err);
    }
    debug('deleteItem response', data);

    if(options.update) {
      schema.parseDynamo(this, data.Item);
      debug('deleteItem parsed model', this);

    }

    next(null);
  }.bind(this));

};

/*

query(query, options, callback);
query(query, options)...exec(callback);
query(query, callback);
query(query)...exec(callback);

query({hash: {id: {eq : 1}}, range: {name: {beginwith: 'foxy'}}})

query({id: 1})

query('id').eq(1).where('name').begienwith('foxy')
*/
Model.query = function(NewModel, query, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  query = new Query(NewModel, query, options);

  if(next) {
    query.exec(next);
  }

  return query;
};

Model.queryOne = function(NewModel, query, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  query = new Query(NewModel, query, options);
  query.one();

  if(next) {
    query.exec(next);
  }

  return query;
};

/*

scan(filter, options, callback);
scan(filter, options)...exec(callback);
scan(filter, callback);
scan(filter)...exec(callback);

scan('id').between(a, b).and().where('name').begienwith('foxy').exec();

scan().exec(); // All records

*/
Model.scan = function(NewModel, filter, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  var scan = new Scan(NewModel, filter, options);

  if(next) {
    scan.exec(next);
  }

  return scan;
};

module.exports = Model;<commit_msg>Default to DELETE for null, undefined, or blank values<commit_after>'use strict';
var util = require('util');
var Table = require('./Table');
var Query = require('./Query');
var Scan = require('./Scan');
var errors = require('./errors');

var debug = require('debug')('dynamoose:model');

function Model(obj) {
  this.$__.isNew = true;

  for(var key in obj) {
    this[key] = obj[key];
  }
}

Model.compile = function compile (name, schema, options, base) {
  debug('compiling NewModel %s', name);

  var table = new Table(name, schema, options, base);

  /*jshint validthis: true */
  function NewModel (obj) {
    Model.call(this, obj);
  }

  util.inherits(NewModel, Model);

  // minimize what is restricted
  NewModel.prototype.$__ = {
    table: table,
    base: base,
    name: name,
    schema: schema,
    options: options
  };
  NewModel.$__ = NewModel.prototype.$__;

  NewModel.get = function (key, options, next) {
    Model.get(NewModel, key, options, next);
  };

  NewModel.update = function (key, update, options, next) {
    Model.update(NewModel, key, update, options, next);
  };

  NewModel.delete = function (key, options, next) {
    Model.delete(NewModel, key, options, next);
  };

  NewModel.query = function (query, options, next) {
    return Model.query(NewModel, query, options, next);
  };

  NewModel.queryOne = function (query, options, next) {
    return Model.queryOne(NewModel, query, options, next);
  };

  NewModel.scan = function (filter, options, next) {
    return Model.scan(NewModel, filter, options, next);
  };

  NewModel.create = function (obj, options, next) {
    return Model.create(NewModel, obj, options, next);
  };

  table.init(function (err) {
    if(err) {
      throw err;
    }
  });

  return NewModel;
};

Model.prototype.put = function(options, next) {
  debug('put', this);
  options = options || {};
  if(options.overwrite === null || options.overwrite === undefined) {
    options.overwrite = true;
  }
  if(typeof options === 'function') {
    next = options;
    options = {};
  }
  var schema = this.$__.schema;
  var item = {
    TableName: this.$__.name,
    Item: schema.toDynamo(this)
  };
  if(options.overwrite) {
    item.Expected = {};
    item.Expected[schema.hashKey.name] = {Exists: false};
    if(schema.rangeKey) {
      item.Expected[schema.rangeKey.name] = {Exists: false};
    }
  }

  debug('putItem', item);
  this.$__.base.ddb().putItem(item, function(err) {
    next(err);
  });
};

Model.prototype.save = Model.prototype.put;

Model.create = function(NewModel, obj, options, next) {
  options = options || {};
  if(options.overwrite === null || options.overwrite === undefined) {
    options.overwrite = false;
  }

  if(typeof options === 'function') {
    next = options;
    options = {};
  }
  var model = new NewModel(obj);
  model.save(options, function (err) {
    if(err) {
      debug('Error creating new model - obj: %j', obj, err);
      return next(err);
    }
    return next(null, model);
  });
};

Model.get = function(NewModel, key, options, next) {
  debug('Get %j', key);
  if(key === null || key === undefined) {
    return next(new errors.ModelError('Key required to get item'));
  }
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: ' + schema.rangeKey.name));
  }

  var getReq = {
    TableName: NewModel.$__.name,
    Key: {}
  };

  getReq.Key[hashKeyName] = schema.hashKey.toDynamo(key[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    getReq.Key[rangeKeyName] = schema.rangeKey.toDynamo(key[rangeKeyName]);
  }

  if(options.attributes) {
    getReq.AttributesToGet = options.attributes;
  }

  if(options.consistent) {
    getReq.ConsistentRead = true;
  }

  debug('getItem', getReq);
  NewModel.$__.base.ddb().getItem(getReq, function(err, data) {
    if(err) {
      debug('Error returned by getItem', err);
      return next(err);
    }
    debug('getItem response', data);

    if(!Object.keys(data).length) {
      return next();
    }

    var model = new NewModel();
    model.$__.isNew = false;
    schema.parseDynamo(model, data.Item);

    debug('getItem parsed model', model);

    next(null, model);
  });
};

// NewModel.update({id: 123},
// {
//   $PUT: {a: 1, b: 2},
//   $DELETE: {c: 'a'},
//   $ADD: {count: 1}
// });
// NewModel.update({id: 123}, { $PUT: {a: 1, b: 2} });
// NewModel.update({id: 123}, {a: 1, b: 2} ); // Defaults to put (same as above)
Model.update = function(NewModel, key, update, options, next) {
  debug('Get %j', key);
  if(key === null || key === undefined) {
    return next(new errors.ModelError('Key required to get item'));
  }
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: ' + schema.rangeKey.name));
  }

  var updateReq = {
    TableName: NewModel.$__.name,
    Key: {},
    AttributeUpdates: {}
  };

  updateReq.Key[hashKeyName] = schema.hashKey.toDynamo(key[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    updateReq.Key[rangeKeyName] = schema.rangeKey.toDynamo(key[rangeKeyName]);
  }

  if(update.$PUT || (!update.$PUT && !update.$DELETE && !update.$ADD)) {
    var updatePUT = update;
    if(update.$PUT) {
      updatePUT = update.$PUT;
    }
    for(var putItem in updatePUT) {
      var putAttr = schema.attributes[putItem];
      if(putAttr) {
        var val = updatePUT[putItem];
        if(val === null || val === undefined || val === '') {
          updateReq.AttributeUpdates[deleteItem] = {
            Action: 'DELETE'
          };
        } else {
          updateReq.AttributeUpdates[putItem] = {
            Action: 'PUT',
            Value: putAttr.toDynamo(val)
          };
        }
      }
    }
  }

  if(update.$DELETE) {
    for(var deleteItem in update.$DELETE) {
      var deleteAttr = schema.attributes[deleteItem];
      if(deleteAttr) {
        var delVal = update.$DELETE[deleteItem];
        if(delVal !== null && delVal !== undefined) {
          updateReq.AttributeUpdates[deleteItem] = {
            Action: 'DELETE',
            Value: deleteAttr.toDynamo(delVal)
          };
        } else {
          updateReq.AttributeUpdates[deleteItem] = {
            Action: 'DELETE'
          };

        }
      }
    }
  }

  if(update.$ADD) {
    for(var addItem in update.$ADD) {
      var addAttr = schema.attributes[addItem];
      if(addAttr) {
        updateReq.AttributeUpdates[addItem] = {
          Action: 'ADD',
          Value: addAttr.toDynamo(update.$ADD[addItem])
        };
      }
    }
  }

  debug('updateItem', updateReq);
  NewModel.$__.base.ddb().updateItem(updateReq, function(err, data) {
    if(err) {
      debug('Error returned by updateItem', err);
      return next(err);
    }
    debug('updateItem response', data);

    if(!Object.keys(data).length) {
      return next();
    }

    var model = new NewModel();
    model.$__.isNew = false;
    schema.parseDynamo(model, data.Item);

    debug('updateItem parsed model', model);

    next(null, model);
  });
};

Model.delete = function(NewModel, key, options, next) {

  var schema = NewModel.$__.schema;

  var hashKeyName = schema.hashKey.name;
  if(!key[hashKeyName]) {
    var keyVal = key;
    key = {};
    key[hashKeyName] = keyVal;
  }

  if(schema.rangeKey && !key[schema.rangeKey.name]) {
    return next(new errors.ModelError('Range key required: %s', schema.hashKey.name));
  }

  var model = new NewModel(key);
  model.delete(options, next);
};

Model.prototype.delete = function(options, next) {
  options = options || {};
  if(typeof options === 'function') {
    next = options;
    options = {};
  }

  var schema = this.$__.schema;

  var hashKeyName = schema.hashKey.name;

  if(this[hashKeyName] === null || this[hashKeyName] === undefined) {
    return next(new errors.ModelError('Hash key required: %s', hashKeyName));
  }

  if(schema.rangeKey &&
    (this[schema.rangeKey.name] === null || this[schema.rangeKey.name] === undefined)) {
    return next(new errors.ModelError('Range key required: %s', schema.hashKey.name));
  }

  var getDelete = {
    TableName: this.$__.name,
    Key: {}
  };

  getDelete.Key[hashKeyName] = schema.hashKey.toDynamo(this[hashKeyName]);

  if(schema.rangeKey) {
    var rangeKeyName = schema.rangeKey.name;
    getDelete.Key[rangeKeyName] = schema.rangeKey.toDynamo(this[rangeKeyName]);
  }

  if(options.update) {
    getDelete.ReturnValues = 'ALL_OLD';
  }

  debug('deleteItem', getDelete);
  this.$__.base.ddb().deleteItem(getDelete, function(err, data) {
    if(err) {
      debug('Error returned by deleteItem', err);
      return next(err);
    }
    debug('deleteItem response', data);

    if(options.update) {
      schema.parseDynamo(this, data.Item);
      debug('deleteItem parsed model', this);

    }

    next(null);
  }.bind(this));

};

/*

query(query, options, callback);
query(query, options)...exec(callback);
query(query, callback);
query(query)...exec(callback);

query({hash: {id: {eq : 1}}, range: {name: {beginwith: 'foxy'}}})

query({id: 1})

query('id').eq(1).where('name').begienwith('foxy')
*/
Model.query = function(NewModel, query, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  query = new Query(NewModel, query, options);

  if(next) {
    query.exec(next);
  }

  return query;
};

Model.queryOne = function(NewModel, query, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  query = new Query(NewModel, query, options);
  query.one();

  if(next) {
    query.exec(next);
  }

  return query;
};

/*

scan(filter, options, callback);
scan(filter, options)...exec(callback);
scan(filter, callback);
scan(filter)...exec(callback);

scan('id').between(a, b).and().where('name').begienwith('foxy').exec();

scan().exec(); // All records

*/
Model.scan = function(NewModel, filter, options, next) {
  if(typeof options === 'function') {
    next = options;
    options = null;
  }

  var scan = new Scan(NewModel, filter, options);

  if(next) {
    scan.exec(next);
  }

  return scan;
};

module.exports = Model;<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require("./MatrixClientPeg");
var TextForEvent = require('./TextForEvent');
var Avatar = require('./Avatar');
var dis = require("./dispatcher");

/*
 * Dispatches:
 * {
 *   action: "notifier_enabled",
 *   value: boolean
 * }
 */

var Notifier = {

    notificationMessageForEvent: function(ev) {
        return TextForEvent.textForEvent(ev);
    },

    _displayPopupNotification: function(ev, room) {
        if (!global.Notification || global.Notification.permission != 'granted') {
            return;
        }
        if (global.document.hasFocus()) {
            return;
        }

        var msg = this.notificationMessageForEvent(ev);
        if (!msg) return;

        var title;
        if (!ev.sender ||  room.name == ev.sender.name) {
            title = room.name;
            // notificationMessageForEvent includes sender,
            // but we already have the sender here
            if (ev.getContent().body) msg = ev.getContent().body;
        } else if (ev.getType() == 'm.room.member') {
            // context is all in the message here, we don't need
            // to display sender info
            title = room.name;
        } else if (ev.sender) {
            title = ev.sender.name + " (" + room.name + ")";
            // notificationMessageForEvent includes sender,
            // but we've just out sender in the title
            if (ev.getContent().body) msg = ev.getContent().body;
        }

        var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
            ev.sender, 40, 40, 'crop'
        ) : null;

        var notification = new global.Notification(
            title,
            {
                "body": msg,
                "icon": avatarUrl,
                "tag": "matrixreactsdk"
            }
        );

        notification.onclick = function() {
            dis.dispatch({
                action: 'view_room',
                room_id: room.roomId
            });
            global.focus();
        };

        global.setTimeout(function() {
            notification.close();
        }, 5 * 1000);
    },

    _playAudioNotification: function(ev, room) {
        var e = document.getElementById("messageAudio");
        if (e) {
            e.load();
            e.play();
        };
    },

    start: function() {
        this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
        this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
        MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
        MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
        this.toolbarHidden = false;
        this.isPrepared = false;
    },

    stop: function() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
            MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
        }
        this.isPrepared = false;
    },

    supportsDesktopNotifications: function() {
        return !!global.Notification;
    },

    havePermission: function() {
        if (!this.supportsDesktopNotifications()) return false;
        return global.Notification.permission == 'granted';
    },

    setEnabled: function(enable, callback) {
        // make sure that we persist the current setting audio_enabled setting
        // before changing anything
        if (global.localStorage) {
            if(global.localStorage.getItem('audio_notifications_enabled') == null) {
                this.setAudioEnabled(this.isEnabled());
            }
        }

        if(enable) {
            if (!this.havePermission()) {
                global.Notification.requestPermission(function() {
                    if (callback) {
                        callback();
                        dis.dispatch({
                            action: "notifier_enabled",
                            value: true
                        });
                    }
                });
            }

            if (!global.localStorage) return;
            global.localStorage.setItem('notifications_enabled', 'true');

            if (this.havePermission) {
                dis.dispatch({
                    action: "notifier_enabled",
                    value: true
                });
            }
        }
        else {
            if (!global.localStorage) return;
            global.localStorage.setItem('notifications_enabled', 'false');
            dis.dispatch({
                action: "notifier_enabled",
                value: false
            });
        }

        this.setToolbarHidden(false);
    },

    isEnabled: function() {
        if (!this.havePermission()) return false;

        if (!global.localStorage) return true;

        var enabled = global.localStorage.getItem('notifications_enabled');
        if (enabled === null) return true;
        return enabled === 'true';
    },

    setAudioEnabled: function(enable) {
        if (!global.localStorage) return;
        global.localStorage.setItem('audio_notifications_enabled',
                                    enable ? 'true' : 'false');
    },

    isAudioEnabled: function(enable) {
        if (!global.localStorage) return true;
        var enabled = global.localStorage.getItem(
            'audio_notifications_enabled');
        // default to true if the popups are enabled
        if (enabled === null) return this.isEnabled();
        return enabled === 'true';
    },

    setToolbarHidden: function(hidden) {
        this.toolbarHidden = hidden;
        dis.dispatch({
            action: "notifier_enabled",
            value: this.isEnabled()
        });
    },

    isToolbarHidden: function() {
        return this.toolbarHidden;
    },

    onSyncStateChange: function(state) {
        console.log("sync state change: " + state);
        if (state === "PREPARED" || state === "SYNCING") {
            this.isPrepared = true;
        }
    },

    onRoomTimeline: function(ev, room, toStartOfTimeline) {
        if (toStartOfTimeline) return;
        if (!this.isPrepared) return; // don't alert for any messages initially
        if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;

        var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
        if (actions && actions.notify) {
            if (this.isEnabled()) {
                this._displayPopupNotification(ev, room);
            }
            if (actions.tweaks.sound && this.isAudioEnabled()) {
                this._playAudioNotification(ev, room);
            }
        }
    }
};

if (!global.mxNotifier) {
    global.mxNotifier = Notifier;
}

module.exports = global.mxNotifier;
<commit_msg>suppress notification floods again after returning from error state<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require("./MatrixClientPeg");
var TextForEvent = require('./TextForEvent');
var Avatar = require('./Avatar');
var dis = require("./dispatcher");

/*
 * Dispatches:
 * {
 *   action: "notifier_enabled",
 *   value: boolean
 * }
 */

var Notifier = {

    notificationMessageForEvent: function(ev) {
        return TextForEvent.textForEvent(ev);
    },

    _displayPopupNotification: function(ev, room) {
        if (!global.Notification || global.Notification.permission != 'granted') {
            return;
        }
        if (global.document.hasFocus()) {
            return;
        }

        var msg = this.notificationMessageForEvent(ev);
        if (!msg) return;

        var title;
        if (!ev.sender ||  room.name == ev.sender.name) {
            title = room.name;
            // notificationMessageForEvent includes sender,
            // but we already have the sender here
            if (ev.getContent().body) msg = ev.getContent().body;
        } else if (ev.getType() == 'm.room.member') {
            // context is all in the message here, we don't need
            // to display sender info
            title = room.name;
        } else if (ev.sender) {
            title = ev.sender.name + " (" + room.name + ")";
            // notificationMessageForEvent includes sender,
            // but we've just out sender in the title
            if (ev.getContent().body) msg = ev.getContent().body;
        }

        var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
            ev.sender, 40, 40, 'crop'
        ) : null;

        var notification = new global.Notification(
            title,
            {
                "body": msg,
                "icon": avatarUrl,
                "tag": "matrixreactsdk"
            }
        );

        notification.onclick = function() {
            dis.dispatch({
                action: 'view_room',
                room_id: room.roomId
            });
            global.focus();
        };

        global.setTimeout(function() {
            notification.close();
        }, 5 * 1000);
    },

    _playAudioNotification: function(ev, room) {
        var e = document.getElementById("messageAudio");
        if (e) {
            e.load();
            e.play();
        };
    },

    start: function() {
        this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
        this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
        MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
        MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
        this.toolbarHidden = false;
        this.isPrepared = false;
    },

    stop: function() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
            MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
        }
        this.isPrepared = false;
    },

    supportsDesktopNotifications: function() {
        return !!global.Notification;
    },

    havePermission: function() {
        if (!this.supportsDesktopNotifications()) return false;
        return global.Notification.permission == 'granted';
    },

    setEnabled: function(enable, callback) {
        // make sure that we persist the current setting audio_enabled setting
        // before changing anything
        if (global.localStorage) {
            if(global.localStorage.getItem('audio_notifications_enabled') == null) {
                this.setAudioEnabled(this.isEnabled());
            }
        }

        if(enable) {
            if (!this.havePermission()) {
                global.Notification.requestPermission(function() {
                    if (callback) {
                        callback();
                        dis.dispatch({
                            action: "notifier_enabled",
                            value: true
                        });
                    }
                });
            }

            if (!global.localStorage) return;
            global.localStorage.setItem('notifications_enabled', 'true');

            if (this.havePermission) {
                dis.dispatch({
                    action: "notifier_enabled",
                    value: true
                });
            }
        }
        else {
            if (!global.localStorage) return;
            global.localStorage.setItem('notifications_enabled', 'false');
            dis.dispatch({
                action: "notifier_enabled",
                value: false
            });
        }

        this.setToolbarHidden(false);
    },

    isEnabled: function() {
        if (!this.havePermission()) return false;

        if (!global.localStorage) return true;

        var enabled = global.localStorage.getItem('notifications_enabled');
        if (enabled === null) return true;
        return enabled === 'true';
    },

    setAudioEnabled: function(enable) {
        if (!global.localStorage) return;
        global.localStorage.setItem('audio_notifications_enabled',
                                    enable ? 'true' : 'false');
    },

    isAudioEnabled: function(enable) {
        if (!global.localStorage) return true;
        var enabled = global.localStorage.getItem(
            'audio_notifications_enabled');
        // default to true if the popups are enabled
        if (enabled === null) return this.isEnabled();
        return enabled === 'true';
    },

    setToolbarHidden: function(hidden) {
        this.toolbarHidden = hidden;
        dis.dispatch({
            action: "notifier_enabled",
            value: this.isEnabled()
        });
    },

    isToolbarHidden: function() {
        return this.toolbarHidden;
    },

    onSyncStateChange: function(state) {
        console.log("sync state change: " + state);
        if (state === "PREPARED" || state === "SYNCING") {
            this.isPrepared = true;
        }
        else if (state === "STOPPED" || state === "ERROR") {
            this.isPrepared = false;
        }
    },

    onRoomTimeline: function(ev, room, toStartOfTimeline) {
        if (toStartOfTimeline) return;
        if (!this.isPrepared) return; // don't alert for any messages initially
        if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;

        var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
        if (actions && actions.notify) {
            if (this.isEnabled()) {
                this._displayPopupNotification(ev, room);
            }
            if (actions.tweaks.sound && this.isAudioEnabled()) {
                this._playAudioNotification(ev, room);
            }
        }
    }
};

if (!global.mxNotifier) {
    global.mxNotifier = Notifier;
}

module.exports = global.mxNotifier;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2012 Vladimir's Software. All rights reserved.
 *
 * @fileoverview Range of allowed characters
 * @author vladimir.darmin@gmail.com
 *
 * @created Fri, Oct 12 2012 - 16:08:21 -0700
 * @updated Fri, Nov 02 2012 - 22:10:17 -0700
 */

var Boundary = require('./Boundary');
var Interval = require('./Interval');

var Range = module.exports = function Range ( range ) {
  if ( !(this instanceof Range) ) return new Range(range);

  this.boundaries = Range.boundaries(range);

  this.intervals = Range.intervals(this.boundaries);

  this.intIntervals = Range.intIntervals(this.intervals);

  this.closure = Range.closure(this.intervals);
}

Range.prototype.contains = function contains ( value ) {
  for ( var i = 0; i < this.intervals.length; i += 1 ) {
    if ( this.intervals[i].contains(value) ) return true;
  }

  return false;
}

Range.prototype.generate = function generate ( ) {
  var index = Math.floor(this.intervals.length * Math.random());

  return this.intervals[index].generate();
}

Range.prototype.generateInt = function generateInt ( ) {
  var index = Math.floor(this.intIntervals.length * Math.random());

  return this.intIntervals[index].generateInt();
}

Range.prototype.add = function add ( range ) {
  return new Range([ ].concat(this.boundaries, range.boundaries));
}

Range.prototype.subtract = function subtract ( range ) {
  return new Range([ ].concat(this.boundaries, Range.boundaries(range, -1)));
}

Range.prototype.invert = function invert ( range ) {
  var range = range || this.closure;

  return this.subtract(range).add(range.subtract(this));
}

Range.prototype.intersect = function intersect ( range ) {
  return this.invert().add(range.invert()).invert();
}

Range.prototype.crop = function crop ( range ) {
  return this.subtract(range.inverse());
}

Range.boundaries = function boundaries ( range, inverted ) {
  var bounds = [ ];

  if ( range instanceof Range ) {
    for ( var i = 0; i < range.intervals.length; i += 1 ) {
      var interval = range.intervals[i];

      bounds.push(new Boundary(interval.min, inverted).right(interval.hasMin));
      bounds.push(new Boundary(interval.max, inverted).left(interval.hasMax));
    }
  }
  else {
    for ( var i = 0; i < range.length; i += 1 ) {
      var interval = range[i];

      if ( interval instanceof Array ) {
        if ( interval[0] > interval[1] ) continue;

        bounds.push(new Boundary(+interval[0], inverted).right(true));
        bounds.push(new Boundary(+interval[1], inverted).left(true));
      }
      else if ( interval instanceof Boundary ) bounds.push(interval);
      else {
        bounds.push(new Boundary(+interval, inverted).right(true));
        bounds.push(new Boundary(+interval, inverted).left(true));
      }
    }
  }

  return bounds;
}

Range.intBoundaries = function intBoundaries ( intervals ) {
  var bounds = [ ];

  for ( var i = 0; i < intervals.length; i += 1 ) {
    var interval = intervals[i], min = interval.minInt, max = interval.maxInt;

    if ( min != NaN ) bounds.push(new Boundary(min));
    if ( max != NaN && max != min ) bounds.push(new Boundary(max));
  }

  return bounds;
}

Range.intervals = function intervals ( bounds ) {
  var range = [ ], pos = 0, neg = 0;

  bounds.sort(Boundary.compare);

  for ( var i = 0; i < bounds.length; i += 1 ) {
    var border = bounds[i], pr = border.isInverted, st = border.state;

    if ( pos == 0 && neg == 0 && !pr && st > 0 ) range.push(border);

    if ( pos >= 1 && neg == 1 && pr && st < 0 ) range.push(border.next());

    if ( pos == 1 && neg == 0 && !pr && st < 0 ) range.push(border);

    if ( pos >= 1 && neg == 0 && pr < 0 && st > 0 ) range.push(border.next());

    current[pr ? 'positive' : 'negative'] += st > 0 ? 1 : -1;
  }

  var result = [ ];

  while ( range.length > 1 ) {
    var a = range.shift(), b = range.shift();

    while ( range.length > 1 && b.isNeighbourOf(range[0]) ) {
      b = range.shift();
      b = range.shift();
    }

    result.push(a.value, b.value, a.isContained, b.isContained);
  }
}

Range.intIntervals = function intIntervals ( intervals ) {
  var result;

  for ( var i = 0; i < intervals.length; i += 1 ) {
    var interval = intervals[i];

    if ( interval.count > 0 ) {
      var a = interval.minInt, b = interval.maxInt;

      while ( interval = intervals[i + 1] && interval.minInt == b + 1 ) {
        b = intervals[i += 1].maxInt;
      }

      result.push(new Interval(a, b));
    }
  }

  return result;
}

Range.closure = function closure ( intervals ) {
  if ( !intervals || intervals.length == 0 ) return null;

  var a = intervals[0], b = intervals.slice(-1)[0];

  return new Interval(a.min, b.max, a.hasMin, b.hasMax);
}

Range.unite = function unite ( ranges ) {
  var bounds = [ ];

  for ( var i = 0; i < ranges.length; i += 1 ) {
    bounds.push(ranges[i].boundaries);
  }

  return new Range(Array.prototype.concat.apply([ ], bounds));
}

Range.intersect = function intersect ( ranges ) {
  var inverses = [ ];

  for ( var i = 0; i < ranges.length; i += 1 ) {
    inverses.push(ranges[i].invert());
  }

  return Range.unite(inverses).invert();
}

Range.prototype.toString = function toString ( ) {
  return [ '{', '}' ].join(this.intervals.join(', '));
}
<commit_msg>actually added int boundaries to range instances<commit_after>/**
 * Copyright (c) 2012 Vladimir's Software. All rights reserved.
 *
 * @fileoverview Range of allowed characters
 * @author vladimir.darmin@gmail.com
 *
 * @created Fri, Oct 12 2012 - 16:08:21 -0700
 * @updated Fri, Nov 02 2012 - 22:10:17 -0700
 */

var Boundary = require('./Boundary');
var Interval = require('./Interval');

var Range = module.exports = function Range ( range ) {
  if ( !(this instanceof Range) ) return new Range(range);

  this.boundaries = Range.boundaries(range);
  this.intervals = Range.intervals(this.boundaries);

  this.intBoundaries = Range.intBoundaries(this.intervals);
  this.intIntervals = Range.intIntervals(this.intervals);

  this.closure = Range.closure(this.intervals);
}

Range.prototype.contains = function contains ( value ) {
  for ( var i = 0; i < this.intervals.length; i += 1 ) {
    if ( this.intervals[i].contains(value) ) return true;
  }

  return false;
}

Range.prototype.generate = function generate ( ) {
  var index = Math.floor(this.intervals.length * Math.random());

  return this.intervals[index].generate();
}

Range.prototype.generateInt = function generateInt ( ) {
  var index = Math.floor(this.intIntervals.length * Math.random());

  return this.intIntervals[index].generateInt();
}

Range.prototype.add = function add ( range ) {
  return new Range([ ].concat(this.boundaries, range.boundaries));
}

Range.prototype.subtract = function subtract ( range ) {
  return new Range([ ].concat(this.boundaries, Range.boundaries(range, -1)));
}

Range.prototype.invert = function invert ( range ) {
  var range = range || this.closure;

  return this.subtract(range).add(range.subtract(this));
}

Range.prototype.intersect = function intersect ( range ) {
  return this.invert().add(range.invert()).invert();
}

Range.prototype.crop = function crop ( range ) {
  return this.subtract(range.inverse());
}

Range.boundaries = function boundaries ( range, inverted ) {
  var bounds = [ ];

  if ( range instanceof Range ) {
    for ( var i = 0; i < range.intervals.length; i += 1 ) {
      var interval = range.intervals[i];

      bounds.push(new Boundary(interval.min, inverted).right(interval.hasMin));
      bounds.push(new Boundary(interval.max, inverted).left(interval.hasMax));
    }
  }
  else {
    for ( var i = 0; i < range.length; i += 1 ) {
      var interval = range[i];

      if ( interval instanceof Array ) {
        if ( interval[0] > interval[1] ) continue;

        bounds.push(new Boundary(+interval[0], inverted).right(true));
        bounds.push(new Boundary(+interval[1], inverted).left(true));
      }
      else if ( interval instanceof Boundary ) bounds.push(interval);
      else {
        bounds.push(new Boundary(+interval, inverted).right(true));
        bounds.push(new Boundary(+interval, inverted).left(true));
      }
    }
  }

  return bounds;
}

Range.intBoundaries = function intBoundaries ( intervals ) {
  var bounds = [ ];

  for ( var i = 0; i < intervals.length; i += 1 ) {
    var interval = intervals[i], min = interval.minInt, max = interval.maxInt;

    if ( min != NaN ) bounds.push(new Boundary(min));
    if ( max != NaN && max != min ) bounds.push(new Boundary(max));
  }

  return bounds;
}

Range.intervals = function intervals ( bounds ) {
  var range = [ ], pos = 0, neg = 0;

  bounds.sort(Boundary.compare);

  for ( var i = 0; i < bounds.length; i += 1 ) {
    var border = bounds[i], pr = border.isInverted, st = border.state;

    if ( pos == 0 && neg == 0 && !pr && st > 0 ) range.push(border);

    if ( pos >= 1 && neg == 1 && pr && st < 0 ) range.push(border.next());

    if ( pos == 1 && neg == 0 && !pr && st < 0 ) range.push(border);

    if ( pos >= 1 && neg == 0 && pr < 0 && st > 0 ) range.push(border.next());

    current[pr ? 'positive' : 'negative'] += st > 0 ? 1 : -1;
  }

  var result = [ ];

  while ( range.length > 1 ) {
    var a = range.shift(), b = range.shift();

    while ( range.length > 1 && b.isNeighbourOf(range[0]) ) {
      b = range.shift();
      b = range.shift();
    }

    result.push(a.value, b.value, a.isContained, b.isContained);
  }
}

Range.intIntervals = function intIntervals ( intervals ) {
  var result;

  for ( var i = 0; i < intervals.length; i += 1 ) {
    var interval = intervals[i];

    if ( interval.count > 0 ) {
      var a = interval.minInt, b = interval.maxInt;

      while ( interval = intervals[i + 1] && interval.minInt == b + 1 ) {
        b = intervals[i += 1].maxInt;
      }

      result.push(new Interval(a, b));
    }
  }

  return result;
}

Range.closure = function closure ( intervals ) {
  if ( !intervals || intervals.length == 0 ) return null;

  var a = intervals[0], b = intervals.slice(-1)[0];

  return new Interval(a.min, b.max, a.hasMin, b.hasMax);
}

Range.unite = function unite ( ranges ) {
  var bounds = [ ];

  for ( var i = 0; i < ranges.length; i += 1 ) {
    bounds.push(ranges[i].boundaries);
  }

  return new Range(Array.prototype.concat.apply([ ], bounds));
}

Range.intersect = function intersect ( ranges ) {
  var inverses = [ ];

  for ( var i = 0; i < ranges.length; i += 1 ) {
    inverses.push(ranges[i].invert());
  }

  return Range.unite(inverses).invert();
}

Range.prototype.toString = function toString ( ) {
  return [ '{', '}' ].join(this.intervals.join(', '));
}
<|endoftext|>