<commit_before>/*
filereader.js - a lightweight wrapper for common FileReader usage.
Open source code under MIT license: http://www.opensource.org/licenses/mit-license.php
Author: Brian Grinstead

See http://github.com/bgrins/filereader.js for documentation
*/

(function(global) {

    var FileReader = global.FileReader;
    var FileReaderJS = global.FileReaderJS = {
        enabled: false,
        setupInput: setupInput,
        setupDrop: setupDrop,
        setupClipboard: setupClipboard,

        opts: {
            dragClass: "drag",
            accept: false,
            readAsMap: {
                'image/*': 'DataURL',
                'text/*' : 'Text'
            },
            readAsDefault: 'BinaryString',
            on: {
                loadstart: noop,
                progress: noop,
                load: noop,
                abort: noop,
                error: noop,
                loadend: noop,
                skip: noop,
                groupstart: noop,
                groupend: noop,
                beforestart: noop
            }
        },
        output: []
    };
    var fileReaderEvents = ['loadstart', 'progress', 'load', 'abort', 'error', 'loadend'];

    // setup jQuery plugin if available
    if (typeof(jQuery) !== "undefined") {
        jQuery.fn.fileReaderJS = function(opts) {
            return this.each(function() {
                $(this).is("input") ? setupInput(this, opts) : setupDrop(this, opts);
            });
        };

        jQuery.fn.fileClipboard = function(opts) {
            return this.each(function() {
                setupClipboard(this, opts);
            });
        };
    }

    if (!FileReader) {
        // Not all browsers support the FileReader interface.  Return with the enabled bit = false
        return;
    }

    function setupClipboard(element, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
        element.addEventListener("paste", onpaste, false);

        function onpaste(ev) {
            var files = [];
            var clipboardData = ev.clipboardData || {};
            var items = clipboardData.items || [];

            for (var i = 0; i < items.length; i++) {
                var file = items[i].getAsFile();
                if (file) {
                    var matches = new RegExp("image/\(.*\)").exec(file.type);
                    if (matches) {
                        var extension = matches[1];
                        file.name = "clipboard" + i + "." + extension;
                        files.push(file);
                    }
                }
            }

            if (files.length) {
                processFileList(files, instanceOptions);
                ev.preventDefault();
                ev.stopPropagation();
            }
        }
    };

    // setupInput: bind the 'change' event to an input[type=file]
    function setupInput(input, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);

        input.addEventListener("change", inputChange, false);
        function inputChange(e) {
            processFileList(e.target.files, instanceOptions);
        }
    }

    // setupDrop: bind the 'drop' event for a DOM element
    function setupDrop(dropbox, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts),
            dragClass = instanceOptions.dragClass;

        dropbox.addEventListener("dragenter", dragenter, false);
        dropbox.addEventListener("dragleave", dragleave, false);
        dropbox.addEventListener("dragover", dragover, false);
        dropbox.addEventListener("drop", drop, false);

        function drop(e) {
            e.stopPropagation();
            e.preventDefault();
            if (dragClass) {
                removeClass(dropbox, dragClass);
            }
            processFileList(e.dataTransfer.files, instanceOptions);
        }
        function dragenter(e) {
            if (dragClass) {
                addClass(dropbox, dragClass);
            }
            e.stopPropagation();
            e.preventDefault();
        }
        function dragleave(e) {
            if (dragClass) {
                removeClass(dropbox, dragClass);
            }
        }
        function dragover(e) {
            if (dragClass) {
                addClass(dropbox, dragClass);
            }
            e.stopPropagation();
            e.preventDefault();
        }
    }

    // setupCustomFileProperties: modify the file object with extra properties
    function setupCustomFileProperties(files, groupID) {
        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            file.extra = {
                nameNoExtension: file.name.substring(0, file.name.lastIndexOf('.')),
                extension: file.name.substring(file.name.lastIndexOf('.') + 1),
                fileID: i,
                uniqueID: getUniqueID(),
                groupID: groupID,
                prettySize: prettySize(file.size)
            };
        }
    }

    // getReadAsMethod: return method name for 'readAs*' - http://dev.w3.org/2006/webapi/FileAPI/#reading-a-file
    function getReadAsMethod(type, readAsMap, readAsDefault) {
        for (var r in readAsMap) {
            if (type.match(new RegExp(r))) {
                return 'readAs' + readAsMap[r];
            }
        }
        return 'readAs' + readAsDefault;
    }

    // processFileList: read the files with FileReader, send off custom events.
    function processFileList(files, opts) {

        var group = {
            groupID: getGroupID(),
            files: files,
            started: new Date()
        };

        FileReaderJS.output.push(group);

        var filesLeft = files.length;
        var groupFileDone =	function() {
            if (--filesLeft == 0) {
                group.ended = new Date();
                opts.on.groupend(group);
            }
        };

        setupCustomFileProperties(files, group.groupID);

        opts.on.groupstart(group);

        // No files in group - call groupend immediately
        if (!files.length) {
            group.ended = new Date();
            opts.on.groupend(group);
        }

        for (var i = 0; i < files.length; i++) {

            var file = files[i];
            if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
                opts.on.skip(file);
                groupFileDone();
                continue;
            }

            if (opts.on.beforestart(file) === false) {
                opts.on.skip(file);
                groupFileDone();
                continue;
            }

            var reader = new FileReader();

            for (var j = 0; j < fileReaderEvents.length; j++) {
                var eventName = fileReaderEvents[j];

                // bind to a self executing function that returns a function that
                // passes the file along to the callback, so we have access to the file
                // from the ProgressEvent.  Need to keep scope for current file and eventName
                reader['on' + eventName] = (function(eventName, file) {
                    return function(e) {
                        opts.on[eventName](e, file);
                        if (eventName == 'loadend') {
                            groupFileDone();
                        }
                    };
                })(eventName, file);
            }

            reader[getReadAsMethod(file.type, opts.readAsMap, opts.readAsDefault)](file);
        }
    }

    // noop: do nothing
    function noop() {

    }

    // extend: used to make deep copies of options object
    function extend(destination, source) {
        for (var property in source) {
            if (source[property] && source[property].constructor &&
                source[property].constructor === Object) {
                destination[property] = destination[property] || {};
                arguments.callee(destination[property], source[property]);
            }
            else {
                destination[property] = source[property];
            }
        }
        return destination;
    }

    // hasClass: does an element have the css class?
    function hasClass(el, name) {
        return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className);
    }

    // addClass: add the css class for the element.
    function addClass(el, name) {
        if (!hasClass(el, name)) {
          el.className = el.className ? [el.className, name].join(' ') : name;
        }
    }

    // removeClass: remove the css class from the element.
    function removeClass(el, name) {
        if (hasClass(el, name)) {
          var c = el.className;
          el.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        }
    }

    // prettySize: convert bytes to a more readable string.
    function prettySize(bytes) {
        var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
        var e = Math.floor(Math.log(bytes)/Math.log(1024));
        return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
    }

    // getGroupID: generate a unique int ID for groups.
    var getGroupID = (function(id) {
        return function() {
            return id++;
        }
    })(0);

    // getUniqueID: generate a unique int ID for files
    var getUniqueID = (function(id) {
        return function() {
            return id++;
        }
    })(0);

    // The interface is supported, bind the FileReaderJS callbacks
    FileReaderJS.enabled = true;

})(this);<commit_msg>whitespace for Windows Git<commit_after>/*
filereader.js - a lightweight wrapper for common FileReader usage.
Open source code under MIT license: http://www.opensource.org/licenses/mit-license.php
Author: Brian Grinstead

See http://github.com/bgrins/filereader.js for documentation
*/

