<commit_before>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2015, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */

(function(Utils, API, VFS) {
  'use strict';

  var OSjs = window.OSjs = window.OSjs || {};
  OSjs.Helpers = OSjs.Helpers || {};

  var requestFileSystem = window.webkitRequestFileSystem || window.mozRequestFileSystem || window.requestFileSystem;
  var URL = window.webkitURL || window.mozURL || window.URL;

  function getEntries(file, callback) {
    zip.createReader(new zip.BlobReader(file), function(zipReader) {
      zipReader.getEntries(function(entries) {
        callback(entries);
      });
    }, function(message) {
      alert(message); // FIXME
    });
  }

  function getEntryFile(entry, onend, onprogress) {
    var writer = new zip.BlobWriter();
    entry.getData(writer, function(blob) {
      onend(blob);
      writer = null;
    }, onprogress);
  }

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  var SingletonInstance = null;

  /**
   * The GoogleAPI wrapper class
   *
   * This is a private class and can only be aquired through
   * OSjs.Helpers.ZipArchiver.createInsatance()
   *
   * Generally you want to create an instance of this helper
   * and when successfully created use `window.zip` use the instance helpers.
   *
   * @see OSjs.Helpers.ZipArchiver.createInsatance()
   * @api OSjs.Helpers.ZipArchiver.ZipArchiver
   *
   * @private
   * @class
   */
  function ZipArchiver(opts) {
    this.opts = opts;
    this.inited = false;
    this.preloads = [{
      type: 'javascript',
      src: '/vendor/zip.js/WebContent/zip.js'
    }];
  }

  ZipArchiver.prototype.init = function(cb) {
    cb = cb || function() {};

    if ( this.inited ) {
      cb();
      return;
    }

    var self = this;
    Utils.preload(this.preloads, function(total, failed) {
      if ( failed.length ) {
        cb('Failed to load zip.js', failed); // FIXME: Translation
        return;
      }

      if ( window.zip ) {
        zip.workerScriptsPath = '/vendor/zip.js/WebContent/';

        self.inited = true;
      }

      cb();
    });
  };

  /**
   * Lists contents of a ZIP file
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   Function          cb            Callback function => fn(error, entries)
   *
   * @return  void
   * @method  ZipArchiver::list()
   */
  ZipArchiver.prototype.list = function(file, cb) {
    VFS.download(file, function(error, result) {
      if ( error ) {
        alert(error);

        cb(error, null);
        return;
      }

      var blob = new Blob([result], {type: 'application/zip'});
      getEntries(blob, function(entries) {
        cb(false, entries);
      });
    });
  };

  /**
   * Create a new blank ZIP file
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   Function          cb            Callback function => fn(error)
   *
   * @return  void
   * @method  ZipArchiver::create()
   */
  ZipArchiver.prototype.create = function(file, cb) {
    var writer = new zip.BlobWriter();
    zip.createWriter(writer, function(writer) {
      writer.close(function(blob) {
        VFS.upload({
          destination: Utils.dirname(file.path),
          files: [
            {filename: Utils.filename(file.path), data: blob}
          ]
        }, function(type, ev) {
          if ( type === 'error' ) {
            console.warn('Error creating blank zip', ev);
          }
          writer = null;

          cb(type === 'error' ? ev : false, type !== 'error');
        }, {overwrite: true});
      });
    });
  };

  /**
   * Add a entry to the ZIP file
   *
   * @param   OSjs.VFS.File     file          Archive File
   * @param   OSjs.VFS.File     add           File to add
   * @param   Object            args          Arguments
   *
   * @option  args    Function    onprogress      Callback on progress => fn(state[, args, ...])
   * @option  args    Function    oncomplete      Callback on complete => fn(error, result)
   *
   * @return  void
   * @method  ZipArchiver::add()
   */
  ZipArchiver.prototype.add = function(file, add, args) {
    var cb = args.oncomplete || function() {};
    var pr = args.onprogress || function() {};
    var currentDir = args.path || '/';

    console.group('ZipArchiver::add()');
    console.log('Archive', file);
    console.log('Add', file);

    function finished(err, res) {
      console.groupEnd();
      cb(err, res);
    }

    function openFile(done) {
      console.log('-->', 'openFile()');

      VFS.download(file, function(error, data) {
        if ( error ) {
          console.warning('An error while opening zip', error);
          done(error);
          return;
        }

        var blob = new Blob([data], {type: add.mime});
        getEntries(blob, function(result) {
          done(false, result || []);
        });
      });
    }

    function checkIfExists(entries, done) {
      console.log('-->', 'checkIfExists()');

      done(false);
    }

    function createZip(done) {
      console.log('-->', 'createZip()');

      var writer = new zip.BlobWriter();
      zip.createWriter(writer, function(writer) {
        done(false, writer);
      }, function(error) {
        done(error);
      });
    }

    function importFiles(writer, entries, done) {
      console.log('-->', 'importFiles()', entries);

      function _next(index) {
        if ( !entries.length || index >= entries.length ) {
          done(false);
          return;
        }

        var current = entries[index];
        console.log('Importing', index, current);
        getEntryFile(current, function(blob) {
          writer.add(current.filename, new zip.BlobReader(blob), function() {
            pr('added', index, current);
            _next(index+1);
          }, function(current, total) {
            pr('reading', index, total, current);
          }, {
            directory: current.directory,
            lastModDate: current.lastModDate,
            version: current.version
          });
        });
      }

      _next(0);
    }

    function addFile(writer, done) {
      var filename = add instanceof window.File ? add.name : add.filename;
      var type = add instanceof window.File ? 'file' : (add.type || 'file');

      console.log('-->', 'addFile()', filename, type);

      filename = ((currentDir || '/').replace(/\/$/, '') + '/' + filename).replace(/^\//, '');

      function _saveChanges(ccb) {
        console.log('-->', 'addFile()', '-->', '_saveChanges()');

        writer.close(function(blob) {
          VFS.upload({
            destination: Utils.dirname(file.path),
            files: [{filename: Utils.filename(file.path), data: blob}]
          }, function(type, ev) {
            var error = (type === 'error') ? ev : false;
            ccb(error, !!error);
          }, {overwrite: true});
        });
      }

      function _addBlob(blob) {
        console.log('-->', 'addFile()', '-->', '_addBlob()');

        writer.add(filename, new zip.BlobReader(blob), function() {
          console.log('ADDED FILE', filename);

          _saveChanges(done);
        }, function(current, total) {
          pr('compressing', current);
        });
      }

      function _addFolder() {
        console.log('-->', 'addFile()', '-->', '_addFolder()');
        writer.add(filename, null, function() {
          console.log('ADDED FOLDER', filename);

          _saveChanges(done);
        }, null, {directory: true});
      }

      if ( type === 'dir' ) {
        _addFolder();
      } else {
        if ( add instanceof window.File ) {
          _addBlob(add);
        } else {
          VFS.download(add, function(error, data) {
            if ( error ) {
              done(error);
              return;
            }

            var blob = new Blob([data], {type: add.mime});
            _addBlob(blob);
          });
        }
      }
    }

    // Proceed!
    openFile(function(err, entries) {
      if ( err ) { finished(err); return; }

      checkIfExists(entries, function(err, result) {
        if ( err ) { finished(err); return; }

        createZip(function(err, writer) {
          if ( err ) { finished(err); return; }

          importFiles(writer, entries, function(err) {
            if ( err ) { finished(err); return; }
            addFile(writer, function(err) {
              finished(err, !!err);
            });
          });
        });
      });
    });

  };

  /**
   * Creates a new directory inside ZIP file
   *
   * TODO
   *
   * @param   OSjs.VFS.File     file          Archive File
   * @param   String            path          Path
   * @param   Function          cb            Callback function => fn(err, result)
   *
   * @return  void
   * @method  ZipArchiver::mkdir()
   */
  ZipArchiver.prototype.mkdir = function(file, path, cb) {
    cb('Not implemented yet');
  };

  /**
   * Removes an entry from ZIP file
   *
   * TODO
   *
   * @param   OSjs.VFS.File     file          Archive File
   * @param   String            path          Path
   * @param   Function          cb            Callback function => fn(err, result)
   *
   * @return  void
   * @method  ZipArchiver::remove()
   */
  ZipArchiver.prototype.remove = function(file, path, cb) {
    cb('Not implemented yet');
  };

  /**
   * Extract a File to destination
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   String            destination   Destination path
   * @param   Object            args          Arguments
   *
   * @option  args    Function    onprogress      Callback on progress => fn(filename, currentIndex, totalIndex)
   * @option  args    Function    oncomplete      Callback on complete => fn(error, warnings, result)
   *
   * @return  void
   * @method  ZipArchiver::extract()
   */
  ZipArchiver.prototype.extract = function(file, destination, args) {
    args = args || {};

    args.onprogress = args.onprogress || function(/*filename, current, total*/) {};
    args.oncomplete = args.oncomplete || function(/*error, warnings, result*/) {};

    console.group('ZipArchiver::extract()');
    console.log('Archive', file);
    console.log('Destination', destination);

    function finished(error, warnings, result) {
      console.groupEnd();
      args.oncomplete(error, warnings, result);
    }

    var extracted = [];
    var warnings = [];
    var total = 0;

    function _extractList(list, destination) {
      total = list.length;
      console.debug('ZipArchiver::extract()', 'Extracting', total, 'item(s)');

      var index = 0;

      function _extract(item, cb) {
        args.onprogress(item.filename, index, total);

        var dest = destination;
        if ( item.filename.match(/\//) ) {
          if ( item.directory ) {
            dest += '/' + item.filename;
          } else {
            dest += '/' + Utils.dirname(item.filename);
          }
        }

        console.log('Extract', item, dest);
        if ( item.directory ) {
          VFS.mkdir(new VFS.File(dest), function(error, result) {
            if ( error ) {
              warnings.push(Utils.format('Could not create directory "{0}": {1}', item.filename, error));
            } else {
              extracted.push(item.filename);
            }

            cb();
          });
          return;
        }

        getEntryFile(item, function(blob) {
          console.log('....', blob);
          VFS.upload({
            destination: dest,
            files: [{filename: Utils.filename(item.filename), data: blob}]
          }, function(type, ev) { // error, result, ev
            console.warn('ZipArchiver::extract()', '_extract()', 'upload', type, ev);

            if ( type === 'error' ) {
              warnings.push(Utils.format('Could not extract "{0}": {1}', item.filename, ev));
            } else {
              extracted.push(item.filename);
            }

            cb();
          });

        }, function() {
        });
      }

      function _finished() {
        console.log('Extract finished', total, 'total', extracted.length, 'extracted', extracted);
        console.log(warnings.length, 'warnings', warnings);
        finished(false, warnings, true);
      }

      function _next() {
        if ( !list.length || index >= list.length ) {
          return _finished();
        }

        _extract(list[index], function() {
          index++;
          _next();
        });
      }

      _next();
    }

    function _checkDirectory(destination, cb) {
      console.debug('ZipArchiver::extract()', 'Checking destination');

      var dst = new VFS.File({path: destination, type: 'dir'});
      VFS.mkdir(dst, function(error, result) {
        VFS.exists(dst, function(err, result) {
          if ( result ) {
            cb(false);
          } else {
            cb('Destination directory was not created or does not exist');
          }
        });
      });
    }

    console.debug('ZipArchiver::extract()', 'Downloading file...');

    VFS.download(file, function(error, result) {
      if ( error ) {
        finished(error, warnings, false);
        return;
      }

      var blob = new Blob([result], {type: 'application/zip'});
      _checkDirectory(destination, function(err) {

        if ( err ) {
          finished(error, warnings, false);
          return;
        }

        getEntries(blob, function(entries) {
          _extractList(entries, destination);
        });
      });
    });
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Helpers.ZipArchiver = OSjs.Helpers.ZipArchiver || {};

  /**
   * Gets the currently running instance
   *
   * @api OSjs.Helpers.ZipArchiver.getInstance()
   *
   * @return  ZipArchiver       Can also be null
   */
  OSjs.Helpers.ZipArchiver.getInstance = function() {
    return SingletonInstance;
  };

  /**
   * Create an instance of ZipArchiver
   *
   * @param   Object    args      Arguments
   * @param   Function  callback  Callback function => fn(error, instance)
   *
   * @option  args    Array     scope     What scope to load
   *
   * @api OSjs.Helpers.ZipArchiver.createInstance()
   *
   * @return  void
   */
  OSjs.Helpers.ZipArchiver.createInstance = function(args, callback) {
    args = args || {};
    if ( !SingletonInstance ) {
      SingletonInstance = new ZipArchiver(args);
    }

    SingletonInstance.init(function(error) {
      if ( !error ) {
        if ( !window.zip ) {
          error = 'zip.js library was not found. Did it load properly?'; // FIXME: Translation
        }
      }
      callback(error, error ? false : SingletonInstance);
    });
  };

})(OSjs.Utils, OSjs.API, OSjs.VFS);
<commit_msg>zip-archiver: Improvmements<commit_after>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2015, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */

(function(Utils, API, VFS) {
  'use strict';

  var OSjs = window.OSjs = window.OSjs || {};
  OSjs.Helpers = OSjs.Helpers || {};

  var requestFileSystem = window.webkitRequestFileSystem || window.mozRequestFileSystem || window.requestFileSystem;
  var URL = window.webkitURL || window.mozURL || window.URL;

  function getEntries(file, callback) {
    zip.createReader(new zip.BlobReader(file), function(zipReader) {
      zipReader.getEntries(function(entries) {
        callback(entries);
      });
    }, function(message) {
      alert(message); // FIXME
    });
  }

  function getEntryFile(entry, onend, onprogress) {
    var writer = new zip.BlobWriter();
    entry.getData(writer, function(blob) {
      onend(blob);
      writer = null;
    }, onprogress);
  }

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  var SingletonInstance = null;

  /**
   * The GoogleAPI wrapper class
   *
   * This is a private class and can only be aquired through
   * OSjs.Helpers.ZipArchiver.createInsatance()
   *
   * Generally you want to create an instance of this helper
   * and when successfully created use `window.zip` use the instance helpers.
   *
   * @see OSjs.Helpers.ZipArchiver.createInsatance()
   * @api OSjs.Helpers.ZipArchiver.ZipArchiver
   *
   * @private
   * @class
   */
  function ZipArchiver(opts) {
    this.opts = opts;
    this.inited = false;
    this.preloads = [{
      type: 'javascript',
      src: '/vendor/zip.js/WebContent/zip.js'
    }];
  }

  ZipArchiver.prototype.init = function(cb) {
    cb = cb || function() {};

    if ( this.inited ) {
      cb();
      return;
    }

    var self = this;
    Utils.preload(this.preloads, function(total, failed) {
      if ( failed.length ) {
        cb('Failed to load zip.js', failed); // FIXME: Translation
        return;
      }

      if ( window.zip ) {
        zip.workerScriptsPath = '/vendor/zip.js/WebContent/';

        self.inited = true;
      }

      cb();
    });
  };

  /**
   * Lists contents of a ZIP file
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   Function          cb            Callback function => fn(error, entries)
   *
   * @return  void
   * @method  ZipArchiver::list()
   */
  ZipArchiver.prototype.list = function(file, cb) {
    VFS.download(file, function(error, result) {
      if ( error ) {
        alert(error);

        cb(error, null);
        return;
      }

      var blob = new Blob([result], {type: 'application/zip'});
      getEntries(blob, function(entries) {
        cb(false, entries);
      });
    });
  };

  /**
   * Create a new blank ZIP file
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   Function          cb            Callback function => fn(error)
   *
   * @return  void
   * @method  ZipArchiver::create()
   */
  ZipArchiver.prototype.create = function(file, cb) {
    var writer = new zip.BlobWriter();
    zip.createWriter(writer, function(writer) {
      writer.close(function(blob) {
        VFS.upload({
          destination: Utils.dirname(file.path),
          files: [
            {filename: Utils.filename(file.path), data: blob}
          ]
        }, function(type, ev) {
          if ( type === 'error' ) {
            console.warn('Error creating blank zip', ev);
          }
          writer = null;

          cb(type === 'error' ? ev : false, type !== 'error');
        }, {overwrite: true});
      });
    });
  };

  /**
   * Add a entry to the ZIP file
   *
   * TODO: Adding directory does not actually add files inside dirs yet
   *
   * @param   OSjs.VFS.File     file          Archive File
   * @param   OSjs.VFS.File     add           File to add
   * @param   Object            args          Arguments
   *
   * @option  args    String      path            Root path to add to (default='/')
   * @option  args    Function    onprogress      Callback on progress => fn(state[, args, ...])
   * @option  args    Function    oncomplete      Callback on complete => fn(error, result)
   *
   * @return  void
   * @method  ZipArchiver::add()
   */
  ZipArchiver.prototype.add = function(file, add, args) {
    var cb = args.oncomplete || function() {};
    var pr = args.onprogress || function() {};
    var currentDir = args.path || '/';

    console.group('ZipArchiver::add()');
    console.log('Archive', file);
    console.log('Add', file);

    function finished(err, res) {
      console.groupEnd();
      cb(err, res);
    }

    function openFile(done) {
      console.log('-->', 'openFile()');

      VFS.download(file, function(error, data) {
        if ( error ) {
          console.warning('An error while opening zip', error);
          done(error);
          return;
        }

        var blob = new Blob([data], {type: add.mime});
        getEntries(blob, function(result) {
          done(false, result || []);
        });
      });
    }

    function checkIfExists(entries, done) {
      console.log('-->', 'checkIfExists()');

      var found = false;
      var chk = Utils.filename(add.path);

      entries.forEach(function(i) {
        if ( i.filename === chk ) {
          if ( !i.directory || (i.directory && add.type === 'dir') ) {
            found = true;
          }
        }
        return !found;
      });

      done(found ? 'File is already in archive' : false);
    }

    function createZip(done) {
      console.log('-->', 'createZip()');

      var writer = new zip.BlobWriter();
      zip.createWriter(writer, function(writer) {
        done(false, writer);
      }, function(error) {
        done(error);
      });
    }

    function importFiles(writer, entries, done) {
      console.log('-->', 'importFiles()', entries);

      function _next(index) {
        if ( !entries.length || index >= entries.length ) {
          done(false);
          return;
        }

        var current = entries[index];
        console.log('Importing', index, current);
        getEntryFile(current, function(blob) {
          writer.add(current.filename, new zip.BlobReader(blob), function() {
            pr('added', index, current);
            _next(index+1);
          }, function(current, total) {
            pr('reading', index, total, current);
          }, {
            directory: current.directory,
            lastModDate: current.lastModDate,
            version: current.version
          });
        });
      }

      _next(0);
    }

    function addFile(writer, done) {
      var filename = add instanceof window.File ? add.name : add.filename;
      var type = add instanceof window.File ? 'file' : (add.type || 'file');

      console.log('-->', 'addFile()', filename, type, add);

      filename = ((currentDir || '/').replace(/\/$/, '') + '/' + filename).replace(/^\//, '');

      function _saveChanges(ccb) {
        console.log('-->', 'addFile()', '-->', '_saveChanges()');

        writer.close(function(blob) {
          VFS.upload({
            destination: Utils.dirname(file.path),
            files: [{filename: Utils.filename(file.path), data: blob}]
          }, function(type, ev) {
            var error = (type === 'error') ? ev : false;
            ccb(error, !!error);
          }, {overwrite: true});
        });
      }

      function _addBlob(blob) {
        console.log('-->', 'addFile()', '-->', '_addBlob()');

        writer.add(filename, new zip.BlobReader(blob), function() {
          console.log('ADDED FILE', filename);

          _saveChanges(done);
        }, function(current, total) {
          pr('compressing', current);
        });
      }

      function _addFolder() {
        console.log('-->', 'addFile()', '-->', '_addFolder()');
        writer.add(filename, null, function() {
          console.log('ADDED FOLDER', filename);

          _saveChanges(done);
        }, null, {directory: true});
      }

      if ( type === 'dir' ) {
        _addFolder();
      } else {
        if ( add instanceof window.File ) {
          _addBlob(add);
        } else {
          VFS.download(add, function(error, data) {
            if ( error ) {
              done(error);
              return;
            }

            var blob = new Blob([data], {type: add.mime});
            _addBlob(blob);
          });
        }
      }
    }

    // Proceed!
    openFile(function(err, entries) {
      if ( err ) { finished(err); return; }

      checkIfExists(entries, function(err) {
        if ( err ) { finished(err); return; }

        createZip(function(err, writer) {
          if ( err ) { finished(err); return; }

          importFiles(writer, entries, function(err) {
            if ( err ) { finished(err); return; }
            addFile(writer, function(err) {
              finished(err, !!err);
            });
          });
        });
      });
    });

  };

  /**
   * Removes an entry from ZIP file
   *
   * TODO
   *
   * @param   OSjs.VFS.File     file          Archive File
   * @param   String            path          Path
   * @param   Function          cb            Callback function => fn(err, result)
   *
   * @return  void
   * @method  ZipArchiver::remove()
   */
  ZipArchiver.prototype.remove = function(file, path, cb) {
    cb('Not implemented yet');
  };

  /**
   * Extract a File to destination
   *
   * @param   OSjs.VFS.File     file          File to extract
   * @param   String            destination   Destination path
   * @param   Object            args          Arguments
   *
   * @option  args    Function    onprogress      Callback on progress => fn(filename, currentIndex, totalIndex)
   * @option  args    Function    oncomplete      Callback on complete => fn(error, warnings, result)
   *
   * @return  void
   * @method  ZipArchiver::extract()
   */
  ZipArchiver.prototype.extract = function(file, destination, args) {
    args = args || {};

    args.onprogress = args.onprogress || function(/*filename, current, total*/) {};
    args.oncomplete = args.oncomplete || function(/*error, warnings, result*/) {};

    console.group('ZipArchiver::extract()');
    console.log('Archive', file);
    console.log('Destination', destination);

    function finished(error, warnings, result) {
      console.groupEnd();
      args.oncomplete(error, warnings, result);
    }

    var extracted = [];
    var warnings = [];
    var total = 0;

    function _extractList(list, destination) {
      total = list.length;
      console.debug('ZipArchiver::extract()', 'Extracting', total, 'item(s)');

      var index = 0;

      function _extract(item, cb) {
        args.onprogress(item.filename, index, total);

        var dest = destination;
        if ( item.filename.match(/\//) ) {
          if ( item.directory ) {
            dest += '/' + item.filename;
          } else {
            dest += '/' + Utils.dirname(item.filename);
          }
        }

        console.log('Extract', item, dest);
        if ( item.directory ) {
          VFS.mkdir(new VFS.File(dest), function(error, result) {
            if ( error ) {
              warnings.push(Utils.format('Could not create directory "{0}": {1}', item.filename, error));
            } else {
              extracted.push(item.filename);
            }

            cb();
          });
          return;
        }

        getEntryFile(item, function(blob) {
          console.log('....', blob);
          VFS.upload({
            destination: dest,
            files: [{filename: Utils.filename(item.filename), data: blob}]
          }, function(type, ev) { // error, result, ev
            console.warn('ZipArchiver::extract()', '_extract()', 'upload', type, ev);

            if ( type === 'error' ) {
              warnings.push(Utils.format('Could not extract "{0}": {1}', item.filename, ev));
            } else {
              extracted.push(item.filename);
            }

            cb();
          });

        }, function() {
        });
      }

      function _finished() {
        console.log('Extract finished', total, 'total', extracted.length, 'extracted', extracted);
        console.log(warnings.length, 'warnings', warnings);
        finished(false, warnings, true);
      }

      function _next() {
        if ( !list.length || index >= list.length ) {
          return _finished();
        }

        _extract(list[index], function() {
          index++;
          _next();
        });
      }

      _next();
    }

    function _checkDirectory(destination, cb) {
      console.debug('ZipArchiver::extract()', 'Checking destination');

      var dst = new VFS.File({path: destination, type: 'dir'});
      VFS.mkdir(dst, function(error, result) {
        VFS.exists(dst, function(err, result) {
          if ( result ) {
            cb(false);
          } else {
            cb('Destination directory was not created or does not exist');
          }
        });
      });
    }

    console.debug('ZipArchiver::extract()', 'Downloading file...');

    VFS.download(file, function(error, result) {
      if ( error ) {
        finished(error, warnings, false);
        return;
      }

      var blob = new Blob([result], {type: 'application/zip'});
      _checkDirectory(destination, function(err) {

        if ( err ) {
          finished(error, warnings, false);
          return;
        }

        getEntries(blob, function(entries) {
          _extractList(entries, destination);
        });
      });
    });
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Helpers.ZipArchiver = OSjs.Helpers.ZipArchiver || {};

  /**
   * Gets the currently running instance
   *
   * @api OSjs.Helpers.ZipArchiver.getInstance()
   *
   * @return  ZipArchiver       Can also be null
   */
  OSjs.Helpers.ZipArchiver.getInstance = function() {
    return SingletonInstance;
  };

  /**
   * Create an instance of ZipArchiver
   *
   * @param   Object    args      Arguments
   * @param   Function  callback  Callback function => fn(error, instance)
   *
   * @option  args    Array     scope     What scope to load
   *
   * @api OSjs.Helpers.ZipArchiver.createInstance()
   *
   * @return  void
   */
  OSjs.Helpers.ZipArchiver.createInstance = function(args, callback) {
    args = args || {};
    if ( !SingletonInstance ) {
      SingletonInstance = new ZipArchiver(args);
    }

    SingletonInstance.init(function(error) {
      if ( !error ) {
        if ( !window.zip ) {
          error = 'zip.js library was not found. Did it load properly?'; // FIXME: Translation
        }
      }
      callback(error, error ? false : SingletonInstance);
    });
  };

})(OSjs.Utils, OSjs.API, OSjs.VFS);
<|endoftext|>


<commit_before>import React from 'react';
import exposeRouter from '../components/exposerouter.react';
import PureComponent from '../components/purecomponent.react';

const pagesAround = 5;
const steps = 10;
const productPerPage = 48;

class PaginationLinks extends PureComponent {
  handleClick(e, page) {
    e.preventDefault();
    const router = this.props.router;
    const query = this.props.query;
    if (query.page !== page) {
      query.page = page;
      router.transitionTo(this.props.path, {}, query);
    }
  }

  hrefForPage(page) {
    const router = this.props.router;
    const query = Object.assign({}, this.props.query); //clone query to avoid mutating it
    query.page = page;
    return router.makeHref(this.props.path, {}, this.props.query);
  }

  static getPages(page, total) {
    const pages = [];
    for (var i = 1; i <= total; i++) {
      if (
        i === 1 ||
        i % steps === 0 ||
        page - pagesAround <= i && i <= page + pagesAround ||
        i === total) {
        pages.push(i);
      }
    }
    return pages;
  }

  render() {
    const totalPages = Math.ceil(this.props.total / productPerPage);
    const currentPage = parseInt(this.props.query.page);
    return (

{PaginationLinks.getPages(currentPage, totalPages).map((page) =>

               this.handleClick(e, page)}>{page}

        )}

    );
  }
}

export default exposeRouter(PaginationLinks);

PaginationLinks.propTypes = {
  router: React.PropTypes.func.isRequired,
  path: React.PropTypes.string.isRequired,
  query: React.PropTypes.object.isRequired,
  total: React.PropTypes.number.isRequired
};
<commit_msg>Fix pagination links not working without javascript<commit_after>import React from 'react';
import exposeRouter from '../components/exposerouter.react';
import PureComponent from '../components/purecomponent.react';

const pagesAround = 5;
const steps = 10;
const productPerPage = 48;

class PaginationLinks extends PureComponent {
  handleClick(e, page) {
    e.preventDefault();
    const router = this.props.router;
    const query = this.props.query;
    if (query.page !== page) {
      query.page = page;
      router.transitionTo(this.props.path, {}, query);
    }
  }

  hrefForPage(page) {
    const router = this.props.router;
    const query = Object.assign({}, this.props.query); //clone query to avoid mutating it
    query.page = page;
    return router.makeHref(this.props.path, {}, query);
  }

  static getPages(page, total) {
    const pages = [];
    for (var i = 1; i <= total; i++) {
      if (
        i === 1 ||
        i % steps === 0 ||
        page - pagesAround <= i && i <= page + pagesAround ||
        i === total) {
        pages.push(i);
      }
    }
    return pages;
  }

  render() {
    const totalPages = Math.ceil(this.props.total / productPerPage);
    const currentPage = parseInt(this.props.query.page);
    return (

{PaginationLinks.getPages(currentPage, totalPages).map((page) =>

               this.handleClick(e, page)}>{page}

        )}

    );
  }
}

export default exposeRouter(PaginationLinks);

PaginationLinks.propTypes = {
  router: React.PropTypes.func.isRequired,
  path: React.PropTypes.string.isRequired,
  query: React.PropTypes.object.isRequired,
  total: React.PropTypes.number.isRequired
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import autobind from 'core-decorators/es/autobind';
import { connect } from 'react-redux';
import { graphql } from 'react-apollo';
import { getUserByEmail } from '../../../../graphql/user';
import ContactsEvent from './ContactsEvent';
import ContactsTeam from './ContactsTeam';

const mapStateToProps = (state, ownProps) => {
	return { email: state.auth.login.email }
}

const QueryGetUserOptions = {
	name: 'QueryGetUser',
	options: (props) => {
		return {
			fetchPolicy: 'cache-and-network',
			variables: { email: props.email }
		}
	}
}

@connect(mapStateToProps)
@graphql(getUserByEmail('_id teamId'), QueryGetUserOptions)
@autobind
class Contacts extends React.Component {
	render() {
		const { QueryTelEventCoordinator, QueryTelFirstAidOfficer, QueryTelWebsiteAdmin } = this.props;
		return (

Important contacts

In case of emergency

							Please call 000, then notify the event co‑ordinator

								(pressing on links in blue on this page will CALL the number).

					<ContactsEvent/>

					{
						this.props.QueryGetUser.getUserByEmail ?
						<ContactsTeam teamId={this.props.QueryGetUser.getUserByEmail.teamId}/> : null
					}

		);
	}
}

export default Contacts;
<commit_msg>[FIX] Rename contacts page id<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import autobind from 'core-decorators/es/autobind';
import { connect } from 'react-redux';
import { graphql } from 'react-apollo';
import { getUserByEmail } from '../../../../graphql/user';
import ContactsEvent from './ContactsEvent';
import ContactsTeam from './ContactsTeam';

const mapStateToProps = (state, ownProps) => {
	return { email: state.auth.login.email }
}

const QueryGetUserOptions = {
	name: 'QueryGetUser',
	options: (props) => {
		return {
			fetchPolicy: 'cache-and-network',
			variables: { email: props.email }
		}
	}
}

@connect(mapStateToProps)
@graphql(getUserByEmail('_id teamId'), QueryGetUserOptions)
@autobind
class Contacts extends React.Component {
	render() {
		const { QueryTelEventCoordinator, QueryTelFirstAidOfficer, QueryTelWebsiteAdmin } = this.props;
		return (

Important contacts

In case of emergency

							Please call 000, then notify the event co‑ordinator

								(pressing on links in blue on this page will CALL the number).

					<ContactsEvent/>

					{
						this.props.QueryGetUser.getUserByEmail ?
						<ContactsTeam teamId={this.props.QueryGetUser.getUserByEmail.teamId}/> : null
					}

		);
	}
}

export default Contacts;
<|endoftext|>


<commit_before>'use strict';

const Format = require('util').format;

const EPOCH_OFFSET = 978307200;

const TYPE_ENERGY  = 'energy',
      TYPE_ROOM    = 'room',
      TYPE_WEATHER = 'weather';

var homebridge;
var Characteristic, Service;

module.exports = function(pHomebridge) {
    if (pHomebridge && !homebridge) {
        homebridge = pHomebridge;
        Characteristic = homebridge.hap.Characteristic;
        Service = homebridge.hap.Service;
    }

    var hexToBase64 = function(val) {
        return new Buffer((''+val).replace(/[^0-9A-F]/ig, ''), 'hex').toString('base64');
    }, base64ToHex = function(val) {
        if(!val) return val;
        return new Buffer(val, 'base64').toString('hex');
    }, swap16 = function (val) {
        return ((val & 0xFF) << 8)
            | ((val >>> 8) & 0xFF);
    }, swap32 = function (val) {
        return ((val & 0xFF) << 24)
           | ((val & 0xFF00) << 8)
           | ((val >>> 8) & 0xFF00)
           | ((val >>> 24) & 0xFF);
    }, hexToHPA = function(val) {
        return parseInt(swap16(val), 10);
    }, hPAtoHex = function(val) {
        return swap16(Math.round(val)).toString(16);
    }, numToHex = function(val, len) {
        var s = Number(val >>> 0).toString(16);
        if(s.length % 2 != 0) {
            s = '0' + s;
        }
        if(len) {
            return ('0000000000000' + s).slice(-1 * len);
        }
    return s;
}

    class S2R1Characteristic extends Characteristic {
        constructor() {
            super('S2R1', S2R1Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2R1Characteristic.UUID = 'E863F116-079E-48FF-8F27-9C2605A29F52';

    class S2R2Characteristic extends Characteristic {
        constructor() {
            super('S2R2', S2R2Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2R2Characteristic.UUID = 'E863F117-079E-48FF-8F27-9C2605A29F52';

    class S2W1Characteristic extends Characteristic {
        constructor() {
            super('S2W1', S2W1Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.WRITE, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2W1Characteristic.UUID = 'E863F11C-079E-48FF-8F27-9C2605A29F52';

    class S2W2Characteristic extends Characteristic {
        constructor() {
            super('S2W2', S2W2Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.WRITE, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2W2Characteristic.UUID = 'E863F121-079E-48FF-8F27-9C2605A29F52';

    class FakeGatoHistoryService extends Service {
        constructor(accessoryType, accessory, size) {
            if (typeof size === 'undefined') { size = 4032; }

            super(accessory.displayName + " History", FakeGatoHistoryService.UUID);

            var entry2address = function(val) {
                var temp = val % this.memorySize;
                return temp;
            }.bind(this);

            this.log = accessory.log;
            switch (accessoryType) {
                case TYPE_WEATHER:
                    this.accessoryType116 = "03";
                    this.accessoryType117 = "07";
                    break;
                case TYPE_ENERGY:
                    this.accessoryType116 = "07";
                    this.accessoryType117 = "1f";
                    break;
                case TYPE_ROOM:
                    this.accessoryType116 = "04";
                    this.accessoryType117 = "0f";
                    break;
            }

            this.accessoryType = accessoryType;
            this.firstEntry = 0;
            this.lastEntry = 0;
            this.history = [];
            this.memorySize = size;
            this.usedMemory = 0;
            this.currentEntry = 1;
            this.transfer = false;
            this.setTime = true;
            this.refTime = 0;
            this.memoryAddress = 0;
            this.dataStream = '';

            this.addCharacteristic(S2R1Characteristic);

            this.addCharacteristic(S2R2Characteristic)
                .on('get', (callback) => {
                    if ((this.currentEntry < this.lastEntry) && (this.transfer == true)) {
                        this.memoryAddress = entry2address(this.currentEntry);

                        if ((this.history[this.memoryAddress].temp == 0 &&
                            this.history[this.memoryAddress].pressure == 0 &&
                            this.history[this.memoryAddress].humidity == 0) || (this.history[this.memoryAddress].power == 0xFFFF) || (this.setTime == true)) {

                            var val = Format(
                                '15%s 0000 0000 81%s0000 0000 00 0000',
                                numToHex(swap32(this.currentEntry),8),
                                numToHex(swap32(this.refTime),8)
                            );

                            this.log.debug("Data %s: %s", this.accessoryType, val);
                            callback(null, hexToBase64(val));
                            this.setTime=false;
                            this.currentEntry++;
                        }
                        else {
                            for (var i = 0;i < 11;i++) {
                                switch (this.accessoryType) {
                                    case TYPE_WEATHER:
                                        this.log.debug("%s Entry: %s, Address: %s", this.accessoryType, this.currentEntry, this.memoryAddress);
                                        this.dataStream += Format(
                                            " 10 %s%s%s%s%s%s",
                                            numToHex(swap32(this.currentEntry), 8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
                                            numToHex(swap16(this.history[this.memoryAddress].humidity*100), 4),
                                            numToHex(swap16(this.history[this.memoryAddress].pressure*10), 4)
                                        );
                                        break;
                                    case TYPE_ENERGY:
                                        this.log.debug("%s Entry: %s, Address: %s", this.accessoryType, this.currentEntry, this.memoryAddress);
                                        this.dataStream += Format(
                                            " 14 %s%s%s0000 0000%s0000 0000",
                                            numToHex(swap32(this.currentEntry),8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].power * 10), 4)
                                        );
                                        break;
                                }
                                this.currentEntry++;
                                this.memoryAddress = entry2address(this.currentEntry);
                                if (this.currentEntry > this.lastEntry) {
                                    break;
                                }
                            }
                            this.log.debug("Data %s: %s", this.accessoryType, this.dataStream);
                            callback(null, hexToBase64(this.dataStream));
                            this.dataStream = '';
                        }
                    }
                    else {
                        this.transfer = false;
                        callback(null, hexToBase64('00'));
                    }
            });

            this.addCharacteristic(S2W1Characteristic)
                .on('set', this.setCurrentS2W1.bind(this));

            this.addCharacteristic(S2W2Characteristic)
                .on('set', this.setCurrentS2W2.bind(this));
        }

        sendHistory(address){
            var hexAddress= address.toString('16');
            if (address != 0) {
                this.currentEntry = address;
            }
            else {
                this.currentEntry = 1;
            }
            this.transfer=true;
        }

        //in order to be consistent with Eve, entry address start from 1
        addEntry(entry){

            var entry2address = function(val) {
                return val % this.memorySize;
            }.bind(this);

            if (this.usedMemory < this.memorySize) {
                this.usedMemory++;
                this.firstEntry = 0;
                this.lastEntry = this.usedMemory;
            }
            else {
                this.firstEntry++;
                this.lastEntry = this.firstEntry + this.usedMemory;
            }

            if (this.refTime == 0) {
                this.refTime = entry.time - EPOCH_OFFSET;
                switch (this.accessoryType)
                    {
                        case TYPE_WEATHER:
                            this.history[this.lastEntry]= {time: entry.time, temp: 0, pressure: 0, humidity: 0};
                            break;
                        case TYPE_ENERGY:
                            this.history[this.lastEntry]= {time: entry.time, power: 0xFFFF};
                            break;
                    }
                this.lastEntry++;
                this.usedMemory++;
            }

            this.history[entry2address(this.lastEntry)] = (entry);

            var val = Format(
                '%s00000000%s0401020202%s020f03%s%s%s000000000101',
                numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8),
                numToHex(swap32(this.refTime), 8), this.accessoryType116,
                numToHex(swap16(this.usedMemory), 4),
                numToHex(swap16(this.memorySize), 4),
                numToHex(swap32(this.firstEntry), 8)
            );

            this.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val));

            this.log.debug("First entry %s: %s", this.accessoryType, this.firstEntry.toString(16));
            this.log.debug("Last entry %s: %s", this.accessoryType, this.lastEntry.toString(16));
            this.log.debug("Used memory %s: %s", this.accessoryType, this.usedMemory.toString(16));
            this.log.debug("116 %s: %s", this.accessoryType, val);
        }

        setCurrentS2W1(val, callback) {
            callback(null,val);
            this.log.debug("Data request %s: %s", this.accessoryType, base64ToHex(val));
            var valHex = base64ToHex(val);
            var substring = valHex.substring(4, 12);
            var valInt = parseInt(substring, 16);
            var address = swap32(valInt);
            var hexAddress= address.toString('16');

            this.log.debug("Address requested %s: %s", this.accessoryType, hexAddress);
            //if (this.transfer==false)
            //{
                //this.transfer=true;
                this.sendHistory(address);
            //}
        }

        setCurrentS2W2(val, callback) {
            this.log.debug("Clock adjust: %s", base64ToHex(val));
            callback(null, val);
        }

    }

    FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52';

    return FakeGatoHistoryService;
}
<commit_msg>added Door, Motion, Thermo. Updated protocol for Weather and Energy. TO BE TESTED<commit_after>'use strict';

const Format = require('util').format;

const EPOCH_OFFSET = 978307200;

const TYPE_ENERGY  = 'energy',
      TYPE_ROOM    = 'room',
      TYPE_WEATHER = 'weather',
      TYPE_DOOR = 'door',
      TYPE_MOTION = 'motion',
      TYPE_THERMO = 'thermo';

var homebridge;
var Characteristic, Service;

module.exports = function(pHomebridge) {
    if (pHomebridge && !homebridge) {
        homebridge = pHomebridge;
        Characteristic = homebridge.hap.Characteristic;
        Service = homebridge.hap.Service;
    }

    var hexToBase64 = function(val) {
        return new Buffer((''+val).replace(/[^0-9A-F]/ig, ''), 'hex').toString('base64');
    }, base64ToHex = function(val) {
        if(!val) return val;
        return new Buffer(val, 'base64').toString('hex');
    }, swap16 = function (val) {
        return ((val & 0xFF) << 8)
            | ((val >>> 8) & 0xFF);
    }, swap32 = function (val) {
        return ((val & 0xFF) << 24)
           | ((val & 0xFF00) << 8)
           | ((val >>> 8) & 0xFF00)
           | ((val >>> 24) & 0xFF);
    }, hexToHPA = function(val) {
        return parseInt(swap16(val), 10);
    }, hPAtoHex = function(val) {
        return swap16(Math.round(val)).toString(16);
    }, numToHex = function(val, len) {
        var s = Number(val >>> 0).toString(16);
        if(s.length % 2 != 0) {
            s = '0' + s;
        }
        if(len) {
            return ('0000000000000' + s).slice(-1 * len);
        }
    return s;
}

    class S2R1Characteristic extends Characteristic {
        constructor() {
            super('S2R1', S2R1Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2R1Characteristic.UUID = 'E863F116-079E-48FF-8F27-9C2605A29F52';

    class S2R2Characteristic extends Characteristic {
        constructor() {
            super('S2R2', S2R2Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2R2Characteristic.UUID = 'E863F117-079E-48FF-8F27-9C2605A29F52';

    class S2W1Characteristic extends Characteristic {
        constructor() {
            super('S2W1', S2W1Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.WRITE, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2W1Characteristic.UUID = 'E863F11C-079E-48FF-8F27-9C2605A29F52';

    class S2W2Characteristic extends Characteristic {
        constructor() {
            super('S2W2', S2W2Characteristic.UUID);
            this.setProps({
                format: Characteristic.Formats.DATA,
                perms: [
                    Characteristic.Perms.WRITE, Characteristic.Perms.HIDDEN
                ]
            });
        }
    }

    S2W2Characteristic.UUID = 'E863F121-079E-48FF-8F27-9C2605A29F52';

    class FakeGatoHistoryService extends Service {
        constructor(accessoryType, accessory, size) {
            if (typeof size === 'undefined') { size = 4032; }

            super(accessory.displayName + " History", FakeGatoHistoryService.UUID);

            var entry2address = function(val) {
                var temp = val % this.memorySize;
                return temp;
            }.bind(this);

            this.log = accessory.log;
            switch (accessoryType) {
                case TYPE_WEATHER:
                    this.accessoryType116 = "03 0102 0202 0302";
                    this.accessoryType117 = "07";
                    break;
                case TYPE_ENERGY:
                    this.accessoryType116 = "05 0502 0b02 0c02 0d02 0702";
                    this.accessoryType117 = "1e";
                    break;
                case TYPE_ROOM:
                    this.accessoryType116 = "04 0102 0202 0402 0f03";
                    this.accessoryType117 = "0f";
                    break;
                case TYPE_DOOR:
                    this.accessoryType116 = "01 0601";
                    this.accessoryType117 = "01";
                    break;
                case TYPE_MOTION:
                    this.accessoryType116 = "02 1301 1c01";
                    this.accessoryType117 = "02";
                    break;
                case TYPE_THERMO:
                    this.accessoryType116 = "05 0102 1102 1001 1201 1d01";
                    this.accessoryType117 = "1f";
                    break;
            }

            this.accessoryType = accessoryType;
            this.firstEntry = 0;
            this.lastEntry = 0;
            this.history = [];
            this.memorySize = size;
            this.usedMemory = 0;
            this.currentEntry = 1;
            this.transfer = false;
            this.setTime = true;
            this.refTime = 0;
            this.memoryAddress = 0;
            this.dataStream = '';

            this.addCharacteristic(S2R1Characteristic);

            this.addCharacteristic(S2R2Characteristic)
                .on('get', (callback) => {
                    if ((this.currentEntry < this.lastEntry) && (this.transfer == true)) {
                        this.memoryAddress = entry2address(this.currentEntry);

                        if ((this.history[this.memoryAddress].temp == 0 &&
                            this.history[this.memoryAddress].pressure == 0 &&
                            this.history[this.memoryAddress].humidity == 0) || (this.history[this.memoryAddress].power == 0xFFFF) || (this.setTime == true)) {

                            var val = Format(
                                '15%s 0000 0000 81%s0000 0000 00 0000',
                                numToHex(swap32(this.currentEntry),8),
                                numToHex(swap32(this.refTime),8)
                            );

                            this.log.debug("Data %s: %s", this.accessoryType, val);
                            callback(null, hexToBase64(val));
                            this.setTime=false;
                            this.currentEntry++;
                        }
                        else {
                            for (var i = 0;i < 11;i++) {
                                this.log.debug("%s Entry: %s, Address: %s", this.accessoryType, this.currentEntry, this.memoryAddress);
                                switch (this.accessoryType) {
                                    case TYPE_WEATHER:
                                        this.dataStream += Format(
                                            " 10 %s%s%s%s%s%s",
                                            numToHex(swap32(this.currentEntry), 8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
                                           numToHex(swap16(this.history[this.memoryAddress].humidity*100), 4),
                                            numToHex(swap16(this.history[this.memoryAddress].pressure*10), 4)
                                        );
                                        break;
                                    case TYPE_ENERGY:
                                        this.dataStream += Format(
                                            " 12 %s%s%s%s0000 0000 0000",
                                            numToHex(swap32(this.currentEntry),8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].power * 10), 4)
                                        );
                                        break;
                                    case TYPE_ROOM:
                                        this.dataStream += Format(
                                            " 13 %s%s%s%s%s%s0000 00",
                                            numToHex(swap32(this.currentEntry), 8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
                                            numToHex(swap16(this.history[this.memoryAddress].humidity*100), 4),
                                            numToHex(swap16(this.history[this.memoryAddress].ppm), 4)
                                        );
                                        break;
                                    case TYPE_DOOR:
                                    case TYPE_MOTION:
                                        this.dataStream += Format(
                                            " 0b %s%s%s%s",
                                            numToHex(swap32(this.currentEntry), 8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(this.history[this.memoryAddress].status, 2)
                                        );
                                        break;
                                    case TYPE_THERMO:
                                        this.dataStream += Format(
                                            " 11 %s%s%s%s%s%s 0000",
                                            numToHex(swap32(this.currentEntry), 8),
                                            numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
                                            this.accessoryType117,
                                            numToHex(swap16(this.history[this.memoryAddress].currentTemp * 100), 4),
                                           numToHex(swap16(this.history[this.memoryAddress].setTemp*100), 4),
                                            numToHex(this.history[this.memoryAddress].valvePosition, 2)
                                        );
                                        break;
                                }
                                this.currentEntry++;
                                this.memoryAddress = entry2address(this.currentEntry);
                                if (this.currentEntry > this.lastEntry) {
                                    break;
                                }
                            }
                            this.log.debug("Data %s: %s", this.accessoryType, this.dataStream);
                            callback(null, hexToBase64(this.dataStream));
                            this.dataStream = '';
                        }
                    }
                    else {
                        this.transfer = false;
                        callback(null, hexToBase64('00'));
                    }
            });

            this.addCharacteristic(S2W1Characteristic)
                .on('set', this.setCurrentS2W1.bind(this));

            this.addCharacteristic(S2W2Characteristic)
                .on('set', this.setCurrentS2W2.bind(this));
        }

        sendHistory(address){
            var hexAddress= address.toString('16');
            if (address != 0) {
                this.currentEntry = address;
            }
            else {
                this.currentEntry = 1;
            }
            this.transfer=true;
        }

        //in order to be consistent with Eve, entry address start from 1
        addEntry(entry){

            var entry2address = function(val) {
                return val % this.memorySize;
            }.bind(this);

            if (this.usedMemory < this.memorySize) {
                this.usedMemory++;
                this.firstEntry = 0;
                this.lastEntry = this.usedMemory;
            }
            else {
                this.firstEntry++;
                this.lastEntry = this.firstEntry + this.usedMemory;
            }

            if (this.refTime == 0) {
                this.refTime = entry.time - EPOCH_OFFSET;
                switch (this.accessoryType)
                    {
                        case TYPE_WEATHER:
                            this.history[this.lastEntry]= {time: entry.time, temp: 0, pressure: 0, humidity: 0};
                            break;
                        case TYPE_ENERGY:
                            this.history[this.lastEntry]= {time: entry.time, power: 0xFFFF};
                            break;
                        case TYPE_ROOM:
                            this.history[this.lastEntry]= {time: entry.time, temp: 0, humidity:0, ppm: 0};
                            break;
                        case TYPE_DOOR:
                        case TYPE_MOTION:
                            this.history[this.lastEntry]= {time: entry.time, status: 'undefined'};
                            break;
                        case TYPE_THERMO:
                            this.history[this.lastEntry]= {time: entry.time, currentTemp: 0, setTemp: 0, valvePosition: -1};
                            break;
                    }
                this.lastEntry++;
                this.usedMemory++;
            }

            this.history[entry2address(this.lastEntry)] = (entry);

            var val = Format(
                '%s00000000%s%s%s%s%s000000000101',
                numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8),
                numToHex(swap32(this.refTime), 8),
                this.accessoryType116,
                numToHex(swap16(this.usedMemory), 4),
                numToHex(swap16(this.memorySize), 4),
                numToHex(swap32(this.firstEntry), 8)
            );

            this.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val));

            this.log.debug("First entry %s: %s", this.accessoryType, this.firstEntry.toString(16));
            this.log.debug("Last entry %s: %s", this.accessoryType, this.lastEntry.toString(16));
            this.log.debug("Used memory %s: %s", this.accessoryType, this.usedMemory.toString(16));
            this.log.debug("116 %s: %s", this.accessoryType, val);
        }

        setCurrentS2W1(val, callback) {
            callback(null,val);
            this.log.debug("Data request %s: %s", this.accessoryType, base64ToHex(val));
            var valHex = base64ToHex(val);
            var substring = valHex.substring(4, 12);
            var valInt = parseInt(substring, 16);
            var address = swap32(valInt);
            var hexAddress= address.toString('16');

            this.log.debug("Address requested %s: %s", this.accessoryType, hexAddress);
            this.sendHistory(address);

        }

        setCurrentS2W2(val, callback) {
            this.log.debug("Clock adjust: %s", base64ToHex(val));
            callback(null, val);
        }

    }

    FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52';

    return FakeGatoHistoryService;
}
<|endoftext|>


<commit_before>/*
 Copyright (c) 2012 GFT Appverse, S.L., Sociedad Unipersonal.
 This Source Code Form is subject to the terms of the Appverse Public License
 Version 2.0 (“APL v2.0”). If a copy of the APL was not distributed with this
 file, You can obtain one at http://www.appverse.mobi/licenses/apl_v2.0.pdf. [^]
 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the conditions of the AppVerse Public License v2.0
 are met.
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. EXCEPT IN CASE OF WILLFUL MISCONDUCT OR GROSS NEGLIGENCE, IN NO EVENT
 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 */
'use strict';
var path = require('path');
var fs = require('fs-extra');
var cheerio = require('cheerio');
var appverse = require ('appverse-generator-commons');
var chalk = require('chalk');
var ZSchema = require('z-schema');
var request = require('request');
var beautify = require('js-beautify').js_beautify;

var appverseHTML5Generator = appverse.extend({
/**
 * Get the Generated application name
 **/
    getAppName: function getAppName() {
    //APP NAME
    var pkgPath = this.destinationPath('package.json');
    var pkg = JSON.parse(this.fs.read(pkgPath));
    return pkg.name;
},
/**
 * Get the Generated AngularJS application name
 **/
 getApplicationName : function getApplicationName() {
    return this.getAppName() + "App";
},
/**
 *
 * Find module by name from the available module list
 * @param {string} nameKey - Name to find
 * @param {Object} configs - Configuration object
 *
 */
 findConfig: function findConfig(nameKey, configs) {
    for (var i = 0; i < configs.length; i++) {
        if (configs[i].name === nameKey) {
            return configs[i];
        }
    }
},
/**
 *
 * Find build for prompting on start
 * @param {string} config - Configuration file path
 */
promptsConfig: function promptsConfig(config) {
    var modules = require(config);
    var prompts = [];
    for (var i = 0; i < modules.length; i++) {
        if (modules[i].startprompt) {
            prompts.push(modules[i].name);
        }
    }
    return prompts;
},
/**
 * Check required arguments
 * Rewrite the method to beatufiy the error message.
 **/
checkRequiredArgs : function () {
    // If the help option was provided, we don't want to check for required
    // arguments, since we're only going to print the help message anyway.
    if (this.options.help) {
        return;
    }
    // Bail early if it's not possible to have a missing required arg
    if (this.args.length > this._arguments.length) {
        return;
    }
    this._arguments.forEach(function (arg, position) {
        // If the help option was not provided, check whether the argument was
        // required, and whether a value was provided.
        if (arg.config.required && position >= this.args.length) {
            this.warning('Did not provide required argument ' + chalk.bold(arg.name) + '!');
            return;
        }
    }, this);
},
/** Read JSON SCHEMA
 *  Remote JSON Schema uses 'Accept': 'application/schema+json' header for request
 *  @param {string} url - Remote JSON Schema URL or path
 *  @param {function} callback - Callback function
 **/
readJSONSchemaFileOrUrl: function readJSONSchemaFileOrUrl(url, callback) {
    if (/^https?:/.test(url)) {
        var options = {
            url: url,
            headers: {
                'Accept': 'application/schema+json'
            }
        };
        request(options, function (error, response, body) {
            if (response.statusCode === 404) {
                callback(error, null);
                return;
            }
            if (!error && response.statusCode === 200) {
                callback(null, JSON.parse(body.toString()));
            } else {
                callback(error, null);
            }
        });
        return;
    }
    if (!fs.existsSync(url)) {
        callback("Wrong Path. I can't find a JSON file there!", null);
    }
    callback(null, JSON.parse(fs.readFileSync(url, 'utf8')));
},
/**
* Read JSON from file or URL
* @param {string} url - JSON file URL or Path
* @param {function} callback - Callback function
**/
readJSONFileOrUrl: function readJSONFileOrUrl(url, callback) {
    if (/^https?:/.test(url)) {
        request(url, function (error, response, body) {
            if (!error && response.statusCode === 200) {
                callback(null, JSON.parse(body.toString()));
            } else {
                callback(error, null);
            }
        });
        return;
    }
    if (!fs.existsSync(url)) {
        this.info("JSON URL: " + url);
        callback("Wrong Path. I can't find a JSON file there!", null);
    }
    callback(null, JSON.parse(fs.readFileSync(url, 'utf8')));
},
/**
*  Validate JSON against Appverse Project JSON schema
*  @param {string} json - Json to validate
*  @param {string} tpath - Template path
*  @param {function} callback - Callback function
**/
validateJson : function validateJson(json, tpath, callback) {
    var validator = new ZSchema();
    this.schema = JSON.parse(fs.readFileSync(path.join(tpath, '../schema/appverse-project-schema.json'), 'utf-8'));
    var valid = validator.validate(json, this.schema);
    if (!valid) {
        callback("Sorry. Not a valid JSON project! ", null);
        callback("Check the project-schema.json for a valid JSON file. \n", null);
        callback(JSON.stringify(this.schema), null);
    }
    callback(null, true);
},
/**
 * Add Routing
 * @param {string} name - rounting name
 **/
addRouteState : function addRouteState(name) {
    //STATES
    var hook = '$stateProvider',
        path = this.destinationPath('app/states/app-states.js'),
        file = this.fs.read(path),
        insert = ".state('" + name + "', {url: '/" + name + "',templateUrl: 'components/" + name + "/" + name + ".html',controller: '" + name.capitalizeFirstLetter() + "Controller'})";

    if (file.indexOf(insert) === -1) {
        var pos = file.lastIndexOf(hook) + hook.length;
        var output = [file.slice(0, pos), insert, file.slice(pos)].join('');
        this.fs.write(path, output);
    }
},
/**
 * Add Link to NAV Bar.
 * @param {string} name - rounting name
 * @param {String} _icon - OPTIONAL icon to be shown in the NavBar
 **/
addLinkToNavBar : function addLinkToNavBar(name, _icon) {
    var icon = _icon || "glyphicon-globe";
    var indexPath = this.destinationPath('app/index.html');
    var index = this.fs.read(indexPath);
    var indexHTML = cheerio.load(index);
    //ADD LINK
    var findlink = indexHTML('*[ui-sref="' + name + '"]');
    if (require('lodash').isEmpty(findlink)) {
        var navLink = '
 ' + name.capitalizeFirstLetter() + '
';
        indexHTML('ul.nav.navbar-nav').append(navLink);
        indexHTML('ul.sidebar-nav').append(navLink);
    }
    this.fs.write(indexPath, indexHTML.html());
    this.addRouteState(name);
}
});
module.exports = appverseHTML5Generator;
/**
* Check if an element exists in array using a comparer function
* @param {function} comparer - Comparer
**/
Array.prototype.inArray = function (comparer) {
    for (var i = 0; i < this.length; i++) {
        if (comparer(this[i])) {
            return true;
        }
    }
    return false;
};
/**
* Adds an element to the array if it does not already exist using a comparer
* @param {Object} element - Element
* @param {function} comparer - Comparer
**/
Array.prototype.pushIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.push(element);
    }
};
/**
* Adds an element to the array if it does not already exist using a comparer function
* @param {Object} element - Element
* @param {function} comparer - Comparer
**/
Array.prototype.unshiftIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.unshift(element);
    }
};

/**
* Serialize object to module with beautify
* @param  {Object} plain - Element
*
**/
function modularize( plain ){
    return beautify( '\'use strict\'; \n module.exports = ' + util.inspect( plain,
            { depth : null }) + ';',
            { indent_size : 2 });
};

/**
 * Changes the first letter to its capital.
 */
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}<commit_msg>Add -h description for --demo<commit_after>/*
 Copyright (c) 2012 GFT Appverse, S.L., Sociedad Unipersonal.
 This Source Code Form is subject to the terms of the Appverse Public License
 Version 2.0 (“APL v2.0”). If a copy of the APL was not distributed with this
 file, You can obtain one at http://www.appverse.mobi/licenses/apl_v2.0.pdf. [^]
 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the conditions of the AppVerse Public License v2.0
 are met.
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. EXCEPT IN CASE OF WILLFUL MISCONDUCT OR GROSS NEGLIGENCE, IN NO EVENT
 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 */
'use strict';
var path = require('path');
var fs = require('fs-extra');
var cheerio = require('cheerio');
var appverse = require ('appverse-generator-commons');
var chalk = require('chalk');
var ZSchema = require('z-schema');
var request = require('request');
var beautify = require('js-beautify').js_beautify;

var appverseHTML5Generator = appverse.extend({
    constructor: function () {
        // Calling the super constructor
        appverse.apply(this, arguments);

        //adding a custom option
        this.option('demo', {
            alias: 'd',
            desc: 'Loads demo code for example purposes'
        }); //Adds support for --demo flag
    },
/**
 * Get the Generated application name
 **/
    getAppName: function getAppName() {
    //APP NAME
    var pkgPath = this.destinationPath('package.json');
    var pkg = JSON.parse(this.fs.read(pkgPath));
    return pkg.name;
},
/**
 * Get the Generated AngularJS application name
 **/
 getApplicationName : function getApplicationName() {
    return this.getAppName() + "App";
},
/**
 *
 * Find module by name from the available module list
 * @param {string} nameKey - Name to find
 * @param {Object} configs - Configuration object
 *
 */
 findConfig: function findConfig(nameKey, configs) {
    for (var i = 0; i < configs.length; i++) {
        if (configs[i].name === nameKey) {
            return configs[i];
        }
    }
},
/**
 *
 * Find build for prompting on start
 * @param {string} config - Configuration file path
 */
promptsConfig: function promptsConfig(config) {
    var modules = require(config);
    var prompts = [];
    for (var i = 0; i < modules.length; i++) {
        if (modules[i].startprompt) {
            prompts.push(modules[i].name);
        }
    }
    return prompts;
},
/**
 * Check required arguments
 * Rewrite the method to beatufiy the error message.
 **/
checkRequiredArgs : function () {
    // If the help option was provided, we don't want to check for required
    // arguments, since we're only going to print the help message anyway.
    if (this.options.help) {
        return;
    }
    // Bail early if it's not possible to have a missing required arg
    if (this.args.length > this._arguments.length) {
        return;
    }
    this._arguments.forEach(function (arg, position) {
        // If the help option was not provided, check whether the argument was
        // required, and whether a value was provided.
        if (arg.config.required && position >= this.args.length) {
            this.warning('Did not provide required argument ' + chalk.bold(arg.name) + '!');
            return;
        }
    }, this);
},
/** Read JSON SCHEMA
 *  Remote JSON Schema uses 'Accept': 'application/schema+json' header for request
 *  @param {string} url - Remote JSON Schema URL or path
 *  @param {function} callback - Callback function
 **/
readJSONSchemaFileOrUrl: function readJSONSchemaFileOrUrl(url, callback) {
    if (/^https?:/.test(url)) {
        var options = {
            url: url,
            headers: {
                'Accept': 'application/schema+json'
            }
        };
        request(options, function (error, response, body) {
            if (response.statusCode === 404) {
                callback(error, null);
                return;
            }
            if (!error && response.statusCode === 200) {
                callback(null, JSON.parse(body.toString()));
            } else {
                callback(error, null);
            }
        });
        return;
    }
    if (!fs.existsSync(url)) {
        callback("Wrong Path. I can't find a JSON file there!", null);
    }
    callback(null, JSON.parse(fs.readFileSync(url, 'utf8')));
},
/**
* Read JSON from file or URL
* @param {string} url - JSON file URL or Path
* @param {function} callback - Callback function
**/
readJSONFileOrUrl: function readJSONFileOrUrl(url, callback) {
    if (/^https?:/.test(url)) {
        request(url, function (error, response, body) {
            if (!error && response.statusCode === 200) {
                callback(null, JSON.parse(body.toString()));
            } else {
                callback(error, null);
            }
        });
        return;
    }
    if (!fs.existsSync(url)) {
        this.info("JSON URL: " + url);
        callback("Wrong Path. I can't find a JSON file there!", null);
    }
    callback(null, JSON.parse(fs.readFileSync(url, 'utf8')));
},
/**
*  Validate JSON against Appverse Project JSON schema
*  @param {string} json - Json to validate
*  @param {string} tpath - Template path
*  @param {function} callback - Callback function
**/
validateJson : function validateJson(json, tpath, callback) {
    var validator = new ZSchema();
    this.schema = JSON.parse(fs.readFileSync(path.join(tpath, '../schema/appverse-project-schema.json'), 'utf-8'));
    var valid = validator.validate(json, this.schema);
    if (!valid) {
        callback("Sorry. Not a valid JSON project! ", null);
        callback("Check the project-schema.json for a valid JSON file. \n", null);
        callback(JSON.stringify(this.schema), null);
    }
    callback(null, true);
},
/**
 * Add Routing
 * @param {string} name - rounting name
 **/
addRouteState : function addRouteState(name) {
    //STATES
    var hook = '$stateProvider',
        path = this.destinationPath('app/states/app-states.js'),
        file = this.fs.read(path),
        insert = ".state('" + name + "', {url: '/" + name + "',templateUrl: 'components/" + name + "/" + name + ".html',controller: '" + name.capitalizeFirstLetter() + "Controller'})";

    if (file.indexOf(insert) === -1) {
        var pos = file.lastIndexOf(hook) + hook.length;
        var output = [file.slice(0, pos), insert, file.slice(pos)].join('');
        this.fs.write(path, output);
    }
},
/**
 * Add Link to NAV Bar.
 * @param {string} name - rounting name
 * @param {String} _icon - OPTIONAL icon to be shown in the NavBar
 **/
addLinkToNavBar : function addLinkToNavBar(name, _icon) {
    var icon = _icon || "glyphicon-globe";
    var indexPath = this.destinationPath('app/index.html');
    var index = this.fs.read(indexPath);
    var indexHTML = cheerio.load(index);
    //ADD LINK
    var findlink = indexHTML('*[ui-sref="' + name + '"]');
    if (require('lodash').isEmpty(findlink)) {
        var navLink = '
 ' + name.capitalizeFirstLetter() + '
';
        indexHTML('ul.nav.navbar-nav').append(navLink);
        indexHTML('ul.sidebar-nav').append(navLink);
    }
    this.fs.write(indexPath, indexHTML.html());
    this.addRouteState(name);
}
});
module.exports = appverseHTML5Generator;
/**
* Check if an element exists in array using a comparer function
* @param {function} comparer - Comparer
**/
Array.prototype.inArray = function (comparer) {
    for (var i = 0; i < this.length; i++) {
        if (comparer(this[i])) {
            return true;
        }
    }
    return false;
};
/**
* Adds an element to the array if it does not already exist using a comparer
* @param {Object} element - Element
* @param {function} comparer - Comparer
**/
Array.prototype.pushIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.push(element);
    }
};
/**
* Adds an element to the array if it does not already exist using a comparer function
* @param {Object} element - Element
* @param {function} comparer - Comparer
**/
Array.prototype.unshiftIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.unshift(element);
    }
};

/**
* Serialize object to module with beautify
* @param  {Object} plain - Element
*
**/
function modularize( plain ){
    return beautify( '\'use strict\'; \n module.exports = ' + util.inspect( plain,
            { depth : null }) + ';',
            { indent_size : 2 });
};

/**
 * Changes the first letter to its capital.
 */
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}<|endoftext|>


<commit_before>import isObject from 'lodash/isObject';
import isFunction from 'lodash/isFunction';
import isArray from 'lodash/isArray';
import isEqual from 'lodash/isEqual';
import isString from 'lodash/isString';
import isBoolean from 'lodash/isBoolean';
import isNumber from 'lodash/isNumber';
import isDate from 'lodash/isDate';
import clone from 'lodash/clone';
import cloneDeep from 'lodash/cloneDeepWith';
import isUndefined from 'lodash/isUndefined';
import debounce from 'lodash/debounce';
import merge from 'lodash/merge';
import flatten from 'lodash/flatten';
import startCase from 'lodash/startCase';
import assignWith from 'lodash/assignWith';
import uniq from 'lodash/uniq';
import sumBy from 'lodash/sumBy';
import max from 'lodash/maxBy';
import min from 'lodash/minBy';
import zip from 'lodash/zip';
import takeWhile from 'lodash/takeWhile';
import dropWhile from 'lodash/dropWhile';
import groupBy from 'lodash/groupBy';
import {AppError} from '../infrastructure';

const noop = () => {
};
const yes = () => true;
const no = () => false;
const identity = arg => arg;

const toCamelCase = (...names) => {
	const length = names.length;
	const nameList = names.map(name => '' + name);
	if (length > 0) {
		return (nameList[0] +
		nameList.slice(1)
			.map(name => name[0].toUpperCase() + name.substring(1, name.length))
			.join(''));
	}

	return '';
};

const isEmail = value => {
	if (value) {
		const re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; // eslint-disable-line no-useless-escape
		return re.test(value);
	}

	return false;
};

function compare(x, y) {
	if (x === y) {
		return 0;
	}

	if (x > y) {
		return 1;
	}

	return -1;
}

function orderBy(data, selectors, compares) {
	const length = selectors.length;
	if (selectors.length !== compares.length) {
		throw new AppError('utility', `Number of compares should match number of selectors, expected ${length} got ${compares.length}`);
	}

	const compare = (x, y) => {
		let result = 0;
		for (let i = 0; i < length; i++) {
			const select = selectors[i];
			const compare = compares[i];

			const xv = select(x);
			const yv = select(y);

			result = compare(xv, yv, x, y);
			if (result !== 0) {
				break;
			}
		}

		return result;
	};

	return data.sort(compare);
}

export {
	isObject,
	isFunction,
	isArray,
	isEqual,
	isString,
	isUndefined,
	isBoolean,
	isDate,
	isNumber,
	isEmail,
	clone,
	cloneDeep,
	debounce,
	merge,
	flatten,
	startCase,
	assignWith,
	uniq,
	identity,
	yes,
	no,
	toCamelCase,
	noop,
	compare,
	orderBy,
	max,
	min,
	sumBy,
	zip,
	takeWhile,
	dropWhile,
	groupBy
};<commit_msg>stable sort + performance improvements<commit_after>import isObject from 'lodash/isObject';
import isFunction from 'lodash/isFunction';
import isArray from 'lodash/isArray';
import isEqual from 'lodash/isEqual';
import isString from 'lodash/isString';
import isBoolean from 'lodash/isBoolean';
import isNumber from 'lodash/isNumber';
import isDate from 'lodash/isDate';
import clone from 'lodash/clone';
import cloneDeep from 'lodash/cloneDeepWith';
import isUndefined from 'lodash/isUndefined';
import debounce from 'lodash/debounce';
import merge from 'lodash/merge';
import flatten from 'lodash/flatten';
import startCase from 'lodash/startCase';
import assignWith from 'lodash/assignWith';
import uniq from 'lodash/uniq';
import sumBy from 'lodash/sumBy';
import max from 'lodash/maxBy';
import min from 'lodash/minBy';
import zip from 'lodash/zip';
import takeWhile from 'lodash/takeWhile';
import dropWhile from 'lodash/dropWhile';
import groupBy from 'lodash/groupBy';
import {AppError} from '../infrastructure';

const noop = () => {
};
const yes = () => true;
const no = () => false;
const identity = arg => arg;

const toCamelCase = (...names) => {
	const length = names.length;
	const nameList = names.map(name => '' + name);
	if (length > 0) {
		return (nameList[0] +
		nameList.slice(1)
			.map(name => name[0].toUpperCase() + name.substring(1, name.length))
			.join(''));
	}

	return '';
};

const isEmail = value => {
	if (value) {
		const re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; // eslint-disable-line no-useless-escape
		return re.test(value);
	}

	return false;
};

function compare(x, y) {
	if (x === y) {
		return 0;
	}

	if (x > y) {
		return 1;
	}

	return -1;
}

function orderBy(data, selectors, compares) {
	const length = selectors.length;
	if (selectors.length !== compares.length) {
		throw new AppError('utility', `Number of compares should match number of selectors, expected ${length} got ${compares.length}`);
	}

	const result = [];
	const count = data.length;

	// iterate through data to create similar array but with applied selectors
	let index = count;
	while (index--) {
		const row = data[index];
		const criteria = [];
		for (let i = 0; i < length; i++) {
			const select = selectors[i];
			criteria.push(select(row));
		}

		result.push({row, criteria, index});
	}

	// multi selector comparator
	const compare = (x, y) => {
		let result = 0;
		for (let i = 0; i < length; i++) {
			const compare = compares[i];
			const xv = x.criteria[i];
			const yv = y.criteria[i];

			result = compare(xv, yv, x.row, y.row);
			if (result !== 0) {
				return result;
			}
		}

		// ensures a stable sort
		return x.index - y.index;
	};

	result.sort(compare);

	// copy origin values to result array
	index = count;
	while (index--) {
		result[index] = result[index].row;
	}

	return result;
}

export {
	isObject,
	isFunction,
	isArray,
	isEqual,
	isString,
	isUndefined,
	isBoolean,
	isDate,
	isNumber,
	isEmail,
	clone,
	cloneDeep,
	debounce,
	merge,
	flatten,
	startCase,
	assignWith,
	uniq,
	identity,
	yes,
	no,
	toCamelCase,
	noop,
	compare,
	orderBy,
	max,
	min,
	sumBy,
	zip,
	takeWhile,
	dropWhile,
	groupBy
};<|endoftext|>