(function(global) {

    var FileReader = global.FileReader;
    var FileReaderJS = global.FileReaderJS = {
        enabled: false,
        setupInput: setupInput,
        setupDrop: setupDrop,
        setupClipboard: setupClipboard,

        opts: {
            dragClass: "drag",
            accept: false,
            readAsMap: {
                'image/*': 'DataURL',
                'text/*' : 'Text'
            },
            readAsDefault: 'BinaryString',
            on: {
                loadstart: noop,
                progress: noop,
                load: noop,
                abort: noop,
                error: noop,
                loadend: noop,
                skip: noop,
                groupstart: noop,
                groupend: noop,
                beforestart: noop
            }
        },
        output: []
    };
    var fileReaderEvents = ['loadstart', 'progress', 'load', 'abort', 'error', 'loadend'];

    // setup jQuery plugin if available
    if (typeof(jQuery) !== "undefined") {
        jQuery.fn.fileReaderJS = function(opts) {
            return this.each(function() {
                $(this).is("input") ? setupInput(this, opts) : setupDrop(this, opts);
            });
        };

        jQuery.fn.fileClipboard = function(opts) {
            return this.each(function() {
                setupClipboard(this, opts);
            });
        };
    }

    if (!FileReader) {
        // Not all browsers support the FileReader interface.  Return with the enabled bit = false
        return;
    }

    function setupClipboard(element, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
        element.addEventListener("paste", onpaste, false);

        function onpaste(ev) {
            var files = [];
            var clipboardData = ev.clipboardData || {};
            var items = clipboardData.items || [];

            for (var i = 0; i < items.length; i++) {
                var file = items[i].getAsFile();
                if (file) {
                    var matches = new RegExp("image/\(.*\)").exec(file.type);
                    if (matches) {
                        var extension = matches[1];
                        file.name = "clipboard" + i + "." + extension;
                        files.push(file);
                    }
                }
            }

            if (files.length) {
                processFileList(files, instanceOptions);
                ev.preventDefault();
                ev.stopPropagation();
            }
        }
    };

    // setupInput: bind the 'change' event to an input[type=file]
    function setupInput(input, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);

        input.addEventListener("change", inputChange, false);
        function inputChange(e) {
            processFileList(e.target.files, instanceOptions);
        }
    }

    // setupDrop: bind the 'drop' event for a DOM element
    function setupDrop(dropbox, opts) {
        if (!FileReaderJS.enabled) {
            return;
        }

        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts),
            dragClass = instanceOptions.dragClass;

        dropbox.addEventListener("dragenter", dragenter, false);
        dropbox.addEventListener("dragleave", dragleave, false);
        dropbox.addEventListener("dragover", dragover, false);
        dropbox.addEventListener("drop", drop, false);

        function drop(e) {
            e.stopPropagation();
            e.preventDefault();
            if (dragClass) {
                removeClass(dropbox, dragClass);
            }
            processFileList(e.dataTransfer.files, instanceOptions);
        }
        function dragenter(e) {
            if (dragClass) {
                addClass(dropbox, dragClass);
            }
            e.stopPropagation();
            e.preventDefault();
        }
        function dragleave(e) {
            if (dragClass) {
                removeClass(dropbox, dragClass);
            }
        }
        function dragover(e) {
            if (dragClass) {
                addClass(dropbox, dragClass);
            }
            e.stopPropagation();
            e.preventDefault();
        }
    }

    // setupCustomFileProperties: modify the file object with extra properties
    function setupCustomFileProperties(files, groupID) {
        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            file.extra = {
                nameNoExtension: file.name.substring(0, file.name.lastIndexOf('.')),
                extension: file.name.substring(file.name.lastIndexOf('.') + 1),
                fileID: i,
                uniqueID: getUniqueID(),
                groupID: groupID,
                prettySize: prettySize(file.size)
            };
        }
    }

    // getReadAsMethod: return method name for 'readAs*' - http://dev.w3.org/2006/webapi/FileAPI/#reading-a-file
    function getReadAsMethod(type, readAsMap, readAsDefault) {
        for (var r in readAsMap) {
            if (type.match(new RegExp(r))) {
                return 'readAs' + readAsMap[r];
            }
        }
        return 'readAs' + readAsDefault;
    }

    // processFileList: read the files with FileReader, send off custom events.
    function processFileList(files, opts) {

        var group = {
            groupID: getGroupID(),
            files: files,
            started: new Date()
        };

        FileReaderJS.output.push(group);

        var filesLeft = files.length;
        var groupFileDone =	function() {
            if (--filesLeft == 0) {
                group.ended = new Date();
                opts.on.groupend(group);
            }
        };

        setupCustomFileProperties(files, group.groupID);

        opts.on.groupstart(group);

        // No files in group - call groupend immediately
        if (!files.length) {
            group.ended = new Date();
            opts.on.groupend(group);
        }

        for (var i = 0; i < files.length; i++) {

            var file = files[i];
            if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
                opts.on.skip(file);
                groupFileDone();
                continue;
            }

            if (opts.on.beforestart(file) === false) {
                opts.on.skip(file);
                groupFileDone();
                continue;
            }

            var reader = new FileReader();

            for (var j = 0; j < fileReaderEvents.length; j++) {
                var eventName = fileReaderEvents[j];

                // bind to a self executing function that returns a function that
                // passes the file along to the callback, so we have access to the file
                // from the ProgressEvent.  Need to keep scope for current file and eventName
                reader['on' + eventName] = (function(eventName, file) {
                    return function(e) {
                        opts.on[eventName](e, file);
                        if (eventName == 'loadend') {
                            groupFileDone();
                        }
                    };
                })(eventName, file);
            }

            reader[getReadAsMethod(file.type, opts.readAsMap, opts.readAsDefault)](file);
        }
    }

    // noop: do nothing
    function noop() {

    }

    // extend: used to make deep copies of options object
    function extend(destination, source) {
        for (var property in source) {
            if (source[property] && source[property].constructor &&
                source[property].constructor === Object) {
                destination[property] = destination[property] || {};
                arguments.callee(destination[property], source[property]);
            }
            else {
                destination[property] = source[property];
            }
        }
        return destination;
    }

    // hasClass: does an element have the css class?
    function hasClass(el, name) {
        return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className);
    }

    // addClass: add the css class for the element.
    function addClass(el, name) {
        if (!hasClass(el, name)) {
          el.className = el.className ? [el.className, name].join(' ') : name;
        }
    }

    // removeClass: remove the css class from the element.
    function removeClass(el, name) {
        if (hasClass(el, name)) {
          var c = el.className;
          el.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        }
    }

    // prettySize: convert bytes to a more readable string.
    function prettySize(bytes) {
        var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
        var e = Math.floor(Math.log(bytes)/Math.log(1024));
        return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
    }

    // getGroupID: generate a unique int ID for groups.
    var getGroupID = (function(id) {
        return function() {
            return id++;
        }
    })(0);

    // getUniqueID: generate a unique int ID for files
    var getUniqueID = (function(id) {
        return function() {
            return id++;
        }
    })(0);

    // The interface is supported, bind the FileReaderJS callbacks
    FileReaderJS.enabled = true;

})(this);<|endoftext|>


<commit_before>var appCacheIframe;

function hasSW() {
  return 'serviceWorker' in navigator &&
    // This is how I block Chrome 40 and detect Chrome 41, because first has
    // bugs with history.pustState and/or hashchange
    (window.fetch || 'imageRendering' in document.documentElement.style) &&
    (window.location.protocol === 'https:' || window.location.hostname === 'localhost')
}

function install(options) {
  options || (options = {});

  <% if (typeof ServiceWorker !== 'undefined') { %>
    if (hasSW()) {
      var registration = navigator.serviceWorker
        .register(
          <%- JSON.stringify(ServiceWorker.location) %>
          <% if (ServiceWorker.scope) { %>
            , { scope: <%- JSON.stringify(ServiceWorker.scope) %> }
          <% } %>
        );

      <% if (ServiceWorker.events) { %>
        var handleUpdating = function(registration) {
          var sw = registration.installing || registration.waiting;
          var ignoreInstalling;
          var ignoreWaiting;

          // No SW or already handled
          if (!sw || sw.onstatechange) return;

          var stateChangeHandler;

          // Already has SW
          if (registration.active) {
            onUpdateStateChange();
            stateChangeHandler = onUpdateStateChange;
          } else {
            onInstallStateChange();
            stateChangeHandler = onInstallStateChange;
          }

          ignoreInstalling = true;
          if (registration.waiting) {
            ignoreWaiting = true;
          }

          sw.onstatechange = stateChangeHandler;

          function onUpdateStateChange() {
            switch (sw.state) {
              case 'redundant': {
                sendEvent('onUpdateFailed');
                sw.onstatechange = null;
              } break;

              case 'installing': {
                if (!ignoreInstalling) {
                  sendEvent('onUpdating');
                }
              } break;

              case 'installed': {
                if (!ignoreWaiting) {
                  sendEvent('onUpdateReady');
                }
              } break;

              case 'activated': {
                sendEvent('onUpdated');
                sw.onstatechange = null;
              } break;
            }
          }

          function onInstallStateChange() {
            switch (sw.state) {
              case 'redundant': {
                // Failed to install, ignore
                sw.onstatechange = null;
              } break;

              case 'installing': {
                // Installing, ignore
              } break;

              case 'installed': {
                // Installed, wait activation
              } break;

              case 'activated': {
                sendEvent('onInstalled');
                sw.onstatechange = null;
              } break;
            }
          }
        };

        var sendEvent = function(event) {
          if (typeof options[event] === 'function') {
            options[event]({
              source: 'ServiceWorker'
            });
          }
        };

        registration.then(function(reg) {
          // WTF no reg?
          if (!reg) return;

          // Installed but Shift-Reloaded (page is not controller by SW),
          // update might be ready at this point (more than one tab opened).
          // Anyway, if page is hard-reloaded, then it probably already have latest version
          // but it's not controlled by SW yet. Applying update will claim this page
          // to be controlled by SW. Maybe set flag to not reload it?
          // if (!navigator.serviceWorker.controller) return;

          handleUpdating(reg);
          reg.onupdatefound = function() {
            handleUpdating(reg);
          };
        }).catch(function(err) {
          sendEvent('onError');
          return Promise.reject(err);
        });
      <% } %>

      return;
    }
  <% } %>

  <% if (typeof AppCache !== 'undefined' && AppCache.disableInstall !== true) { %>
    if (window.applicationCache) {
      var directory = <%- JSON.stringify(AppCache.location) %>;
      var name = <%- JSON.stringify(AppCache.name) %>;

      var doLoad = function() {
        var page = directory + name + '.html';
        var iframe = document.createElement('iframe');

        <% if (AppCache.events) { %>
          window.addEventListener('message', function(e) {
            if (e.source !== iframe.contentWindow) return;

            var match = (e.data + '').match(/__offline-plugin_AppCacheEvent:(\w+)/);
            var event = match[1];

            if (typeof options[event] === 'function') {
              options[event]({
                source: 'AppCache'
              });
            }
          });
        <% } %>

        iframe.src = page;
        iframe.style.display = 'none';

        appCacheIframe = iframe;
        document.body.appendChild(iframe);
      };

      if (document.readyState === 'complete') {
        setTimeout(doLoad);
      } else {
        window.addEventListener('load', doLoad);
      }

      return;
    }
  <% } %>
}

function applyUpdate(callback, errback) {
  <% if (typeof ServiceWorker !== 'undefined' && ServiceWorker.events) { %>
    if (hasSW()) {
      navigator.serviceWorker.getRegistration().then(function(registration) {
        if (!registration || !registration.waiting) {
          errback && errback();
          return;
        }

        registration.waiting.postMessage({
          action: 'skipWaiting'
        });

        callback && callback();
      });

      return;
    }
  <% } %>

  <% if (typeof AppCache !== 'undefined' && AppCache.events && AppCache.disableInstall !== true) { %>
    if (appCacheIframe) {
      try {
        appCacheIframe.contentWindow.__applyUpdate();
        callback && setTimeout(callback);
      } catch (e) {
        errback && setTimeout(errback);
      }
    }
  <% } %>
}

function updateOfflineService() {
  if (navigator.serviceWorker) {
    navigator.serviceWorker.ready.then((registration) => {
      if(!registration) {
        return;
      }
      return registration.update();
    });
  } else if (window.applicationCache) {
    applicationCache.update();
  }
}

<% if (typeof autoUpdate !== 'undefined' && autoUpdate === true) { %>
  setInterval(() => updateOfflineService(), <%- JSON.stringify(autoUpdateInterval) %>);
<% } %>

exports.install = install;
exports.applyUpdate = applyUpdate;
<commit_msg>Making the template more browser friendly.<commit_after>var appCacheIframe;

function hasSW() {
  return 'serviceWorker' in navigator &&
    // This is how I block Chrome 40 and detect Chrome 41, because first has
    // bugs with history.pustState and/or hashchange
    (window.fetch || 'imageRendering' in document.documentElement.style) &&
    (window.location.protocol === 'https:' || window.location.hostname === 'localhost')
}

function install(options) {
  options || (options = {});

  <% if (typeof ServiceWorker !== 'undefined') { %>
    if (hasSW()) {
      var registration = navigator.serviceWorker
        .register(
          <%- JSON.stringify(ServiceWorker.location) %>
          <% if (ServiceWorker.scope) { %>
            , { scope: <%- JSON.stringify(ServiceWorker.scope) %> }
          <% } %>
        );

      <% if (ServiceWorker.events) { %>
        var handleUpdating = function(registration) {
          var sw = registration.installing || registration.waiting;
          var ignoreInstalling;
          var ignoreWaiting;

          // No SW or already handled
          if (!sw || sw.onstatechange) return;

          var stateChangeHandler;

          // Already has SW
          if (registration.active) {
            onUpdateStateChange();
            stateChangeHandler = onUpdateStateChange;
          } else {
            onInstallStateChange();
            stateChangeHandler = onInstallStateChange;
          }

          ignoreInstalling = true;
          if (registration.waiting) {
            ignoreWaiting = true;
          }

          sw.onstatechange = stateChangeHandler;

          function onUpdateStateChange() {
            switch (sw.state) {
              case 'redundant': {
                sendEvent('onUpdateFailed');
                sw.onstatechange = null;
              } break;

              case 'installing': {
                if (!ignoreInstalling) {
                  sendEvent('onUpdating');
                }
              } break;

              case 'installed': {
                if (!ignoreWaiting) {
                  sendEvent('onUpdateReady');
                }
              } break;

              case 'activated': {
                sendEvent('onUpdated');
                sw.onstatechange = null;
              } break;
            }
          }

          function onInstallStateChange() {
            switch (sw.state) {
              case 'redundant': {
                // Failed to install, ignore
                sw.onstatechange = null;
              } break;

              case 'installing': {
                // Installing, ignore
              } break;

              case 'installed': {
                // Installed, wait activation
              } break;

              case 'activated': {
                sendEvent('onInstalled');
                sw.onstatechange = null;
              } break;
            }
          }
        };

        var sendEvent = function(event) {
          if (typeof options[event] === 'function') {
            options[event]({
              source: 'ServiceWorker'
            });
          }
        };

        registration.then(function(reg) {
          // WTF no reg?
          if (!reg) return;

          // Installed but Shift-Reloaded (page is not controller by SW),
          // update might be ready at this point (more than one tab opened).
          // Anyway, if page is hard-reloaded, then it probably already have latest version
          // but it's not controlled by SW yet. Applying update will claim this page
          // to be controlled by SW. Maybe set flag to not reload it?
          // if (!navigator.serviceWorker.controller) return;

          handleUpdating(reg);
          reg.onupdatefound = function() {
            handleUpdating(reg);
          };
        }).catch(function(err) {
          sendEvent('onError');
          return Promise.reject(err);
        });
      <% } %>

      return;
    }
  <% } %>

  <% if (typeof AppCache !== 'undefined' && AppCache.disableInstall !== true) { %>
    if (window.applicationCache) {
      var directory = <%- JSON.stringify(AppCache.location) %>;
      var name = <%- JSON.stringify(AppCache.name) %>;

      var doLoad = function() {
        var page = directory + name + '.html';
        var iframe = document.createElement('iframe');

        <% if (AppCache.events) { %>
          window.addEventListener('message', function(e) {
            if (e.source !== iframe.contentWindow) return;

            var match = (e.data + '').match(/__offline-plugin_AppCacheEvent:(\w+)/);
            var event = match[1];

            if (typeof options[event] === 'function') {
              options[event]({
                source: 'AppCache'
              });
            }
          });
        <% } %>

        iframe.src = page;
        iframe.style.display = 'none';

        appCacheIframe = iframe;
        document.body.appendChild(iframe);
      };

      if (document.readyState === 'complete') {
        setTimeout(doLoad);
      } else {
        window.addEventListener('load', doLoad);
      }

      return;
    }
  <% } %>
}

function applyUpdate(callback, errback) {
  <% if (typeof ServiceWorker !== 'undefined' && ServiceWorker.events) { %>
    if (hasSW()) {
      navigator.serviceWorker.getRegistration().then(function(registration) {
        if (!registration || !registration.waiting) {
          errback && errback();
          return;
        }

        registration.waiting.postMessage({
          action: 'skipWaiting'
        });

        callback && callback();
      });

      return;
    }
  <% } %>

  <% if (typeof AppCache !== 'undefined' && AppCache.events && AppCache.disableInstall !== true) { %>
    if (appCacheIframe) {
      try {
        appCacheIframe.contentWindow.__applyUpdate();
        callback && setTimeout(callback);
      } catch (e) {
        errback && setTimeout(errback);
      }
    }
  <% } %>
}

function updateOfflineService() {
  if (navigator.serviceWorker) {
    navigator.serviceWorker.ready.then((registration) => {
      if(!registration) {
        return;
      }
      return registration.update();
    });
  } else if (window.applicationCache) {
    applicationCache.update();
  }
}

<% if (typeof autoUpdate !== 'undefined' && autoUpdate === true) { %>
  setInterval(function () { updateOfflineService(); }, <%- JSON.stringify(autoUpdateInterval) %>);
<% } %>

exports.install = install;
exports.applyUpdate = applyUpdate;
<|endoftext|>


<commit_before>#!/usr/bin/node
"use strict";

// Make sure to initialize the smart terminal before any output.
require("./src/terminal-utils/smart_term")();

const runOperation = require("./src/operations");

let args = process.argv.slice(2);
runOperation(args.shift(), args);

/*
TODO: Create
    - dependency checker module that can accept them as params.
    - command runner that uses async loader and handles errors.
    - history tracker module to undo actions on failures.
    - json configuration loader/saver that handlers errors.

    - drupal class that handles everything for drupal website
        - get from the 3 types
        - read and write to settings.php
        - install

    - container base class that will be extended by the different services
    - docker composer which assembles services
*/<commit_msg>Updates todo's.<commit_after>#!/usr/bin/node
"use strict";

// Make sure to initialize the smart terminal before any output.
require("./src/terminal-utils/smart_term")();

const runOperation = require("./src/operations");

let args = process.argv.slice(2);
runOperation(args.shift(), args);

/*
TODO:
    - create container base class
        - add container handlers for docker and ansible
        - services should support both

    - create project manager that will discover and provide
      interface to project types for available operations
        - this should wrap the container manager so
          that it will setup containers for projects
        - it should handle stored data about projects
        - it manages IP hosts aliases

    - create project handler base class (ex. drupal, laravel etc)
        - this will be extended by the different project types
        - handles setup of the project
        - can provide additional console operations (ex. drush)

    - logging for container services

    - server watcher that provides a server for all domain aliases
      and on request automatically starts container for project
        - optionally used

NOTES:
    - the process should be in the following order:
        - configure project and download/clone
        - configure container for project
        - post project configuration with available services
        - optional project installation

*/<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native';

class MTG extends Component {
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity>
          <Text style={styles.button}>play</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // justifyContent: 'center',
    // alignItems: 'center',
    // backgroundColor: '#000',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  button: {
    fontSize: 25,
    borderColor: '#34B8F1',
    borderWidth: 3,
    borderRadius: 5,
    textAlign: 'center',
    // backgroundColor: '#34B8F1',
    color: '#34B8F1',
    // padding: 5,
    // left: 20,
    top: 41,
  }
});

AppRegistry.registerComponent('MTG', () => MTG);
<commit_msg>better callbacks<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native';

class MTG extends Component {
  constructor(props) {
    super(props);

    this.state = {
      numberPlayers: 2,
      showInitialState: true,
    };
  }
  handleSetNumberPlayers(numberPlayers) {
    this.setState({
      numberPlayers: numberPlayers,
      showInitialState: false,
    });
  }
  render() {
    let {showInitialState} = this.state;
    if (showInitialState) {
      return <MTGSetup onSetNumberPlayers={this.handleSetNumberPlayers} />;
    }
    return (
      <View style={styles.container}>
        <TouchableOpacity>
          <Text style={styles.button}>play</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

class MTGSetup extends Component {
  constructor(props) {
    super(props);
  }
  handleClick(e) {
    console.log(e);
  }
  render () {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>How many players?</Text>
        <TouchableOpacity onPress={() => this.handleClick(2)}>
          <Text style={styles.button}>two</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => this.handleClick(3)}>
          <Text style={styles.button}>three</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => this.handleClick(4)}>
          <Text style={styles.button}>four</Text>
        </TouchableOpacity>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // justifyContent: 'center',
    // alignItems: 'center',
    // backgroundColor: '#000',
  },
  welcome: {
    fontSize: 30,
    textAlign: 'center',
    color: '#34B8F1',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  button: {
    fontSize: 25,
    borderColor: '#34B8F1',
    borderWidth: 3,
    borderRadius: 5,
    textAlign: 'center',
    // backgroundColor: '#34B8F1',
    color: '#34B8F1',
    padding: 15,
    margin: 15,
    // left: 20,
    top: 41,
  }
});

AppRegistry.registerComponent('MTG', () => MTGSetup);
<|endoftext|>


<commit_before>$(document).ready(function() {
   $('#fileControl').change(function() {
      if (this.files.length > 0) {
	      var reader = new FileReader();
		  reader.onload = function(e) {
		      try {
			     $('#errorMessage').text('');
		         var model = parseIGC(this.result);
				 var nPoints = model.recordTime.length;
				 var pressureBarogramData = [];
				 var j;
				 var timestamp;
				 for (j = 0; j < nPoints; j++) {
				     timestamp = model.recordTime[j].getTime();
				     pressureBarogramData.push([timestamp, model.pressureAltitude[j]]);
				 }

				 $('#barograph').plot([pressureBarogramData], {
				     xaxis: {
						mode: "time",
                        timeformat: "%H:%M:%S"
                     }
				 });
			  } catch (e) {
			    if (e instanceof IGCException) {
				    $('#errorMessage').text(e.message);
				}
				else {
				    throw e;
				}
			  }
		  };
		  reader.readAsText(this.files[0]);
	  }
   });
});<commit_msg>Added GPS altitude to the barogram plot.<commit_after>$(document).ready(function() {
   $('#fileControl').change(function() {
      if (this.files.length > 0) {
	      var reader = new FileReader();
		  reader.onload = function(e) {
		      try {
			     $('#errorMessage').text('');
		         var model = parseIGC(this.result);
				 var nPoints = model.recordTime.length;
				 var pressureBarogramData = [];
				 var gpsBarogramData = [];
				 var j;
				 var timestamp;
				 for (j = 0; j < nPoints; j++) {
				     timestamp = model.recordTime[j].getTime();
				     pressureBarogramData.push([timestamp, model.pressureAltitude[j]]);
                     gpsBarogramData.push([timestamp, model.gpsAltitude[j]]);
				 }

				 $('#barograph').plot([{
					label: 'Pressure altitude',
					data: pressureBarogramData}, {
					label: 'GPS altitude',
				    data: gpsBarogramData}], {
				     xaxis: {
						mode: 'time',
                        timeformat: '%H:%M:%S'
                     }
				 });
			  } catch (e) {
			    if (e instanceof IGCException) {
				    $('#errorMessage').text(e.message);
				}
				else {
				    throw e;
				}
			  }
		  };
		  reader.readAsText(this.files[0]);
	  }
   });
});<|endoftext|>


<commit_before>'use strict';

angular.module('firebase', []).value('Firebase', Firebase);

// Implicit syncing. angularFire binds a model to $scope and keeps the dat
// synchronized with a Firebase location both ways.
// TODO: Optimize to use child events instead of whole 'value'.
angular.module('firebase').factory('angularFire', ['$q', '$parse', function($q, $parse) {
  return function(url, scope, name, ret) {
    var af = new AngularFire($q, $parse, url);
    return af.associate(scope, name, ret);
  };
}]);

function AngularFire($q, $parse, url) {
  this._q = $q;
  this._parse = $parse;
  this._initial = true;
  this._remoteValue = false;
  this._fRef = new Firebase(url);
}
AngularFire.prototype = {
  associate: function($scope, name, ret) {
    var self = this;
    if (ret == undefined) {
      ret = [];
    }
    var deferred = this._q.defer();
    var promise = deferred.promise;
    this._fRef.on('value', function(snap) {
      var resolve = false;
      if (deferred) {
        resolve = deferred;
        deferred = false;
      }
      self._remoteValue = ret;
      if (snap && snap.val() != undefined) {
        var val = snap.val();
        if (typeof val != typeof ret) {
          self._log("Error: type mismatch");
          return;
        }
        // Also distinguish between objects and arrays.
        var check = Object.prototype.toString;
        if (check.call(ret) != check.call(val)) {
          self._log("Error: type mismatch");
          return;
        }
        self._remoteValue = angular.copy(val);
        if (angular.equals(val, $scope[name])) {
          return;
        }
      }
      self._safeApply($scope,
        self._resolve.bind(self, $scope, name, resolve, self._remoteValue));
    });
    return promise;
  },
  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },
  _resolve: function($scope, name, deferred, val) {
    this._parse(name).assign($scope, angular.copy(val));
    this._remoteValue = angular.copy(val);
    if (deferred) {
      deferred.resolve(val);
      this._watch($scope, name);
    }
  },
  _watch: function($scope, name) {
    // Watch for local changes.
    var self = this;
    $scope.$watch(name, function() {
      if (self._initial) {
        self._initial = false;
        return;
      }
      var val = JSON.parse(angular.toJson($scope[name]));
      if (angular.equals(val, self._remoteValue)) {
        return;
      }
      self._fRef.set(val);
    }, true);
  },
  _safeApply: function($scope, fn) {
    var phase = $scope.$root.$$phase;
    if (phase == '$apply' || phase == '$digest') {
      fn();
    } else {
      $scope.$apply(fn);
    }
  }
};

// Explicit syncing. Provides a collection object you can modify.
// Original code by @petebacondarwin, from:
// https://github.com/petebacondarwin/angular-firebase/blob/master/ng-firebase-collection.js
angular.module('firebase').factory('angularFireCollection', ['$timeout', function($timeout) {
  function angularFireItem(ref, index) {
    this.$ref = ref.ref();
    this.$id = ref.name();
    this.$index = index;
    angular.extend(this, ref.val());
  }

  return function(collectionUrl, initialCb) {
    var collection = [];
    var indexes = {};
    var collectionRef = new Firebase(collectionUrl);

    function getIndex(prevId) {
      return prevId ? indexes[prevId] + 1 : 0;
    }

    function addChild(index, item) {
      indexes[item.$id] = index;
      collection.splice(index, 0, item);
    }

    function removeChild(id) {
      var index = indexes[id];
      // Remove the item from the collection.
      collection.splice(index, 1);
      indexes[id] = undefined;
    }

    function updateChild (index, item) {
      collection[index] = item;
    }

    function moveChild (from, to, item) {
      collection.splice(from, 1);
      collection.splice(to, 0, item);
      updateIndexes(from, to);
    }

    function updateIndexes(from, to) {
      var length = collection.length;
      to = to || length;
      if (to > length) {
        to = length;
      }
      for (var index = from; index < to; index++) {
        var item = collection[index];
        item.$index = indexes[item.$id] = index;
      }
    }

    if (initialCb && typeof initialCb == 'function') {
      collectionRef.once('value', initialCb);
    }

    collectionRef.on('child_added', function(data, prevId) {
      $timeout(function() {
        var index = getIndex(prevId);
        addChild(index, new angularFireItem(data, index));
        updateIndexes(index);
      });
    });

    collectionRef.on('child_removed', function(data) {
      $timeout(function() {
        var id = data.name();
        removeChild(id);
        updateIndexes(indexes[id]);
      });
    });

    collectionRef.on('child_changed', function(data, prevId) {
      $timeout(function() {
        var index = indexes[data.name()];
        var newIndex = getIndex(prevId);
        var item = new angularFireItem(data, index);

        updateChild(index, item);
        if (newIndex !== index) {
          moveChild(index, newIndex, item);
        }
      });
    });

    collectionRef.on('child_moved', function(ref, prevId) {
      $timeout(function() {
        var oldIndex = indexes[ref.name()];
        var newIndex = getIndex(prevId);
        var item = collection[oldIndex];
        moveChild(oldIndex, newIndex, item);
      });
    });

    collection.add = function(item, cb) {
      if (!cb) {
        collectionRef.push(item);
      } else {
        collectionRef.push(item, cb);
      }
    };
    collection.remove = function(itemOrId) {
      var item = angular.isString(itemOrId) ? collection[indexes[itemOrId]] : itemOrId;
      item.$ref.remove();
    };

    collection.update = function(itemOrId) {
      var item = angular.isString(itemOrId) ? collection[indexes[itemOrId]] : itemOrId;
      var copy = {};
      angular.forEach(item, function(value, key) {
        if (key.indexOf('$') !== 0) {
          copy[key] = value;
        }
      });
      item.$ref.set(copy);
    };

    return collection;
  };
}]);
<commit_msg>Fix broken indexes on remove. Notice that removeChild removes the index, so updateIndexes will always get undefined as the 'from' parameter. As a consequence, the indexes never get updated on remove. This can be reproduced in the following way: remove the 0th element from a collection, this will cause the index go inconsistent, and consequent removals will lead to a broken state. This is a trivial fix that I tested to fix the problem in my use case.<commit_after>'use strict';

angular.module('firebase', []).value('Firebase', Firebase);

// Implicit syncing. angularFire binds a model to $scope and keeps the dat
// synchronized with a Firebase location both ways.
// TODO: Optimize to use child events instead of whole 'value'.
angular.module('firebase').factory('angularFire', ['$q', '$parse', function($q, $parse) {
  return function(url, scope, name, ret) {
    var af = new AngularFire($q, $parse, url);
    return af.associate(scope, name, ret);
  };
}]);

function AngularFire($q, $parse, url) {
  this._q = $q;
  this._parse = $parse;
  this._initial = true;
  this._remoteValue = false;
  this._fRef = new Firebase(url);
}
AngularFire.prototype = {
  associate: function($scope, name, ret) {
    var self = this;
    if (ret == undefined) {
      ret = [];
    }
    var deferred = this._q.defer();
    var promise = deferred.promise;
    this._fRef.on('value', function(snap) {
      var resolve = false;
      if (deferred) {
        resolve = deferred;
        deferred = false;
      }
      self._remoteValue = ret;
      if (snap && snap.val() != undefined) {
        var val = snap.val();
        if (typeof val != typeof ret) {
          self._log("Error: type mismatch");
          return;
        }
        // Also distinguish between objects and arrays.
        var check = Object.prototype.toString;
        if (check.call(ret) != check.call(val)) {
          self._log("Error: type mismatch");
          return;
        }
        self._remoteValue = angular.copy(val);
        if (angular.equals(val, $scope[name])) {
          return;
        }
      }
      self._safeApply($scope,
        self._resolve.bind(self, $scope, name, resolve, self._remoteValue));
    });
    return promise;
  },
  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },
  _resolve: function($scope, name, deferred, val) {
    this._parse(name).assign($scope, angular.copy(val));
    this._remoteValue = angular.copy(val);
    if (deferred) {
      deferred.resolve(val);
      this._watch($scope, name);
    }
  },
  _watch: function($scope, name) {
    // Watch for local changes.
    var self = this;
    $scope.$watch(name, function() {
      if (self._initial) {
        self._initial = false;
        return;
      }
      var val = JSON.parse(angular.toJson($scope[name]));
      if (angular.equals(val, self._remoteValue)) {
        return;
      }
      self._fRef.set(val);
    }, true);
  },
  _safeApply: function($scope, fn) {
    var phase = $scope.$root.$$phase;
    if (phase == '$apply' || phase == '$digest') {
      fn();
    } else {
      $scope.$apply(fn);
    }
  }
};

// Explicit syncing. Provides a collection object you can modify.
// Original code by @petebacondarwin, from:
// https://github.com/petebacondarwin/angular-firebase/blob/master/ng-firebase-collection.js
angular.module('firebase').factory('angularFireCollection', ['$timeout', function($timeout) {
  function angularFireItem(ref, index) {
    this.$ref = ref.ref();
    this.$id = ref.name();
    this.$index = index;
    angular.extend(this, ref.val());
  }

  return function(collectionUrl, initialCb) {
    var collection = [];
    var indexes = {};
    var collectionRef = new Firebase(collectionUrl);

    function getIndex(prevId) {
      return prevId ? indexes[prevId] + 1 : 0;
    }

    function addChild(index, item) {
      indexes[item.$id] = index;
      collection.splice(index, 0, item);
    }

    function removeChild(id) {
      var index = indexes[id];
      // Remove the item from the collection.
      collection.splice(index, 1);
      indexes[id] = undefined;
    }

    function updateChild (index, item) {
      collection[index] = item;
    }

    function moveChild (from, to, item) {
      collection.splice(from, 1);
      collection.splice(to, 0, item);
      updateIndexes(from, to);
    }

    function updateIndexes(from, to) {
      var length = collection.length;
      to = to || length;
      if (to > length) {
        to = length;
      }
      for (var index = from; index < to; index++) {
        var item = collection[index];
        item.$index = indexes[item.$id] = index;
      }
    }

    if (initialCb && typeof initialCb == 'function') {
      collectionRef.once('value', initialCb);
    }

    collectionRef.on('child_added', function(data, prevId) {
      $timeout(function() {
        var index = getIndex(prevId);
        addChild(index, new angularFireItem(data, index));
        updateIndexes(index);
      });
    });

    collectionRef.on('child_removed', function(data) {
      $timeout(function() {
        var id = data.name();
        var pos = indexes[id];
        removeChild(id);
        updateIndexes(pos);
      });
    });

    collectionRef.on('child_changed', function(data, prevId) {
      $timeout(function() {
        var index = indexes[data.name()];
        var newIndex = getIndex(prevId);
        var item = new angularFireItem(data, index);

        updateChild(index, item);
        if (newIndex !== index) {
          moveChild(index, newIndex, item);
        }
      });
    });

    collectionRef.on('child_moved', function(ref, prevId) {
      $timeout(function() {
        var oldIndex = indexes[ref.name()];
        var newIndex = getIndex(prevId);
        var item = collection[oldIndex];
        moveChild(oldIndex, newIndex, item);
      });
    });

    collection.add = function(item, cb) {
      if (!cb) {
        collectionRef.push(item);
      } else {
        collectionRef.push(item, cb);
      }
    };
    collection.remove = function(itemOrId) {
      var item = angular.isString(itemOrId) ? collection[indexes[itemOrId]] : itemOrId;
      item.$ref.remove();
    };

    collection.update = function(itemOrId) {
      var item = angular.isString(itemOrId) ? collection[indexes[itemOrId]] : itemOrId;
      var copy = {};
      angular.forEach(item, function(value, key) {
        if (key.indexOf('$') !== 0) {
          copy[key] = value;
        }
      });
      item.$ref.set(copy);
    };

    return collection;
  };
}]);
<|endoftext|>


<commit_before><commit_msg>Build/Minify Framework (1.0.3 - 05.01.2015 - 09:15:51)<commit_after><|endoftext|>


<commit_before>// Copyright 2013 LinkedIn Corp.
//
// 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.

var fs = require('fs');
var replayerUtil = require('./util');
var EventEmitter = require('events').EventEmitter;
var http = require('http');

var playbackHits = true;
var recordMisses = true;

module.exports.configure = function(mode) {
  switch (mode) {
    case 'record':
      playbackHits = false;
      recordMisses = true;
      break;

    case 'playback':
      playbackHits = true;
      recordMisses = false;
      break;

    case 'cache':
      playbackHits = true;
      recordMisses = true;
      break;

    default:
      throw new Error('Unrecognized mode: ' + mode);
  }
};

/**
* Turns replayer on.
*/
module.exports.enable = function enable() {
  ['http', 'https'].forEach(function(protocol) {
    var protocolModule = require(protocol);
    if (protocolModule.__replayerRequest) {
      protocolModule.request = protocolModule.__replayerRequest;
    } else {
      throw new Error('Unexpectedly, %s.__replayerRequest is missing.  Cannot enable replayer.', protocol);
    }
  });
};

/**
* Turns replayer off.
*/
module.exports.disable = function disable() {
  ['http', 'https'].forEach(function(protocol) {
    var protocolModule = require(protocol);
    if (protocolModule.__originalRequest) {
      protocolModule.request = protocolModule.__originalRequest;
    } else {
      throw new Error('Unexpectedly, %s.__replayerRequest is missing.  Cannot enable replayer.', protocol);
    }
  });
};

module.exports.isEnabled = function isEnabled() {
  var http = require('http');
  var https = require('https');
  if (http.request === http.__replayerRequest) {
    if (https.request === https.__replayerRequest) {
      return true;
    } else {
      throw new Error('Http and https request methods are in a conflicted state.');
    }
  } else {
    if (https.request === https.__replayerRequest) {
      throw new Error('Http and https request methods are in a conflicted state.');
    } else {
      return false;
    }
  }
};

//
['http', 'https'].forEach(function(protocol) {
  var protocolModule = require(protocol);
  var oldRequest = protocolModule.request;
  protocolModule.__originalRequest = oldRequest;

  // Ensure there are enough sockets to handle timeout issues that arise due to
  // slow servers or unaccessible servers when recording.
  protocolModule.globalAgent.maxSockets = 1000;

  protocolModule.__replayerRequest = protocolModule.request = function replayerRequest(options, callback) {
    var reqUrl = replayerUtil.urlFromHttpRequestOptions(options, protocol);
    var reqBody = [];
    var debug = replayerUtil.shouldFindMatchingFixtures();

    var req = new EventEmitter();
    req.setTimeout = req.abort = function() {};

    req.write = function(chunk) {
      reqBody.push(chunk);
    };

    req.end = function(lastChunk) {
      if (lastChunk) {
        reqBody.push(lastChunk);
      }

      reqBody = reqBody.map(function(chunk) {
        if (!Buffer.isBuffer(chunk)) {
          return new Buffer(chunk);
        } else {
          return chunk;
        }
      });

      reqBody = Buffer.concat(reqBody);
      var filename = replayerUtil.constructFilename(options.method, reqUrl,
        reqBody.toString(), options.headers);

      options.headers = replayerUtil.removeInternalHeaders(options.headers);

      var forceLive = replayerUtil.shouldForceLive(reqUrl);

      // Only called if either the fixture with the constructed filename
      // exists, or we're playing back passed in data.
      function playback(resHeaders, resBody) {
        if (!forceLive) {
          var headerContent = replayerUtil.substituteWithRealValues(
            fs.readFileSync(filename + '.headers').toString());
          resHeaders = JSON.parse(headerContent);
        }

        var socket = new EventEmitter();
        socket.setTimeout = socket.setEncoding = function() {};
        // Needed for node 0.8.x
        socket.destroy = socket.pause = socket.resume = function() {};

        req.socket = socket;
        req.emit('socket', socket);

        if (resHeaders.timeout) {
          socket.emit('timeout');
          req.emit('error', new Error('Timeout'));
          return;
        }

        if (resHeaders.error) {
          req.emit('error', resHeaders.error);
          return;
        }

        var res = new http.IncomingMessage(socket);
        res.headers = resHeaders.headers || {};
        res.statusCode = resHeaders.statusCode;

        if (callback) {
          callback(res);
        }

        if (!forceLive) {
          resBody = replayerUtil.substituteWithRealValues(fs.readFileSync(filename).toString());
        }

        req.emit('response', res);

        if (res.push) {
          // node 0.10.x
          res.push(resBody);
          res.push(null);
        } else {
          // node 0.8.x
          res.emit('data', resBody);
          res.emit('end');
        }
      }

      // If the file exists and we allow playback (e.g. we are not in
      // record-only mode), then simply play back the call.
      if (playbackHits && !forceLive && fs.existsSync(filename + '.headers')) {
        playback();
        return;
      }

      // If we are not recording, and the fixtures file does not exist, then
      // throw an exception.
      if (!recordMisses && !forceLive) {
        // But, create a .missing file before throwing the exception.
        var requestData = {
          url: reqUrl,
          method: options.method,
          headers: options.headers,
          body: reqBody.toString()
        };

        var missingFileName = filename + '.missing';
        fs.writeFileSync(missingFileName,
          JSON.stringify(requestData, null, 2));

        if (debug) {
          var bestMatchFileName =
            replayerUtil.findTheBestMatchingFixture(missingFileName);
          if (bestMatchFileName) {
            throw new Error('Fixture ' + filename + ' not found,  Expected ' +
              missingFileName +
              ' , but the best match is ' + bestMatchFileName);
          } else {
            throw new Error('Fixture ' + filename +
              ' not found and could not compute the best matching fixture');
          }
        }

        throw new Error('Fixture ' + filename + ' not found.');
      }

      // Remember how long it took to perform this action.
      var startTime = Date.now();
      var timedOut = false;

      function writeHeaderFile(headers) {
        var timeLength = Date.now() - startTime;
        headers.url = reqUrl;
        headers.time = timeLength;
        headers.request = {
          method: options.method,
          headers: options.headers
        };

        fs.writeFileSync(filename + '.headers',
          replayerUtil.substituteWithOpaqueKeys(JSON.stringify(headers, null, 2)));
      }

      // Suppose the request times out while recording. We don't want the
      // fixtures file to be missing; we want to send back a timeout on
      // playback. To accomplish this, we write a timeout to the .header file
      // pre-emptively, then overwrite it with the server response if the
      // request doesn't time out.
      if (!forceLive) {
        writeHeaderFile({
          timeout: true,
          time: 30000
        });
      }

      var realReq = oldRequest(options, function(res) {
        // It's important that we don't respect the encoding set by
        // application because we want to treat the incoming data as a
        // buffer. When body data is treated as a string, there are issues
        // writing it to a file. With non-ASCII messages, the string's length
        // (in characters) is not necessarily the same as the buffer's length
        // (in bytes). Thus, the solution is to treat the data as a buffer
        // without allowing conversion into a string.
        res.setEncoding = function() {};

        var resBodyChunks = [];
        res.on('data', function(chunk) {
          resBodyChunks.push(chunk);
        });

        res.on('end', function() {
          var resBody = Buffer.concat(resBodyChunks);

          if (forceLive) {
            // Don't write the response to any files, and just send it back to
            // whoever issued the request.
            playback({
              statusCode: res.statusCode,
              headers: res.headers
            }, resBody);
          } else {
            fs.writeFileSync(filename,
              replayerUtil.substituteWithOpaqueKeys(resBody.toString()));

            // Store the request, if debug is true
            if (debug) {
              var requestData = {
                url: reqUrl,
                method: options.method,
                headers: options.headers,
                body: reqBody.toString()
              };
              writeRequestFile(requestData, filename);
            }

            writeHeaderFile({
              statusCode: res.statusCode,
              headers: res.headers
            });

            playback();
          }
        });
      });

      realReq.on('error', function(error) {
        var header = {
          error: error
        };

        if (timedOut) {
          header.timeout = true;
        }

        if (forceLive) {
          // Don't write the error to a file, and just send it back to whoever
          // issued the request.
          playback(undefined, header);
        } else {
          writeHeaderFile(header);
          playback();
        }
      });

      realReq.on('socket', function(socket) {
        socket.on('timeout', function() {
          timedOut = true;
        });
      });

      realReq.end(reqBody);
    };

    return req;
  };
});

function writeRequestFile(requestData, filename) {
  fs.writeFileSync(filename + '.request',
    JSON.stringify(requestData, null, 2));
}

module.exports.internal = {};
module.exports.internal.writeRequestFile = writeRequestFile;
<commit_msg>Fix ECONNRESET handling<commit_after>// Copyright 2013 LinkedIn Corp.
//
// 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.

var fs = require('fs');
var replayerUtil = require('./util');
var EventEmitter = require('events').EventEmitter;
var http = require('http');

var playbackHits = true;
var recordMisses = true;

module.exports.configure = function(mode) {
  switch (mode) {
    case 'record':
      playbackHits = false;
      recordMisses = true;
      break;

    case 'playback':
      playbackHits = true;
      recordMisses = false;
      break;

    case 'cache':
      playbackHits = true;
      recordMisses = true;
      break;

    default:
      throw new Error('Unrecognized mode: ' + mode);
  }
};

/**
* Turns replayer on.
*/
module.exports.enable = function enable() {
  ['http', 'https'].forEach(function(protocol) {
    var protocolModule = require(protocol);
    if (protocolModule.__replayerRequest) {
      protocolModule.request = protocolModule.__replayerRequest;
    } else {
      throw new Error('Unexpectedly, %s.__replayerRequest is missing.  Cannot enable replayer.', protocol);
    }
  });
};

/**
* Turns replayer off.
*/
module.exports.disable = function disable() {
  ['http', 'https'].forEach(function(protocol) {
    var protocolModule = require(protocol);
    if (protocolModule.__originalRequest) {
      protocolModule.request = protocolModule.__originalRequest;
    } else {
      throw new Error('Unexpectedly, %s.__replayerRequest is missing.  Cannot enable replayer.', protocol);
    }
  });
};

module.exports.isEnabled = function isEnabled() {
  var http = require('http');
  var https = require('https');
  if (http.request === http.__replayerRequest) {
    if (https.request === https.__replayerRequest) {
      return true;
    } else {
      throw new Error('Http and https request methods are in a conflicted state.');
    }
  } else {
    if (https.request === https.__replayerRequest) {
      throw new Error('Http and https request methods are in a conflicted state.');
    } else {
      return false;
    }
  }
};

//
['http', 'https'].forEach(function(protocol) {
  var protocolModule = require(protocol);
  var oldRequest = protocolModule.request;
  protocolModule.__originalRequest = oldRequest;

  // Ensure there are enough sockets to handle timeout issues that arise due to
  // slow servers or unaccessible servers when recording.
  protocolModule.globalAgent.maxSockets = 1000;

  protocolModule.__replayerRequest = protocolModule.request = function replayerRequest(options, callback) {
    var reqUrl = replayerUtil.urlFromHttpRequestOptions(options, protocol);
    var reqBody = [];
    var debug = replayerUtil.shouldFindMatchingFixtures();

    var req = new EventEmitter();
    req.setTimeout = req.abort = function() {};

    req.write = function(chunk) {
      reqBody.push(chunk);
    };

    req.end = function(lastChunk) {
      if (lastChunk) {
        reqBody.push(lastChunk);
      }

      reqBody = reqBody.map(function(chunk) {
        if (!Buffer.isBuffer(chunk)) {
          return new Buffer(chunk);
        } else {
          return chunk;
        }
      });

      reqBody = Buffer.concat(reqBody);
      var filename = replayerUtil.constructFilename(options.method, reqUrl,
        reqBody.toString(), options.headers);

      options.headers = replayerUtil.removeInternalHeaders(options.headers);

      var forceLive = replayerUtil.shouldForceLive(reqUrl);

      // Only called if either the fixture with the constructed filename
      // exists, or we're playing back passed in data.
      function playback(resHeaders, resBody) {
        if (!forceLive) {
          var headerContent = replayerUtil.substituteWithRealValues(
            fs.readFileSync(filename + '.headers').toString());
          resHeaders = JSON.parse(headerContent);
        }

        var socket = new EventEmitter();
        socket.setTimeout = socket.setEncoding = function() {};
        // Needed for node 0.8.x
        socket.destroy = socket.pause = socket.resume = function() {};

        req.socket = socket;
        req.emit('socket', socket);

        if (!resHeaders) {
          if (resBody.error) {
            req.emit('error', resBody.error);
          } else {
            req.emit('error',
            new Error(`No response headers. Response body: ${JSON.stringify(resBody)}`));
          }
          return;
        }

        if (resHeaders.timeout) {
          socket.emit('timeout');
          req.emit('error', new Error('Timeout'));
          return;
        }

        if (resHeaders.error) {
          req.emit('error', resHeaders.error);
          return;
        }

        var res = new http.IncomingMessage(socket);
        res.headers = resHeaders.headers || {};
        res.statusCode = resHeaders.statusCode;

        if (callback) {
          callback(res);
        }

        if (!forceLive) {
          resBody = replayerUtil.substituteWithRealValues(fs.readFileSync(filename).toString());
        }

        req.emit('response', res);

        if (res.push) {
          // node 0.10.x
          res.push(resBody);
          res.push(null);
        } else {
          // node 0.8.x
          res.emit('data', resBody);
          res.emit('end');
        }
      }

      // If the file exists and we allow playback (e.g. we are not in
      // record-only mode), then simply play back the call.
      if (playbackHits && !forceLive && fs.existsSync(filename + '.headers')) {
        playback();
        return;
      }

      // If we are not recording, and the fixtures file does not exist, then
      // throw an exception.
      if (!recordMisses && !forceLive) {
        // But, create a .missing file before throwing the exception.
        var requestData = {
          url: reqUrl,
          method: options.method,
          headers: options.headers,
          body: reqBody.toString()
        };

        var missingFileName = filename + '.missing';
        fs.writeFileSync(missingFileName,
          JSON.stringify(requestData, null, 2));

        if (debug) {
          var bestMatchFileName =
            replayerUtil.findTheBestMatchingFixture(missingFileName);
          if (bestMatchFileName) {
            throw new Error('Fixture ' + filename + ' not found,  Expected ' +
              missingFileName +
              ' , but the best match is ' + bestMatchFileName);
          } else {
            throw new Error('Fixture ' + filename +
              ' not found and could not compute the best matching fixture');
          }
        }

        throw new Error('Fixture ' + filename + ' not found.');
      }

      // Remember how long it took to perform this action.
      var startTime = Date.now();
      var timedOut = false;

      function writeHeaderFile(headers) {
        var timeLength = Date.now() - startTime;
        headers.url = reqUrl;
        headers.time = timeLength;
        headers.request = {
          method: options.method,
          headers: options.headers
        };

        fs.writeFileSync(filename + '.headers',
          replayerUtil.substituteWithOpaqueKeys(JSON.stringify(headers, null, 2)));
      }

      // Suppose the request times out while recording. We don't want the
      // fixtures file to be missing; we want to send back a timeout on
      // playback. To accomplish this, we write a timeout to the .header file
      // pre-emptively, then overwrite it with the server response if the
      // request doesn't time out.
      if (!forceLive) {
        writeHeaderFile({
          timeout: true,
          time: 30000
        });
      }

      var realReq = oldRequest(options, function(res) {
        // It's important that we don't respect the encoding set by
        // application because we want to treat the incoming data as a
        // buffer. When body data is treated as a string, there are issues
        // writing it to a file. With non-ASCII messages, the string's length
        // (in characters) is not necessarily the same as the buffer's length
        // (in bytes). Thus, the solution is to treat the data as a buffer
        // without allowing conversion into a string.
        res.setEncoding = function() {};

        var resBodyChunks = [];
        res.on('data', function(chunk) {
          resBodyChunks.push(chunk);
        });

        res.on('end', function() {
          var resBody = Buffer.concat(resBodyChunks);

          if (forceLive) {
            // Don't write the response to any files, and just send it back to
            // whoever issued the request.
            playback({
              statusCode: res.statusCode,
              headers: res.headers
            }, resBody);
          } else {
            fs.writeFileSync(filename,
              replayerUtil.substituteWithOpaqueKeys(resBody.toString()));

            // Store the request, if debug is true
            if (debug) {
              var requestData = {
                url: reqUrl,
                method: options.method,
                headers: options.headers,
                body: reqBody.toString()
              };
              writeRequestFile(requestData, filename);
            }

            writeHeaderFile({
              statusCode: res.statusCode,
              headers: res.headers
            });

            playback();
          }
        });
      });

      realReq.on('error', function(error) {
        var header = {
          error: error
        };

        if (timedOut) {
          header.timeout = true;
        }

        if (forceLive) {
          // Don't write the error to a file, and just send it back to whoever
          // issued the request.
          playback(undefined, header);
        } else {
          writeHeaderFile(header);
          playback();
        }
      });

      realReq.on('socket', function(socket) {
        socket.on('timeout', function() {
          timedOut = true;
        });
      });

      realReq.end(reqBody);
    };

    return req;
  };
});

function writeRequestFile(requestData, filename) {
  fs.writeFileSync(filename + '.request',
    JSON.stringify(requestData, null, 2));
}

module.exports.internal = {};
module.exports.internal.writeRequestFile = writeRequestFile;
<|endoftext|>


<commit_before>/**
 * Slideshow plugin for jQuery.
 *
 * Transitions are done using CSS3 with progressive fallback to jQuery fadeIn.
 *
 * @dependencies jQuery, Modernizr
 * @author Chiel Robben <chiel@pephers.org>
 * @license MIT
 */
(function ($, Modernizr, window, undefined) {

    'use strict';

    /**
     * Constructor
     *
     * @param {object} element
     * @param {object} options
     */
    var Slideshow = function (element, options) {

        var defaults = {
            auto: true,
            interval: 5000,
            duration: 600,
            pause: true,
            zIndexOffset: 0
        };

        this.$element = $(element);
        this.$window = $(window);
        this.$slides = this.$element.children();
        this.isRunning = undefined;
        this.slideCount = this.$slides.length - 1;
        this.currentSlide = 0;
        this.options = $.extend({}, defaults, options);

        if (this.slideCount > 0) {
            this.init();
        }

    };

    Slideshow.prototype = {

        /**
         * Initialize the slideshow
         */
        init: function () {

            var self = this;

            // Hide all images but the first one
            if (Modernizr.csstransitions) {
                self.$slides.eq(self.currentSlide).addClass('slide-active');
            } else {

                self.$slides.css(
                    'zIndex',
                    self.options.zIndexOffset + 1
                ).hide().eq(self.currentSlide).css(
                    'zIndex',
                    self.options.zIndexOffset + 2
                ).show();

            }

            // Start the slideshow on load
            self.$window.on('load', function () {

                self.$element.addClass('slideshow-ready');
                self.setHeight();
                self.start();

            });

            // Pause slideshow when the mouse cursor is hovering
            if (!Modernizr.touch && this.options.pause) {

                self.$element.on({

                    mouseenter: function (e) {
                        self.stop();
                    },

                    mouseleave: function (e) {
                        self.start();
                    }

                });

            }

            // Set height of the slideshow when window is resized
            self.$window.on('resize', function () {
                self.setHeight();
            });

        },

        /**
         * Start the slideshow
         */
        start: function () {

            var self = this;

            // Prevent multiple timers from running at once
            if (self.isRunning) {
                clearInterval(self.isRunning);
            }

            if (self.options.auto) {

                self.isRunning = setInterval(function () {
                    self.nextSlide();
                }, self.options.interval);

            }

        },

        /**
         * Stop the slideshow
         */
        stop: function () {
            clearInterval(this.isRunning);
        },

        /**
         * Slide to the next image
         */
        nextSlide: function () {

            var slide = this.currentSlide === this.slideCount ? 0 : this.currentSlide + 1;

            this.changeSlide(slide);

        },

        /**
         * Slide to the previous image
         */
        previousSlide: function () {

            var slide = this.currentSlide === 0 ? this.slideCount : this.currentSlide - 1;

            this.changeSlide(slide);

        },

        /**
         * Change to the slide specified.
         *
         * @param {int} slide
         */
        changeSlide: function (slide) {

            var self = this,
                previousSlide = slide > self.slideCount ? (self.slideCount - 1) : (slide <= 0 ? self.slideCount : (slide - 1));

            self.currentSlide = slide;

            if (Modernizr.csstransitions) {
                self.$slides.removeClass('slide-active').eq(self.currentSlide).addClass('slide-active');
            } else {

                self.$slides.css(
                    'zIndex',
                    self.options.zIndexOffset + 1
                ).eq(self.currentSlide).css(
                    'zIndex',
                    self.options.zIndexOffset + 2
                ).fadeIn(self.options.duration, function () {
                    self.$slides.eq(previousSlide).hide();
                });

            }

        },

        /**
         * Set height of the slideshow container
         */
        setHeight: function () {

            var height = this.$slides.eq(this.currentSlide)[0].offsetHeight;

            this.$element[0].style.height = height + 'px';

        }

    };

    /**
     * Expose as jQuery plugin in Singleton fashion so it can only be
     * initialized once per element.
     */
    $.fn.slideshow = function (options) {

        return this.each(function () {

            if (!$.data(this, 'slideshow')) {
                $.data(this, 'slideshow', new Slideshow(this, options));
            }

        });

    };

})(jQuery, Modernizr, window);
<commit_msg>Removed non-breaking space<commit_after>/**
 * Slideshow plugin for jQuery.
 *
 * Transitions are done using CSS3 with progressive fallback to jQuery fadeIn.
 *
 * @dependencies jQuery, Modernizr
 * @author Chiel Robben <chiel@pephers.org>
 * @license MIT
 */
(function ($, Modernizr, window, undefined) {

    'use strict';

    /**
     * Constructor
     *
     * @param {object} element
     * @param {object} options
     */
    var Slideshow = function (element, options) {

        var defaults = {
            auto: true,
            interval: 5000,
            duration: 600,
            pause: true,
            zIndexOffset: 0
        };

        this.$element = $(element);
        this.$window = $(window);
        this.$slides = this.$element.children();
        this.isRunning = undefined;
        this.slideCount = this.$slides.length - 1;
        this.currentSlide = 0;
        this.options = $.extend({}, defaults, options);

        if (this.slideCount > 0) {
            this.init();
        }

    };

    Slideshow.prototype = {

        /**
         * Initialize the slideshow
         */
        init: function () {

            var self = this;

            // Hide all images but the first one
            if (Modernizr.csstransitions) {
                self.$slides.eq(self.currentSlide).addClass('slide-active');
            } else {

                self.$slides.css(
                    'zIndex',
                    self.options.zIndexOffset + 1
                ).hide().eq(self.currentSlide).css(
                    'zIndex',
                    self.options.zIndexOffset + 2
                ).show();

            }

            // Start the slideshow on load
            self.$window.on('load', function () {

                self.$element.addClass('slideshow-ready');
                self.setHeight();
                self.start();

            });

            // Pause slideshow when the mouse cursor is hovering
            if (!Modernizr.touch && this.options.pause) {

                self.$element.on({

                    mouseenter: function (e) {
                        self.stop();
                    },

                    mouseleave: function (e) {
                        self.start();
                    }

                });

            }

            // Set height of the slideshow when window is resized
            self.$window.on('resize', function () {
                self.setHeight();
            });

        },

        /**
         * Start the slideshow
         */
        start: function () {

            var self = this;

            // Prevent multiple timers from running at once
            if (self.isRunning) {
                clearInterval(self.isRunning);
            }

            if (self.options.auto) {

                self.isRunning = setInterval(function () {
                    self.nextSlide();
                }, self.options.interval);

            }

        },

        /**
         * Stop the slideshow
         */
        stop: function () {
            clearInterval(this.isRunning);
        },

        /**
         * Slide to the next image
         */
        nextSlide: function () {

            var slide = this.currentSlide === this.slideCount ? 0 : this.currentSlide + 1;

            this.changeSlide(slide);

        },

        /**
         * Slide to the previous image
         */
        previousSlide: function () {

            var slide = this.currentSlide === 0 ? this.slideCount : this.currentSlide - 1;

            this.changeSlide(slide);

        },

        /**
         * Change to the slide specified.
         *
         * @param {int} slide
         */
        changeSlide: function (slide) {

            var self = this,
                previousSlide = slide > self.slideCount ? (self.slideCount - 1) : (slide <= 0 ? self.slideCount : (slide - 1));

            self.currentSlide = slide;

            if (Modernizr.csstransitions) {
                self.$slides.removeClass('slide-active').eq(self.currentSlide).addClass('slide-active');
            } else {

                self.$slides.css(
                    'zIndex',
                    self.options.zIndexOffset + 1
                ).eq(self.currentSlide).css(
                    'zIndex',
                    self.options.zIndexOffset + 2
                ).fadeIn(self.options.duration, function () {
                    self.$slides.eq(previousSlide).hide();
                });

            }

        },

        /**
         * Set height of the slideshow container
         */
        setHeight: function () {

            var height = this.$slides.eq(this.currentSlide)[0].offsetHeight;

            this.$element[0].style.height = height + 'px';

        }

    };

    /**
     * Expose as jQuery plugin in Singleton fashion so it can only be
     * initialized once per element.
     */
    $.fn.slideshow = function (options) {

        return this.each(function () {

            if (!$.data(this, 'slideshow')) {
                $.data(this, 'slideshow', new Slideshow(this, options));
            }

        });

    };

})(jQuery, Modernizr, window);
<|endoftext|>


<commit_before>'use strict';

const debug = require('debug')('miio');

const dgram = require('dgram');
const Packet = require('./packet');

const EventEmitter = require('events');

const DeviceManagement = require('./management');

const IDENTITY_MAPPER  = v => v;

const ERRORS = {
	'-5001': (method, args, err) => err.message == 'invalid_arg' ? 'Invalid argument' : err.message,
	'-10000': (method) => 'Method `' + method + '` is not supported'
};

class Device extends EventEmitter {
	constructor(options) {
		super();

		this.type = 'generic';
		this.model = options.model || 'unknown';

		this.address = options.address;
		this.port = options.port || 54321;

		this.packet = new Packet();
		if(typeof options.token === 'string') {
			this.packet.token = Buffer.from(options.token, 'hex');
		} else if(options.token instanceof Buffer) {
			this.packet.token = options.token;
		}

		this.socket = dgram.createSocket('udp4');
		this.socket.on('message', this._onMessage.bind(this));

		this._id = 0;
		this._promises = {};
		this._hasFailedToken = false;

		this._properties = {};
		this._propertiesToMonitor = [];
		this._propertyMappers = {};

		this._loadProperties = this._loadProperties.bind(this);

		this.management = new DeviceManagement(this);
	}

	_onMessage(msg) {
		try {
			this.packet.raw = msg;
		} catch(ex) {
			debug('<- Unable to parse packet', ex);
			return;
		}

		if(this._tokenResolve) {
			debug('<-', 'Handshake reply:', this.packet.checksum);
			this.packet.handleHandshakeReply();

			let resolve = this._tokenResolve;

			this._lastToken = Date.now();
			this._tokenResolve = null;
			this._tokenPromise = null;

			resolve();
		} else {
			let data = this.packet.data;
			if(! data) {
				debug('<-', null);
				return;
			}

			// Handle null-terminated strings
			if(data[data.length - 1] == 0) {
				data = data.slice(0, data.length - 1);
			}

			// Parse and handle the JSON message
			let str = data.toString('utf8');
			debug('<- Message: `' + str + '`');
			try {
				let object = JSON.parse(str);

				const p = this._promises[object.id];
				if(! p) return;
				if(typeof object.result !== 'undefined') {
					p.resolve(object.result);
				} else {
					p.reject(object.error);
				}
			} catch(ex) {
				debug('<- Invalid JSON');
			}
		}
	}

	_ensureToken() {
		if(! this.packet.needsHandshake) {
			return Promise.resolve();
		}

		if(this._hasFailedToken) {
			return Promise.reject(new Error('Token could not be auto-discovered'));
		}

		if(this._tokenPromise) {
			debug('Using existing promise');
			return this._tokenPromise;
		}

		this._tokenPromise = new Promise((resolve, reject) => {
			this.packet.handshake();
			const data = this.packet.raw;
			this.socket.send(data, 0, data.length, this.port, this.address, err => err && reject(err));
			this._tokenResolve = () => {
				if(this.packet.token) {
					resolve();
				} else {
					this._hasFailedToken = true;
					reject(new Error('Token could not be auto-discovered'));
				}
			};

			// Reject in 1 second
			setTimeout(() => {
				this._tokenPromise = null;
				reject();
			}, 1000);
		});
		return this._tokenPromise;
	}

	call(method, args, options) {
		if(args) {
			if(! Array.isArray(args) && typeof args !== 'object') {
				throw new Error('Arguments need to be an array or object');
			}
		} else {
			args = [];
		}

		const id = this._id = this._id == 10000 ? 1 : this._id + 1;
		const json = JSON.stringify({
			id: id,
			method: method,
			params: args
		});

		if(options && options.sid) {
			// If we have a sub-device set it (used by Lumi Smart Home Gateway)
			json.sid = options.sid;
		}

		return new Promise((resolve, reject) => {
			let resolved = false;
			const promise = this._promises[id] = {
				resolve: res => {
					resolved = true;
					delete this._promises[id];

					if(options && options.refresh) {
						// Special case for loading properties after setting values
						this._loadProperties()
							.then(() => resolve(res))
							.catch(() => resolve(res));
					} else {
						resolve(res);
					}
				},
				reject: err => {
					resolved = true;
					delete this._promises[id];

					if(! (err instanceof Error) && typeof err.code !== 'undefined') {
						const code = err.code;

						const handler = ERRORS[code];
						let msg;
						if(handler) {
							msg = handler(method, args, err.message);
						} else {
							msg = err.message || err.toString();
						}

						err = new Error(msg);
						err.code = code;
					}
					reject(err);
				}
			};

			let sendsLeft = 3;
			const send = () => {
				if(resolved) return;

				this._ensureToken()
					.then(() => {
						this.packet.data = Buffer.from(json, 'utf8');

						const data = this.packet.raw;

						debug('-> (' + sendsLeft + ')', json);
						this.socket.send(data, 0, data.length, this.port, this.address, err => err && reject(err));

						if(--sendsLeft > 0) {
							setTimeout(send, 2000);
						} else {
							promise.reject(new Error('Timeout'));
						}
					})
					.catch(reject);
			};

			send();
		});
	}

	/**
	 * Define a property and how the value should be mapped. All defined
	 * properties are monitored if #monitor() is called.
	 */
	defineProperty(name, mapper) {
		this._propertiesToMonitor.push(name);
		this._propertyMappers[name] = mapper || IDENTITY_MAPPER;
	}

	_mapProperty(name, value) {
		const mapper = this._propertyMappers[name];
		return mapper ? mapper(value) : value;
	}

	/**
	 * Indicate that you want to monitor defined properties.
	 */
	monitor(interval) {
		clearInterval(this._propertyMonitor);

		this._propertyMonitor = setInterval(this._loadProperties, interval || 30000);
		return this._loadProperties();
	}

	_loadProperties() {
		if(this._propertiesToMonitor.length === 0) return Promise.resolve();

		return this.getProperties(this._propertiesToMonitor)
			.then(values => {
				const oldValues = this._properties;
				Object.keys(values).forEach(key => {
					const oldValue = oldValues[key];
					const newValue = values[key];

					if(oldValue !== newValue) {
						debug('Property', key, 'changed from', oldValue, 'to', newValue);
						this.emit('propertyChanged', {
							property: key,
							oldValue: oldValue,
							value: newValue
						});
					}

					oldValues[key] = newValue;
				});
			})
			.catch(err => {
				debug('Unable to load properties', err && err.stack ? err.stack : err);
			});
	}

	stopMonitoring() {
		clearInterval(this._propertyMonitor);
	}

	property(key) {
		return this._properties[key];
	}

	getProperties(props) {
		return this.call('get_prop', props)
			.then(result => {
				const obj = {};
				for(let i=0; i<result.length; i++) {
					obj[props[i]] = this._mapProperty(props[i], result[i]);
				}
				return obj;
			});
	}

	/**
	 * Destroy this instance, this instance will no longer be able to
	 * communicate with the remote device it represents.
	 */
	destroy() {
		this.stopMonitoring();
		this.socket.close();
	}

	discover() {
		return this._ensureToken()
			.then(() => {
				return {
					token: this.packet.token
				}
			});
	}

	updateToken(token) {
		// Update the token used for this device
		this._hasFailedToken = false;

		if(typeof token === 'string') {
			this.packet.token = Buffer.from(token, 'hex');
		} else if(token instanceof Buffer) {
			this.packet.token = token;
		} else {
			throw new Error('Unknown type of token: ' + token);
		}

		// Reload properties when token is updated
		this._loadProperties();
	}
}

module.exports = Device;
<commit_msg>Adding writeOnly option<commit_after>'use strict';

const debug = require('debug')('miio');

const dgram = require('dgram');
const Packet = require('./packet');

const EventEmitter = require('events');

const DeviceManagement = require('./management');

const IDENTITY_MAPPER  = v => v;

const ERRORS = {
	'-5001': (method, args, err) => err.message == 'invalid_arg' ? 'Invalid argument' : err.message,
	'-10000': (method) => 'Method `' + method + '` is not supported'
};

class Device extends EventEmitter {
	constructor(options) {
		super();

		this.type = 'generic';
		this.model = options.model || 'unknown';

		this.address = options.address;
		this.port = options.port || 54321;
		this.writeOnly = options.writeOnly || false;

		this.packet = new Packet();
		if(typeof options.token === 'string') {
			this.packet.token = Buffer.from(options.token, 'hex');
		} else if(options.token instanceof Buffer) {
			this.packet.token = options.token;
		}

		this.socket = dgram.createSocket('udp4');
		this.socket.on('message', this._onMessage.bind(this));

		this._id = 0;
		this._promises = {};
		this._hasFailedToken = false;

		this._properties = {};
		this._propertiesToMonitor = [];
		this._propertyMappers = {};

		this._loadProperties = this._loadProperties.bind(this);

		this.management = new DeviceManagement(this);
	}

	_onMessage(msg) {
		try {
			this.packet.raw = msg;
		} catch(ex) {
			debug('<- Unable to parse packet', ex);
			return;
		}

		if(this._tokenResolve) {
			debug('<-', 'Handshake reply:', this.packet.checksum);
			this.packet.handleHandshakeReply();

			let resolve = this._tokenResolve;

			this._lastToken = Date.now();
			this._tokenResolve = null;
			this._tokenPromise = null;

			resolve();
		} else {
			let data = this.packet.data;
			if(! data) {
				debug('<-', null);
				return;
			}

			// Handle null-terminated strings
			if(data[data.length - 1] == 0) {
				data = data.slice(0, data.length - 1);
			}

			// Parse and handle the JSON message
			let str = data.toString('utf8');
			debug('<- Message: `' + str + '`');
			try {
				let object = JSON.parse(str);

				const p = this._promises[object.id];
				if(! p) return;
				if(typeof object.result !== 'undefined') {
					p.resolve(object.result);
				} else {
					p.reject(object.error);
				}
			} catch(ex) {
				debug('<- Invalid JSON');
			}
		}
	}

	_ensureToken() {
		if(! this.packet.needsHandshake) {
			return Promise.resolve();
		}

		if(this._hasFailedToken) {
			return Promise.reject(new Error('Token could not be auto-discovered'));
		}

		if(this._tokenPromise) {
			debug('Using existing promise');
			return this._tokenPromise;
		}

		this._tokenPromise = new Promise((resolve, reject) => {
			this.packet.handshake();
			const data = this.packet.raw;
			this.socket.send(data, 0, data.length, this.port, this.address, err => err && reject(err));
			this._tokenResolve = () => {
				if(this.packet.token) {
					resolve();
				} else {
					this._hasFailedToken = true;
					reject(new Error('Token could not be auto-discovered'));
				}
			};

			// Reject in 1 second
			setTimeout(() => {
				this._tokenPromise = null;
				reject();
			}, 1000);
		});
		return this._tokenPromise;
	}

	call(method, args, options) {
		if(args) {
			if(! Array.isArray(args) && typeof args !== 'object') {
				throw new Error('Arguments need to be an array or object');
			}
		} else {
			args = [];
		}

		const id = this._id = this._id == 10000 ? 1 : this._id + 1;
		const json = JSON.stringify({
			id: id,
			method: method,
			params: args
		});

		if(options && options.sid) {
			// If we have a sub-device set it (used by Lumi Smart Home Gateway)
			json.sid = options.sid;
		}

		return new Promise((resolve, reject) => {
			let resolved = false;
			const promise = this._promises[id] = {
				resolve: res => {
					resolved = true;
					delete this._promises[id];

					if(options && options.refresh) {
						// Special case for loading properties after setting values
						this._loadProperties()
							.then(() => resolve(res))
							.catch(() => resolve(res));
					} else {
						resolve(res);
					}
				},
				reject: err => {
					resolved = true;
					delete this._promises[id];

					if(! (err instanceof Error) && typeof err.code !== 'undefined') {
						const code = err.code;

						const handler = ERRORS[code];
						let msg;
						if(handler) {
							msg = handler(method, args, err.message);
						} else {
							msg = err.message || err.toString();
						}

						err = new Error(msg);
						err.code = code;
					}
					reject(err);
				}
			};

			let sendsLeft = 3;
			const send = () => {
				if(resolved) return;

				this._ensureToken()
					.then(() => {
						this.packet.data = Buffer.from(json, 'utf8');

						const data = this.packet.raw;

						debug('-> (' + sendsLeft + ')', json);
						this.socket.send(data, 0, data.length, this.port, this.address, err => err && reject(err));

						if(--sendsLeft > 0) {
							setTimeout(send, 2000);
						} else {
							promise.reject(new Error('Timeout'));
						}
					})
					.catch(reject);
			};

			send();
		});
	}

	/**
	 * Define a property and how the value should be mapped. All defined
	 * properties are monitored if #monitor() is called.
	 */
	defineProperty(name, mapper) {
		this._propertiesToMonitor.push(name);
		this._propertyMappers[name] = mapper || IDENTITY_MAPPER;
	}

	_mapProperty(name, value) {
		const mapper = this._propertyMappers[name];
		return mapper ? mapper(value) : value;
	}

	/**
	 * Indicate that you want to monitor defined properties.
	 */
	monitor(interval) {
		clearInterval(this._propertyMonitor);

		this._propertyMonitor = setInterval(this._loadProperties, interval || 30000);
		return this._loadProperties();
	}

	_loadProperties() {
		if(this._propertiesToMonitor.length === 0 || this.writeOnly) return Promise.resolve();

		return this.getProperties(this._propertiesToMonitor)
			.then(values => {
				const oldValues = this._properties;
				Object.keys(values).forEach(key => {
					const oldValue = oldValues[key];
					const newValue = values[key];

					if(oldValue !== newValue) {
						debug('Property', key, 'changed from', oldValue, 'to', newValue);
						this.emit('propertyChanged', {
							property: key,
							oldValue: oldValue,
							value: newValue
						});
					}

					oldValues[key] = newValue;
				});
			})
			.catch(err => {
				debug('Unable to load properties', err && err.stack ? err.stack : err);
			});
	}

	stopMonitoring() {
		clearInterval(this._propertyMonitor);
	}

	property(key) {
		return this._properties[key];
	}

	getProperties(props) {
		return this.call('get_prop', props)
			.then(result => {
				const obj = {};
				for(let i=0; i<result.length; i++) {
					obj[props[i]] = this._mapProperty(props[i], result[i]);
				}
				return obj;
			});
	}

	/**
	 * Destroy this instance, this instance will no longer be able to
	 * communicate with the remote device it represents.
	 */
	destroy() {
		this.stopMonitoring();
		this.socket.close();
	}

	discover() {
		return this._ensureToken()
			.then(() => {
				return {
					token: this.packet.token
				}
			});
	}

	updateToken(token) {
		// Update the token used for this device
		this._hasFailedToken = false;

		if(typeof token === 'string') {
			this.packet.token = Buffer.from(token, 'hex');
		} else if(token instanceof Buffer) {
			this.packet.token = token;
		} else {
			throw new Error('Unknown type of token: ' + token);
		}

		// Reload properties when token is updated
		this._loadProperties();
	}
}

module.exports = Device;
<|endoftext|>