<commit_before>/*
 * files.js: Instance methods for working with files for Openstack Object Storage
 *
 * (C) 2013 Rackspace, Ken Perkins
 * MIT LICENSE
 *
 */

var fs = require('fs'),
    filed = require('filed'),
    mime = require('mime'),
    request = require('request'),
    util = require('util'),
    base = require('../../../core/storage'),
    pkgcloud = require('../../../../pkgcloud'),
    errs = require('errs'),
    through = require('through2'),
    _ = require('underscore'),
    storage = pkgcloud.providers.openstack.storage;

/**
 * client.removeFile
 *
 * @description remove a file from a container
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to delete
 * @param callback
 */
exports.removeFile = function (container, file, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      fileName = file instanceof this.models.File ? file.name : file;

  this._request({
      method: 'DELETE',
      container: containerName,
      path: fileName
    }, function(err) {
      return err
        ? callback(err)
        : callback(null, true);
    }
  );
};

/**
 * client.bulkDelete
 *
 * @description remove a list of files from a container
 *
 * @param {String|object}     container     the container or containerName
 * @param {array}             files         the files or fileNames to delete
 * @param callback
 */
exports.bulkDelete = function(container, files, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container;
  this._request({
    method: 'DELETE',
    body: files.map(function(f) {
      return containerName + '/' + f;
    }).join('\r\n'),
    headers: {
      'Content-Type': 'text/plain'
    },
    qs: {
      'bulk-delete': true
    }
  }, callback);
};

/**
 * client.upload
 *
 * @description upload a new file to a container.
 * Returns the pipe interface so you can call:
 *
 * request('http://some.com/file.txt').pipe(client.upload(options));
 *
 * @param {object}          options
 * @param {String|object}   options.container   the container to store the file in
 * @param {String}          options.remote      the file name for the new file
 * @param {String}          [options.local]     an optional local file path to upload
 * @param {Stream}          [options.stream]    optionally explicitly provide the stream instead of pipe
 * @param {object}          [options.headers]   optionally provide headers for the call
 * @param {object}          [options.metadata]  optionally provide metadata for the object
 * @param callback
 * @returns {request|*}
 */
exports.upload = function (options) {
  var self = this;

  // check for deprecated calling with a callback
  if (typeof arguments[arguments.length - 1] === 'function') {
    self.emit('log::warn', 'storage.upload no longer supports calling with a callback');
  }

  var container = options.container,
      writableStream,
      proxyStream = through(),
      uploadOptions = {
        method: 'PUT',
        upload: true,
        container: container,
        path: options.remote,
        headers: options.headers || {}
      };

  if (options.container instanceof this.models.Container) {
    uploadOptions.container = options.container.name;
  }

  if (options.contentType) {
    uploadOptions.headers['content-type'] = options.contentType;
  }
  else {
    uploadOptions.headers['content-type'] = mime.lookup(options.remote);
  }

  if (options.metadata) {
    uploadOptions.headers = _.extend(uploadOptions.headers,
      self.serializeMetadata(self.OBJECT_META_PREFIX, options.metadata));
  }

  writableStream = this._request(uploadOptions);

  writableStream.on('complete', function(response) {
    var err = self._parseError(response);

    if (err) {
      proxyStream.emit('error', err);
      return;
    }

    // load the file metadata from the cloud, so we can return a proper model
    self.getFile(uploadOptions.container, options.remote, function (err, file) {
      if (err) {
        proxyStream.emit('error', err);
        return;
      }

      proxyStream.emit('success', file);
    });
  });

  writableStream.on('error', function (err) {
    proxyStream.emit('error', err);
  });

  writableStream.on('data', function (chunk) {
    proxyStream.emit('data', chunk);
  });

  // we need a proxy stream so we can always return a file model
  // via the 'success' event
  proxyStream.pipe(writableStream);

  return proxyStream;
};

/**
 * client.download
 *
 * @description download a file from a container
 * Returns the pipe interface so you can call:
 *
 * client.download(options).pipe(fs.createWriteStream(options2));
 *
 * @param {object}          options
 * @param {String|object}   options.container   the container to store the file in
 * @param {String}          options.remote      the file name for the new file
 * @param {String}          [options.local]     an optional local file path to download to
 * @param {Stream}          [options.stream]    optionally explicitly provide the stream instead of pipe
 * @param callback
 * @returns {request|*}
 */
exports.download = function (options, callback) {
  var self = this,
      success = callback ? onDownload : null,
      container = options.container,
      inputStream,
      apiStream;

  //
  // Optional helper function passed to `this._request`
  // in the case when no callback is passed to `.download(options)`.
  //
  function onDownload(err, body, res) {
    return err
      ? callback(err)
      : callback(null, new self.models.File(self, _.extend(res.headers, {
          container: container,
          name: options.remote
        })));
  }

  if (container instanceof self.models.Container) {
    container = container.name;
  }

  if (options.local) {
    inputStream = filed(options.local);
  }
  else if (options.stream) {
    inputStream = options.stream;
  }

  apiStream = this._request({
    container: container,
    path: options.remote,
    download: true
  }, success);

  if (inputStream) {
    apiStream.pipe(inputStream);
  }

  return apiStream;
};

/**
 * client.getFile
 *
 * @description get the details for a specific file
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to get details for
 * @param callback
 */
exports.getFile = function (container, file, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      self = this;

  this._request({
    method: 'HEAD',
    container: containerName,
    path: file,
    qs: {
      format: 'json'
    }
  }, function (err, body, res) {
    return err
      ? callback(err)
      : callback(null, new self.models.File(self, _.extend(res.headers, {
          container: container,
          name: file
        })));
  });
};

/**
 * client.getFiles
 *
 * @description get the list of files in a container. Returns at most 10,000 files if options.limit is unspecified.
 * Abstracts the aggregation of files in the case that options.limit is >10,000.
 *
 * @param {String|object}     container     the container or containerName
 * @param {object|Function}   options
 * @param {Number}            [options.limit]   the number of records to return
 * @param {String}            [options.marker]  the id of the first record to return in the current query
 * @param {Function}          callback
 */
exports.getFiles = function (container, options, callback) {
  var self = this;

  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // If limit is not specified, or it is <=10k, just make a single request
  if (!options.limit || options.limit <= 10000) {
    return this._getFiles(container, options, callback);
  }

  // Limit is specified and is >10k. Abstract the aggregation of files (cloudfiles returns max 10k at once)
  var files = [];

  // Keep track of how many files are left to collect
  var remainingLimit = options.limit;
  delete options.limit;

  var getFilesCallback = function(err, someFiles) {
    if (err) {
      return callback(err);
    }

    files = files.concat(someFiles);
    remainingLimit -= someFiles.length;

    // Check if we should attempt to retrieve more results
    if (remainingLimit > 0 && someFiles.length === 10000) {
      options.marker = someFiles.pop().name;

      // Once the remainingLimit value becomes < 10000, we must pass it
      if (remainingLimit < 10000) {
        options.limit = remainingLimit;
      }

      self._getFiles(container, options, getFilesCallback);
    } else {
      callback(null, files);
    }
  };

  this._getFiles(container, options, getFilesCallback);
};

exports._getFiles = function (container, options, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      self = this;

  var getFilesOpts = {
      path: containerName,
      qs: _.extend({
          format: 'json'
      }, _.pick(options, ['limit', 'marker', 'prefix', 'path', 'delimiter']))
    };

    if (options.endMarker) {
      getFilesOpts.qs.end_marker = options.endMarker;
    }

  if (options.end_marker) {
    getFilesOpts.qs.end_marker = options.end_marker;
  }

  if (options.prefix) {
    getFilesOpts.qs.prefix  = options.prefix;
  }

  if (options.path) {
    getFilesOpts.qs.path  = options.path;
  }

  if (options.delimiter) {
    getFilesOpts.qs.delimiter  = options.delimiter;
  }

  this._request(getFilesOpts, function (err, body) {

    if (err) {
      return callback(err);
    }
    else if (!body || !(body instanceof Array)) {
      return new Error('Malformed API Response')
    }

    return callback(null, body.map(function (file) {
      file.container = container;
      return new self.models.File(self, file);
    }));
  });
};

/**
 * client.updateFileMetadata
 *
 * @description Updates the specified `file` with the provided metadata `headers`
 * in the Rackspace Cloud Files account associated with this instance.
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to update
 * @param callback
 */
exports.updateFileMetadata = function (container, file, callback) {
  var self = this,
      containerName = container instanceof self.models.Container ? container.name : container;

  if (!file instanceof base.File) {
    throw new Error('Must update an existing file instance');
  }

  var updateFileOpts = {
    method: 'POST',
    container: containerName,
    path: file.name,
    headers: self.serializeMetadata(self.OBJECT_META_PREFIX, file.metadata)
  };

  this._request(updateFileOpts, function (err) {
    return err
      ? callback(err)
      : callback(null, file);
  });
};

<commit_msg>accept both string and File values and explicit callback for bulk delete<commit_after>/*
 * files.js: Instance methods for working with files for Openstack Object Storage
 *
 * (C) 2013 Rackspace, Ken Perkins
 * MIT LICENSE
 *
 */

var fs = require('fs'),
    filed = require('filed'),
    mime = require('mime'),
    request = require('request'),
    util = require('util'),
    base = require('../../../core/storage'),
    pkgcloud = require('../../../../pkgcloud'),
    errs = require('errs'),
    through = require('through2'),
    _ = require('underscore'),
    storage = pkgcloud.providers.openstack.storage;

/**
 * client.removeFile
 *
 * @description remove a file from a container
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to delete
 * @param callback
 */
exports.removeFile = function (container, file, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      fileName = file instanceof this.models.File ? file.name : file;

  this._request({
      method: 'DELETE',
      container: containerName,
      path: fileName
    }, function(err) {
      return err
        ? callback(err)
        : callback(null, true);
    }
  );
};

/**
 * client.bulkDelete
 *
 * @description remove a list of files from a container
 *
 * @param {String|object}     container     the container or containerName
 * @param {array}             files         the files or fileNames to delete
 * @param callback
 */
exports.bulkDelete = function(container, files, callback) {
  var self = this,
      containerName = container instanceof this.models.Container ? container.name : container;
  this._request({
    method: 'DELETE',
    body: files.map(function(f) {
      return containerName + '/' + (f instanceof self.models.File ? f.name : f);
    }).join('\r\n'),
    headers: {
      'Content-Type': 'text/plain'
    },
    qs: {
      'bulk-delete': true
    }
  }, function(err, results) {
    return err
      ? callback(err)
      : callback(null, results);
  });
};

/**
 * client.upload
 *
 * @description upload a new file to a container.
 * Returns the pipe interface so you can call:
 *
 * request('http://some.com/file.txt').pipe(client.upload(options));
 *
 * @param {object}          options
 * @param {String|object}   options.container   the container to store the file in
 * @param {String}          options.remote      the file name for the new file
 * @param {String}          [options.local]     an optional local file path to upload
 * @param {Stream}          [options.stream]    optionally explicitly provide the stream instead of pipe
 * @param {object}          [options.headers]   optionally provide headers for the call
 * @param {object}          [options.metadata]  optionally provide metadata for the object
 * @param callback
 * @returns {request|*}
 */
exports.upload = function (options) {
  var self = this;

  // check for deprecated calling with a callback
  if (typeof arguments[arguments.length - 1] === 'function') {
    self.emit('log::warn', 'storage.upload no longer supports calling with a callback');
  }

  var container = options.container,
      writableStream,
      proxyStream = through(),
      uploadOptions = {
        method: 'PUT',
        upload: true,
        container: container,
        path: options.remote,
        headers: options.headers || {}
      };

  if (options.container instanceof this.models.Container) {
    uploadOptions.container = options.container.name;
  }

  if (options.contentType) {
    uploadOptions.headers['content-type'] = options.contentType;
  }
  else {
    uploadOptions.headers['content-type'] = mime.lookup(options.remote);
  }

  if (options.metadata) {
    uploadOptions.headers = _.extend(uploadOptions.headers,
      self.serializeMetadata(self.OBJECT_META_PREFIX, options.metadata));
  }

  writableStream = this._request(uploadOptions);

  writableStream.on('complete', function(response) {
    var err = self._parseError(response);

    if (err) {
      proxyStream.emit('error', err);
      return;
    }

    // load the file metadata from the cloud, so we can return a proper model
    self.getFile(uploadOptions.container, options.remote, function (err, file) {
      if (err) {
        proxyStream.emit('error', err);
        return;
      }

      proxyStream.emit('success', file);
    });
  });

  writableStream.on('error', function (err) {
    proxyStream.emit('error', err);
  });

  writableStream.on('data', function (chunk) {
    proxyStream.emit('data', chunk);
  });

  // we need a proxy stream so we can always return a file model
  // via the 'success' event
  proxyStream.pipe(writableStream);

  return proxyStream;
};

/**
 * client.download
 *
 * @description download a file from a container
 * Returns the pipe interface so you can call:
 *
 * client.download(options).pipe(fs.createWriteStream(options2));
 *
 * @param {object}          options
 * @param {String|object}   options.container   the container to store the file in
 * @param {String}          options.remote      the file name for the new file
 * @param {String}          [options.local]     an optional local file path to download to
 * @param {Stream}          [options.stream]    optionally explicitly provide the stream instead of pipe
 * @param callback
 * @returns {request|*}
 */
exports.download = function (options, callback) {
  var self = this,
      success = callback ? onDownload : null,
      container = options.container,
      inputStream,
      apiStream;

  //
  // Optional helper function passed to `this._request`
  // in the case when no callback is passed to `.download(options)`.
  //
  function onDownload(err, body, res) {
    return err
      ? callback(err)
      : callback(null, new self.models.File(self, _.extend(res.headers, {
          container: container,
          name: options.remote
        })));
  }

  if (container instanceof self.models.Container) {
    container = container.name;
  }

  if (options.local) {
    inputStream = filed(options.local);
  }
  else if (options.stream) {
    inputStream = options.stream;
  }

  apiStream = this._request({
    container: container,
    path: options.remote,
    download: true
  }, success);

  if (inputStream) {
    apiStream.pipe(inputStream);
  }

  return apiStream;
};

/**
 * client.getFile
 *
 * @description get the details for a specific file
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to get details for
 * @param callback
 */
exports.getFile = function (container, file, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      self = this;

  this._request({
    method: 'HEAD',
    container: containerName,
    path: file,
    qs: {
      format: 'json'
    }
  }, function (err, body, res) {
    return err
      ? callback(err)
      : callback(null, new self.models.File(self, _.extend(res.headers, {
          container: container,
          name: file
        })));
  });
};

/**
 * client.getFiles
 *
 * @description get the list of files in a container. Returns at most 10,000 files if options.limit is unspecified.
 * Abstracts the aggregation of files in the case that options.limit is >10,000.
 *
 * @param {String|object}     container     the container or containerName
 * @param {object|Function}   options
 * @param {Number}            [options.limit]   the number of records to return
 * @param {String}            [options.marker]  the id of the first record to return in the current query
 * @param {Function}          callback
 */
exports.getFiles = function (container, options, callback) {
  var self = this;

  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // If limit is not specified, or it is <=10k, just make a single request
  if (!options.limit || options.limit <= 10000) {
    return this._getFiles(container, options, callback);
  }

  // Limit is specified and is >10k. Abstract the aggregation of files (cloudfiles returns max 10k at once)
  var files = [];

  // Keep track of how many files are left to collect
  var remainingLimit = options.limit;
  delete options.limit;

  var getFilesCallback = function(err, someFiles) {
    if (err) {
      return callback(err);
    }

    files = files.concat(someFiles);
    remainingLimit -= someFiles.length;

    // Check if we should attempt to retrieve more results
    if (remainingLimit > 0 && someFiles.length === 10000) {
      options.marker = someFiles.pop().name;

      // Once the remainingLimit value becomes < 10000, we must pass it
      if (remainingLimit < 10000) {
        options.limit = remainingLimit;
      }

      self._getFiles(container, options, getFilesCallback);
    } else {
      callback(null, files);
    }
  };

  this._getFiles(container, options, getFilesCallback);
};

exports._getFiles = function (container, options, callback) {
  var containerName = container instanceof this.models.Container ? container.name : container,
      self = this;

  var getFilesOpts = {
      path: containerName,
      qs: _.extend({
          format: 'json'
      }, _.pick(options, ['limit', 'marker', 'prefix', 'path', 'delimiter']))
    };

    if (options.endMarker) {
      getFilesOpts.qs.end_marker = options.endMarker;
    }

  if (options.end_marker) {
    getFilesOpts.qs.end_marker = options.end_marker;
  }

  if (options.prefix) {
    getFilesOpts.qs.prefix  = options.prefix;
  }

  if (options.path) {
    getFilesOpts.qs.path  = options.path;
  }

  if (options.delimiter) {
    getFilesOpts.qs.delimiter  = options.delimiter;
  }

  this._request(getFilesOpts, function (err, body) {

    if (err) {
      return callback(err);
    }
    else if (!body || !(body instanceof Array)) {
      return new Error('Malformed API Response')
    }

    return callback(null, body.map(function (file) {
      file.container = container;
      return new self.models.File(self, file);
    }));
  });
};

/**
 * client.updateFileMetadata
 *
 * @description Updates the specified `file` with the provided metadata `headers`
 * in the Rackspace Cloud Files account associated with this instance.
 *
 * @param {String|object}     container     the container or containerName
 * @param {String|object}     file          the file or fileName to update
 * @param callback
 */
exports.updateFileMetadata = function (container, file, callback) {
  var self = this,
      containerName = container instanceof self.models.Container ? container.name : container;

  if (!file instanceof base.File) {
    throw new Error('Must update an existing file instance');
  }

  var updateFileOpts = {
    method: 'POST',
    container: containerName,
    path: file.name,
    headers: self.serializeMetadata(self.OBJECT_META_PREFIX, file.metadata)
  };

  this._request(updateFileOpts, function (err) {
    return err
      ? callback(err)
      : callback(null, file);
  });
};

<|endoftext|>


<commit_before>/*
 * client.js: Storage client for Openstack Object Storage
 *
 * (C) 2013 Rackspace, Ken Perkins
 * MIT LICENSE
 *
 */

var util = require('util'),
    urlJoin = require('url-join'),
    openstack = require('../../client'),
    StorageClient = require('../storageClient').StorageClient,
    _ = require('underscore');

var Client = exports.Client = function (options) {
  openstack.Client.call(this, options);

  this.models = {
    Container: require('../container').Container,
    File: require('../file').File
  };

  _.extend(this, require('./containers'));
  _.extend(this, require('./files'));

  this.serviceType = 'object-store';
};

util.inherits(Client, openstack.Client);
_.extend(Client.prototype, StorageClient.prototype);
<commit_msg>Adding explicit check for version in storage client<commit_after>/*
 * client.js: Storage client for Openstack Object Storage
 *
 * (C) 2013 Rackspace, Ken Perkins
 * MIT LICENSE
 *
 */

var util = require('util'),
    urlJoin = require('url-join'),
    openstack = require('../../client'),
    StorageClient = require('../storageClient').StorageClient,
    _ = require('underscore');

var Client = exports.Client = function (options) {
  // explicitly prevent service catalog usage if using version 1.0 for swift
  // this must happen before we call into the base openstack client.
  if (options.version === 1 || options.version === '/v1.0') {
    options.useServiceCatalog = false;
  }

  openstack.Client.call(this, options);

  this.models = {
    Container: require('../container').Container,
    File: require('../file').File
  };

  _.extend(this, require('./containers'));
  _.extend(this, require('./files'));

  this.serviceType = 'object-store';
};

util.inherits(Client, openstack.Client);
_.extend(Client.prototype, StorageClient.prototype);
<|endoftext|>


<commit_before>/*

* update midi-manager mapping to be an array instead of an object
 -> BUT what happens with the grid?
 -> we can't transform the mapping object into an array when it's changing
 because of the active state of a button when it's pressed. MAYBE save the active state
 into another element of the midi controller? not into the mapping itself? so
 the active state can be updated individually and doesn't trigger the mapping

*/

✅ const channels = [...Array(512)].map(() => 0)
☀ const fixtures = [{}, {}]
// effects.js
export const flash(n) => ({})
export const fade(n) => ({})
export const rgb(obj) => ({})

/**
 */
import {* as effects} from './effects'

const universums = [{
  id: 'FOO',
  channels: [...channels]
},
{
  id: 'BAR',
  channels: [...channels]
}]
// effects.flash
// effects.fade
const animations = [
  {
    name: 'flash and fade',
    id: 'FLASH_FADE',
    data: {
      // (simple as possible)
      keyframes: {
        ...flash(0, 0.5, 3),
        '0': {
          dimmer: 255
        },
        '0.25': {
          color: [0, 0, 255]
        },
        '1': {
          color: [0, 255, 0],
          dimmer: 200
        }
      }
    }
  },
  {
    name: 'rainbow',
    id: 'RAINBOW',
    data: {/*  animation data */}
  }
]

// This is how Keytime wants the timeline / keyframes

 'timeline': [{
   'name': 'color',
   'keyframes': [
     { 'time': 0, 'value': [0, 0, 0] },
     { 'time': 0.25, 'value': [0, 0, 0] },
     { 'time': 0.5, 'value': [255, 0, 0] },
     { 'time': 0.75, 'value': [0, 0, 0] },
     { 'time': .9, 'value': [0, 0, 0] }
   ]
 }]

 // Internally we split the keyframes into sub keyframes to have more control over them

keyframes =
 [{
   '0': 'keyframes0',
   '1': 'keyframes0.25'
 },
 {
   '0': 'keyframes0.25',
   '1': 'keyframes1'
 }]

const scenes = [{
  name: 'intro scene',
  id: 'INTRO_SCENE',
  animations: [{id: 'FLASH_FADE', start: 0, dur: 0.2}, {id: 'RAINBOW', start: 0.4, dur: 0.5}]
}]

const seclectedFixtures = [{
  id: 'FUN_GENERATION',
  universum: 'FOO',
  properties: {
    r: {
      id: 'RED',
      channel: 33
    },
    g: {
      id: 'GREEN',
      channel: 34
    },
    b: {
      id: 'BLUE',
      channel: 35
    }
  },
  scenes: [{
    id: 'INTRO_SCENE',
    start: [],
    trigger:
    'DEVICE:CHANNEL:NOTE' ,
    duration: 60000
  }]
}]

const midiManger = {
  enabled: true,
  controllers: [{
    id: '489-491-491',
    name: 'nanoPAD2',
    input: 'nanoPAD2 PAD',
    output: 'nanoPAD2 CTRL',
    width: 8,
    height: 2,
    mapping: {
      0: {
        note: 35
      }
    }
  }]
}

const timelineManager = {
  playing: true,
  scenes: [
    '123-123-123'
  ],
  live: true // true = add a scene and when it's done it's removed / false = add a scene and keep it after it's done
}

// @see https://github.com/NERDDISCO/VisionLord/blob/40d7495d51f2edd8d4e563f1199854cb7c86141b/src/core/BluetoothManager.js
const bluetoothManager = {
  devices: []
}
<commit_msg>chore(ideas): Added new ideas<commit_after>/*

change the address of a fixture
improve the layout of the fixtures
make fixtures searchable
move json generation into worker
add a bulk of Fixtures

* update midi-manager mapping to be an array instead of an object
 -> BUT what happens with the grid?
 -> we can't transform the mapping object into an array when it's changing
 because of the active state of a button when it's pressed. MAYBE save the active state
 into another element of the midi controller? not into the mapping itself? so
 the active state can be updated individually and doesn't trigger the mapping

*/

✅ const channels = [...Array(512)].map(() => 0)
☀ const fixtures = [{}, {}]
// effects.js
export const flash(n) => ({})
export const fade(n) => ({})
export const rgb(obj) => ({})

/**
 */
import {* as effects} from './effects'

const universums = [{
  id: 'FOO',
  channels: [...channels]
},
{
  id: 'BAR',
  channels: [...channels]
}]
// effects.flash
// effects.fade
const animations = [
  {
    name: 'flash and fade',
    id: 'FLASH_FADE',
    data: {
      // (simple as possible)
      keyframes: {
        ...flash(0, 0.5, 3),
        '0': {
          dimmer: 255
        },
        '0.25': {
          color: [0, 0, 255]
        },
        '1': {
          color: [0, 255, 0],
          dimmer: 200
        }
      }
    }
  },
  {
    name: 'rainbow',
    id: 'RAINBOW',
    data: {/*  animation data */}
  }
]

// This is how Keytime wants the timeline / keyframes

 'timeline': [{
   'name': 'color',
   'keyframes': [
     { 'time': 0, 'value': [0, 0, 0] },
     { 'time': 0.25, 'value': [0, 0, 0] },
     { 'time': 0.5, 'value': [255, 0, 0] },
     { 'time': 0.75, 'value': [0, 0, 0] },
     { 'time': .9, 'value': [0, 0, 0] }
   ]
 }]

 // Internally we split the keyframes into sub keyframes to have more control over them

keyframes =
 [{
   '0': 'keyframes0',
   '1': 'keyframes0.25'
 },
 {
   '0': 'keyframes0.25',
   '1': 'keyframes1'
 }]

const scenes = [{
  name: 'intro scene',
  id: 'INTRO_SCENE',
  animations: [{id: 'FLASH_FADE', start: 0, dur: 0.2}, {id: 'RAINBOW', start: 0.4, dur: 0.5}]
}]

const seclectedFixtures = [{
  id: 'FUN_GENERATION',
  universum: 'FOO',
  properties: {
    r: {
      id: 'RED',
      channel: 33
    },
    g: {
      id: 'GREEN',
      channel: 34
    },
    b: {
      id: 'BLUE',
      channel: 35
    }
  },
  scenes: [{
    id: 'INTRO_SCENE',
    start: [],
    trigger:
    'DEVICE:CHANNEL:NOTE' ,
    duration: 60000
  }]
}]

const midiManger = {
  enabled: true,
  controllers: [{
    id: '489-491-491',
    name: 'nanoPAD2',
    input: 'nanoPAD2 PAD',
    output: 'nanoPAD2 CTRL',
    width: 8,
    height: 2,
    mapping: {
      0: {
        note: 35
      }
    }
  }]
}

const timelineManager = {
  playing: true,
  scenes: [
    '123-123-123'
  ],
  live: true // true = add a scene and when it's done it's removed / false = add a scene and keep it after it's done
}

// @see https://github.com/NERDDISCO/VisionLord/blob/40d7495d51f2edd8d4e563f1199854cb7c86141b/src/core/BluetoothManager.js
const bluetoothManager = {
  devices: []
}
<|endoftext|>


<commit_before>import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import App from './App'
import GettingStarted from './GettingStarted'
import Components from './Components'
import Customize from './Customize'

const Routes = () =>
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <IndexRoute component={GettingStarted} />
      <Route path="components">
        <Route path="**" component={Components} />
      </Route>
      <Route path="customize" component={Customize} />
    </Route>
  </Router>

export default Routes
<commit_msg>Fully async routing<commit_after>import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import App from './App'

const Routes = () =>
  <Router history={browserHistory}>
    <Route component={App} path="/">
      <IndexRoute
        getComponent={(_nextState, callback) => {
          require.ensure([], require => {
            callback(null, require('./GettingStarted'))
          }, 'getting-started')
        }}
      />

      <Route path="components">
        <Route
          getComponent={(_nextState, callback) => {
            require.ensure([], require => {
              callback(null, require('./Components'))
            }, 'components')
          }}
          path="**"
        />
      </Route>

      <Route
        getComponent={(_nextState, callback) => {
          require.ensure([], require => {
            callback(null, require('./Customize'))
          }, 'customize')
        }}
        path="customize"
      />
    </Route>
  </Router>

export default Routes
<|endoftext|>


<commit_before>import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import SearchForm from 'shared/components/partials/SearchForm';

describe('<SearchForm />', () => {

  let wrapper = {};
  const spy = sinon.spy();

  before(() => {
    wrapper = mount(<SearchForm getSearchResults={spy} />);
  });

  it('should returns a <form />', () => {
    expect(wrapper.find('form')).to.have.length(1);
  });

  it('should call getSearchResults when input changed', () => {
    wrapper.find('input').simulate('change');
    expect(spy.calledOnce).to.be.true;
  });

});<commit_msg>refactor test for search form<commit_after>import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import SearchForm from 'shared/components/partials/SearchForm';

function mountComponent() {
  const spy = sinon.spy();
  const wrapper = mount(<SearchForm getSearchResults={spy} />);
  return {
    spy,
    wrapper
  }
}

describe('<SearchForm />', () => {

  it('should returns a <form />', () => {
    const wrapper = shallow(<SearchForm />);
    expect(wrapper.find('form')).to.have.length(1);
  });

  describe('should response actions', () => {

    it('should call getSearchResults after form submitted', () => {
      const { wrapper, spy } = mountComponent();
      wrapper.find('form').simulate('submit');
      expect(spy.calledOnce).to.be.true;
    });

    it('should call getSearchResults when input changed', () => {
      const { wrapper, spy } = mountComponent();
      wrapper.find('input').simulate('change');
      expect(spy.calledOnce).to.be.true;
    });
  });
});<|endoftext|>


<commit_before>import {Template} from "meteor/templating";
import {Session} from "meteor/session";
import {Meteor} from "meteor/meteor";
import {FlowRouter} from "meteor/kadira:flow-router";
import {Connections} from "/lib/imports/collections/connections";
import Helper from "/client/imports/helper";
import "./validation_rules.html";

const toastr = require('toastr');
const Ladda = require('ladda');

/**
 * Created by RSercan on 15.2.2016.
 */

const initRules = function (selectedCollection) {
    Ladda.create(document.querySelector('#btnSaveValidationRules')).start();
    Helper.initializeCollectionsCombobox();

    if (!selectedCollection) {
        Ladda.stopAll();
        return;
    }

    const cmbValidationAction = $('#cmbValidationAction');
    const cmbValidationLevel = $('#cmbValidationLevel');

    // clear form
    cmbValidationLevel.prop('disabled', false).val('off').trigger("chosen:updated");
    cmbValidationAction.prop('disabled', false).val('warn').trigger("chosen:updated");

    const connection = Connections.findOne({_id: Session.get(Helper.strSessionConnection)});
    Meteor.call('listCollectionNames', connection.databaseName, function (err, result) {
        if (err || result.error) {
            Helper.showMeteorFuncError(err, result, "Couldn't fetch rules");
        }
        else {
            if (result.result) {
                for (let collection of result.result) {
                    if (collection.name === selectedCollection) {
                        if (collection.options && collection.options.validationAction) {
                            cmbValidationAction.val(collection.options.validationAction);
                        }

                        if (collection.options && collection.options.validationLevel) {
                            cmbValidationLevel.val(collection.options.validationLevel);
                        }

                        if (collection.options.validator) {
                            Helper.setCodeMirrorValue($('#divValidator'), JSON.stringify(collection.options.validator, null, 1));
                        } else {
                            Helper.setCodeMirrorValue($('#divValidator'), "");
                        }
                    }
                }
            }
        }

        Ladda.stopAll();
    });
};

Template.validationRules.onRendered(function () {
    if (Session.get(Helper.strSessionCollectionNames) == undefined) {
        FlowRouter.go('/databaseStats');
        return;
    }

    let settings = this.subscribe('settings');
    let connections = this.subscribe('connections');

    this.autorun(() => {
        if (settings.ready() && connections.ready()) {
            Helper.initializeCodeMirror($('#divValidator'), 'txtValidator', false, 300);
            initRules();
        }
    });

});

Template.validationRules.events({
    'change #cmbCollections'(e){
        e.preventDefault();
        const selectedCollection = $("#cmbCollections").chosen().val();
        if (selectedCollection) {
            initRules(selectedCollection);
        }
    },

    'click #btnSaveValidationRules'  (e) {
        e.preventDefault();

        Ladda.create(document.querySelector('#btnSaveValidationRules')).start();

        const validationAction = $('#cmbValidationAction').val();
        const validationLevel = $('#cmbValidationLevel').val();
        const selectedCollection = $('#cmbCollections').val();

        if (!selectedCollection) {
            toastr.warning("Please select a collection first !");
            Ladda.stopAll();
            return;
        }

        let validator = Helper.getCodeMirrorValue($('#divValidator'));
        validator = Helper.convertAndCheckJSON(validator);
        if (validator["ERROR"]) {
            toastr.error("Syntax Error on validator: " + validator["ERROR"]);
            Ladda.stopAll();
            return;
        }

        const command = {};
        command.collMod = selectedCollection;
        command.validator = validator;
        command.validationLevel = validationLevel;
        command.validationAction = validationAction;

        Meteor.call('command', command, false, {}, function (err, result) {
            if (err || result.error) {
                Helper.showMeteorFuncError(err, result, "Couldn't save rule");
            } else {
                toastr.success("Successfully saved");
            }

            Ladda.stopAll();
        });
    }

});<commit_msg>added chosen for validation comboboxes<commit_after>import {Template} from "meteor/templating";
import {Session} from "meteor/session";
import {Meteor} from "meteor/meteor";
import {FlowRouter} from "meteor/kadira:flow-router";
import {Connections} from "/lib/imports/collections/connections";
import Helper from "/client/imports/helper";
import "./validation_rules.html";

const toastr = require('toastr');
const Ladda = require('ladda');

/**
 * Created by RSercan on 15.2.2016.
 */

const initRules = function (selectedCollection) {
    Ladda.create(document.querySelector('#btnSaveValidationRules')).start();
    Helper.initializeCollectionsCombobox();

    if (!selectedCollection) {
        Ladda.stopAll();
        return;
    }

    const cmbValidationAction = $('#cmbValidationAction');
    const cmbValidationLevel = $('#cmbValidationLevel');

    // clear form
    cmbValidationLevel.prop('disabled', false).val('off').trigger("chosen:updated");
    cmbValidationAction.prop('disabled', false).val('warn').trigger("chosen:updated");

    const connection = Connections.findOne({_id: Session.get(Helper.strSessionConnection)});
    Meteor.call('listCollectionNames', connection.databaseName, function (err, result) {
        if (err || result.error) {
            Helper.showMeteorFuncError(err, result, "Couldn't fetch rules");
        }
        else {
            if (result.result) {
                for (let collection of result.result) {
                    if (collection.name === selectedCollection) {
                        if (collection.options && collection.options.validationAction) {
                            cmbValidationAction.val(collection.options.validationAction);
                        }

                        if (collection.options && collection.options.validationLevel) {
                            cmbValidationLevel.val(collection.options.validationLevel);
                        }

                        if (collection.options.validator) {
                            Helper.setCodeMirrorValue($('#divValidator'), JSON.stringify(collection.options.validator, null, 1));
                        } else {
                            Helper.setCodeMirrorValue($('#divValidator'), "");
                        }
                    }
                }
            }
        }

        Ladda.stopAll();
    });
};

Template.validationRules.onRendered(function () {
    if (Session.get(Helper.strSessionCollectionNames) == undefined) {
        FlowRouter.go('/databaseStats');
        return;
    }

    let settings = this.subscribe('settings');
    let connections = this.subscribe('connections');

    this.autorun(() => {
        if (settings.ready() && connections.ready()) {
            Helper.initializeCodeMirror($('#divValidator'), 'txtValidator', false, 300);
            $('#cmbValidationLevel').chosen();
            $('#cmbValidationAction').chosen();
            initRules();
        }
    });

});

Template.validationRules.events({
    'change #cmbCollections'(e){
        e.preventDefault();
        const selectedCollection = $("#cmbCollections").chosen().val();
        if (selectedCollection) {
            initRules(selectedCollection);
        }
    },

    'click #btnSaveValidationRules'  (e) {
        e.preventDefault();

        Ladda.create(document.querySelector('#btnSaveValidationRules')).start();

        const validationAction = $('#cmbValidationAction').val();
        const validationLevel = $('#cmbValidationLevel').val();
        const selectedCollection = $('#cmbCollections').val();

        if (!selectedCollection) {
            toastr.warning("Please select a collection first !");
            Ladda.stopAll();
            return;
        }

        let validator = Helper.getCodeMirrorValue($('#divValidator'));
        validator = Helper.convertAndCheckJSON(validator);
        if (validator["ERROR"]) {
            toastr.error("Syntax Error on validator: " + validator["ERROR"]);
            Ladda.stopAll();
            return;
        }

        const command = {};
        command.collMod = selectedCollection;
        command.validator = validator;
        command.validationLevel = validationLevel;
        command.validationAction = validationAction;

        Meteor.call('command', command, false, {}, function (err, result) {
            if (err || result.error) {
                Helper.showMeteorFuncError(err, result, "Couldn't save rule");
            } else {
                toastr.success("Successfully saved");
            }

            Ladda.stopAll();
        });
    }

});<|endoftext|>


<commit_before>import React from 'react';
import { Image, ActivityIndicator, NetInfo, Platform } from 'react-native';
import RNFS, { DocumentDirectoryPath } from 'react-native-fs';
import ResponsiveImage from 'react-native-responsive-image';

const SHA1 = require("crypto-js/sha1");
const URL = require('url-parse');

export default
class CacheableImage extends React.Component {

    constructor(props) {
        super(props)
        this.imageDownloadBegin = this.imageDownloadBegin.bind(this);
        this.imageDownloadProgress = this.imageDownloadProgress.bind(this);
        this._handleConnectivityChange = this._handleConnectivityChange.bind(this);
        this._stopDownload = this._stopDownload.bind(this);

        this.state = {
            isRemote: false,
            cachedImagePath: null,
            cacheable: true
        };

        this.networkAvailable = props.networkAvailable;
        this.downloading = false;
        this.jobId = null;
    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.source != this.props.source || nextProps.networkAvailable != this.networkAvailable) {
            this.networkAvailable = nextProps.networkAvailable;
            this._processSource(nextProps.source);
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextState === this.state && nextProps === this.props) {
            return false;
        }
        return true;
    }

    async imageDownloadBegin(info) {
        switch (info.statusCode) {
            case 404:
            case 403:
                break;
            default:
                this.downloading = true;
                this.jobId = info.jobId;
        }
    }

    async imageDownloadProgress(info) {
        if ((info.contentLength / info.bytesWritten) == 1) {
            this.downloading = false;
            this.jobId = null;
        }
    }

    async checkImageCache(imageUri, cachePath, cacheKey) {
        const dirPath = DocumentDirectoryPath+'/'+cachePath;
        const filePath = dirPath+'/'+cacheKey;

        RNFS
        .stat(filePath)
        .then((res) => {
            if (res.isFile() && res.size > 0) {
                // It's possible the component has already unmounted before setState could be called.
                // It happens when the defaultSource and source have both been cached.
                // An attempt is made to display the default however it's instantly removed since source is available

                // means file exists, ie, cache-hit
                this.setState({cacheable: true, cachedImagePath: filePath});
            }
            else {
                throw Error("CacheableImage: Invalid file in checkImageCache()");
            }
        })
        .catch((err) => {

            // means file does not exist
            // first make sure network is available..
            // if (! this.state.networkAvailable) {
            if (! this.networkAvailable) {
                return;
            }

            // then make sure directory exists.. then begin download
            // The NSURLIsExcludedFromBackupKey property can be provided to set this attribute on iOS platforms.
            // Apple will reject apps for storing offline cache data that does not have this attribute.
            // https://github.com/johanneslumpe/react-native-fs#mkdirfilepath-string-options-mkdiroptions-promisevoid
            RNFS
            .mkdir(dirPath, {NSURLIsExcludedFromBackupKey: true})
            .then(() => {

                // before we change the cachedImagePath.. if the previous cachedImagePath was set.. remove it
                if (this.state.cacheable && this.state.cachedImagePath) {
                    let delImagePath = this.state.cachedImagePath;
                    this._deleteFilePath(delImagePath);
                }

                // If already downloading, cancel the job
                if (this.jobId) {
                    this._stopDownload();
                }

                let downloadOptions = {
                    fromUrl: imageUri,
                    toFile: filePath,
                    background: this.props.downloadInBackground,
                    begin: this.imageDownloadBegin,
                    progress: this.imageDownloadProgress
                };

                // directory exists.. begin download
                let download = RNFS
                .downloadFile(downloadOptions);

                this.downloading = true;
                this.jobId = download.jobId;

                download.promise
                .then((res) => {
                    this.downloading = false;
                    this.jobId = null;

                    switch (res.statusCode) {
                        case 404:
                        case 403:
                            this.setState({cacheable: false, cachedImagePath: null});
                            break;
                        default:
                            this.setState({cacheable: true, cachedImagePath: filePath});
                    }
                })
                .catch((err) => {
                    // error occurred while downloading or download stopped.. remove file if created
                    this._deleteFilePath(filePath);

                    // If there was no in-progress job, it may have been cancelled already (and this component may be unmounted)
                    if (this.downloading) {
                        this.downloading = false;
                        this.jobId = null;
                        this.setState({cacheable: false, cachedImagePath: null});
                    }
                });
            })
            .catch((err) => {
                this._deleteFilePath(filePath);
                this.setState({cacheable: false, cachedImagePath: null});
            });
        });
    }

    _deleteFilePath(filePath) {
        RNFS
        .exists(filePath)
        .then((res) => {
            if (res) {
                RNFS
                .unlink(filePath)
                .catch((err) => {});
            }
        });
    }

    _processSource(source, skipSourceCheck) {

        if (source !== null
            && source != ''
            && typeof source === "object"
            && source.hasOwnProperty('uri')
            && (
                skipSourceCheck ||
                typeof skipSourceCheck === 'undefined' ||
                (!skipSourceCheck && source != this.props.source)
           )
        )
        { // remote

            if (this.jobId) { // sanity
                this._stopDownload();
            }

            const url = new URL(source.uri, null, true);

            // handle query params for cache key
            let cacheable = url.pathname;
            if (Array.isArray(this.props.useQueryParamsInCacheKey)) {
                this.props.useQueryParamsInCacheKey.forEach(function(k) {
                    if (url.query.hasOwnProperty(k)) {
                        cacheable = cacheable.concat(url.query[k]);
                    }
                });
            }
            else if (this.props.useQueryParamsInCacheKey) {
                cacheable = cacheable.concat(url.query);
            }

            const type = url.pathname.replace(/.*\.(.*)/, '$1');
            const cacheKey = SHA1(cacheable) + (type.length < url.pathname.length ? '.' + type : '');

            this.checkImageCache(source.uri, url.host, cacheKey);
            this.setState({isRemote: true});
        }
        else {
            this.setState({isRemote: false});
        }
    }

    _stopDownload() {
        if (!this.jobId) return;

        this.downloading = false;
        RNFS.stopDownload(this.jobId);
        this.jobId = null;
    }

    componentWillMount() {
        if (this.props.checkNetwork) {
            NetInfo.isConnected.addEventListener('change', this._handleConnectivityChange);
            // componentWillUnmount unsets this._handleConnectivityChange in case the component unmounts before this fetch resolves
            NetInfo.isConnected.fetch().done(this._handleConnectivityChange);
        }

        this._processSource(this.props.source, true);
    }

    componentWillUnmount() {
        if (this.props.checkNetwork) {
            NetInfo.isConnected.removeEventListener('change', this._handleConnectivityChange);
            this._handleConnectivityChange = null;
        }

        if (this.downloading && this.jobId) {
            this._stopDownload();
        }
    }

    async _handleConnectivityChange(isConnected) {
        this.networkAvailable = isConnected;
    };

    render() {
        if (!this.state.isRemote && !this.props.defaultSource) {
            return this.renderLocal();
        }

        if (this.state.cacheable && this.state.cachedImagePath) {
            return this.renderCache();
        }

        if (this.props.defaultSource) {
            return this.renderDefaultSource();
        }

        return (
            <ActivityIndicator {...this.props.activityIndicatorProps} />
        );
    }

    renderCache() {
        const { children, defaultSource, checkNetwork, networkAvailable, downloadInBackground, activityIndicatorProps, ...props } = this.props;
        return (
            <ResponsiveImage {...props} source={{uri: 'file://'+this.state.cachedImagePath}}>
            {children}
            </ResponsiveImage>
        );
    }

    renderLocal() {
        const { children, defaultSource, checkNetwork, networkAvailable, downloadInBackground, activityIndicatorProps, ...props } = this.props;
        return (
            <ResponsiveImage {...props}>
            {children}
            </ResponsiveImage>
        );
    }

    renderDefaultSource() {
        const { children, defaultSource, checkNetwork, networkAvailable, ...props } = this.props;
        return (
            <CacheableImage {...props} source={defaultSource} checkNetwork={false} networkAvailable={this.networkAvailable} >
            {children}
            </CacheableImage>
        );
    }
}

CacheableImage.propTypes = {
    activityIndicatorProps: React.PropTypes.object,
    defaultSource: Image.propTypes.source,
    useQueryParamsInCacheKey: React.PropTypes.oneOfType([
        React.PropTypes.bool,
        React.PropTypes.array
    ]),
    checkNetwork: React.PropTypes.bool,
    networkAvailable: React.PropTypes.bool,
    downloadInBackground: React.PropTypes.bool
};

console.log(Platform);

CacheableImage.defaultProps = {
    style: { backgroundColor: 'transparent' },
    activityIndicatorProps: {
        style: { backgroundColor: 'transparent', flex: 1 }
    },
    useQueryParamsInCacheKey: false, // bc
    checkNetwork: true,
    networkAvailable: false,
    downloadInBackground: (Platform.OS === 'ios') ? false : true
};
<commit_msg>Support for placing within TouchableHighlight<commit_after>import React from 'react';
import { Image, ActivityIndicator, NetInfo, Platform } from 'react-native';
import RNFS, { DocumentDirectoryPath } from 'react-native-fs';
import ResponsiveImage from 'react-native-responsive-image';

const SHA1 = require("crypto-js/sha1");
const URL = require('url-parse');

export default
class CacheableImage extends React.Component {

    constructor(props) {
        super(props)
        this.imageDownloadBegin = this.imageDownloadBegin.bind(this);
        this.imageDownloadProgress = this.imageDownloadProgress.bind(this);
        this._handleConnectivityChange = this._handleConnectivityChange.bind(this);
        this._stopDownload = this._stopDownload.bind(this);

        this.state = {
            isRemote: false,
            cachedImagePath: null,
            cacheable: true
        };

        this.networkAvailable = props.networkAvailable;
        this.downloading = false;
        this.jobId = null;
    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.source != this.props.source || nextProps.networkAvailable != this.networkAvailable) {
            this.networkAvailable = nextProps.networkAvailable;
            this._processSource(nextProps.source);
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextState === this.state && nextProps === this.props) {
            return false;
        }
        return true;
    }

    setNativeProps(nativeProps) {
        if (this._imageComponent) {
            this._imageComponent.setNativeProps(nativeProps);
        }
    }

    async imageDownloadBegin(info) {
        switch (info.statusCode) {
            case 404:
            case 403:
                break;
            default:
                this.downloading = true;
                this.jobId = info.jobId;
        }
    }

    async imageDownloadProgress(info) {
        if ((info.contentLength / info.bytesWritten) == 1) {
            this.downloading = false;
            this.jobId = null;
        }
    }

    async checkImageCache(imageUri, cachePath, cacheKey) {
        const dirPath = DocumentDirectoryPath+'/'+cachePath;
        const filePath = dirPath+'/'+cacheKey;

        RNFS
        .stat(filePath)
        .then((res) => {
            if (res.isFile() && res.size > 0) {
                // It's possible the component has already unmounted before setState could be called.
                // It happens when the defaultSource and source have both been cached.
                // An attempt is made to display the default however it's instantly removed since source is available

                // means file exists, ie, cache-hit
                this.setState({cacheable: true, cachedImagePath: filePath});
            }
            else {
                throw Error("CacheableImage: Invalid file in checkImageCache()");
            }
        })
        .catch((err) => {

            // means file does not exist
            // first make sure network is available..
            // if (! this.state.networkAvailable) {
            if (! this.networkAvailable) {
                return;
            }

            // then make sure directory exists.. then begin download
            // The NSURLIsExcludedFromBackupKey property can be provided to set this attribute on iOS platforms.
            // Apple will reject apps for storing offline cache data that does not have this attribute.
            // https://github.com/johanneslumpe/react-native-fs#mkdirfilepath-string-options-mkdiroptions-promisevoid
            RNFS
            .mkdir(dirPath, {NSURLIsExcludedFromBackupKey: true})
            .then(() => {

                // before we change the cachedImagePath.. if the previous cachedImagePath was set.. remove it
                if (this.state.cacheable && this.state.cachedImagePath) {
                    let delImagePath = this.state.cachedImagePath;
                    this._deleteFilePath(delImagePath);
                }

                // If already downloading, cancel the job
                if (this.jobId) {
                    this._stopDownload();
                }

                let downloadOptions = {
                    fromUrl: imageUri,
                    toFile: filePath,
                    background: this.props.downloadInBackground,
                    begin: this.imageDownloadBegin,
                    progress: this.imageDownloadProgress
                };

                // directory exists.. begin download
                let download = RNFS
                .downloadFile(downloadOptions);

                this.downloading = true;
                this.jobId = download.jobId;

                download.promise
                .then((res) => {
                    this.downloading = false;
                    this.jobId = null;

                    switch (res.statusCode) {
                        case 404:
                        case 403:
                            this.setState({cacheable: false, cachedImagePath: null});
                            break;
                        default:
                            this.setState({cacheable: true, cachedImagePath: filePath});
                    }
                })
                .catch((err) => {
                    // error occurred while downloading or download stopped.. remove file if created
                    this._deleteFilePath(filePath);

                    // If there was no in-progress job, it may have been cancelled already (and this component may be unmounted)
                    if (this.downloading) {
                        this.downloading = false;
                        this.jobId = null;
                        this.setState({cacheable: false, cachedImagePath: null});
                    }
                });
            })
            .catch((err) => {
                this._deleteFilePath(filePath);
                this.setState({cacheable: false, cachedImagePath: null});
            });
        });
    }

    _deleteFilePath(filePath) {
        RNFS
        .exists(filePath)
        .then((res) => {
            if (res) {
                RNFS
                .unlink(filePath)
                .catch((err) => {});
            }
        });
    }

    _processSource(source, skipSourceCheck) {

        if (source !== null
            && source != ''
            && typeof source === "object"
            && source.hasOwnProperty('uri')
            && (
                skipSourceCheck ||
                typeof skipSourceCheck === 'undefined' ||
                (!skipSourceCheck && source != this.props.source)
           )
        )
        { // remote

            if (this.jobId) { // sanity
                this._stopDownload();
            }

            const url = new URL(source.uri, null, true);

            // handle query params for cache key
            let cacheable = url.pathname;
            if (Array.isArray(this.props.useQueryParamsInCacheKey)) {
                this.props.useQueryParamsInCacheKey.forEach(function(k) {
                    if (url.query.hasOwnProperty(k)) {
                        cacheable = cacheable.concat(url.query[k]);
                    }
                });
            }
            else if (this.props.useQueryParamsInCacheKey) {
                cacheable = cacheable.concat(url.query);
            }

            const type = url.pathname.replace(/.*\.(.*)/, '$1');
            const cacheKey = SHA1(cacheable) + (type.length < url.pathname.length ? '.' + type : '');

            this.checkImageCache(source.uri, url.host, cacheKey);
            this.setState({isRemote: true});
        }
        else {
            this.setState({isRemote: false});
        }
    }

    _stopDownload() {
        if (!this.jobId) return;

        this.downloading = false;
        RNFS.stopDownload(this.jobId);
        this.jobId = null;
    }

    componentWillMount() {
        if (this.props.checkNetwork) {
            NetInfo.isConnected.addEventListener('change', this._handleConnectivityChange);
            // componentWillUnmount unsets this._handleConnectivityChange in case the component unmounts before this fetch resolves
            NetInfo.isConnected.fetch().done(this._handleConnectivityChange);
        }

        this._processSource(this.props.source, true);
    }

    componentWillUnmount() {
        if (this.props.checkNetwork) {
            NetInfo.isConnected.removeEventListener('change', this._handleConnectivityChange);
            this._handleConnectivityChange = null;
        }

        if (this.downloading && this.jobId) {
            this._stopDownload();
        }
    }

    async _handleConnectivityChange(isConnected) {
        this.networkAvailable = isConnected;
    };

    render() {
        if (!this.state.isRemote && !this.props.defaultSource) {
            return this.renderLocal();
        }

        if (this.state.cacheable && this.state.cachedImagePath) {
            return this.renderCache();
        }

        if (this.props.defaultSource) {
            return this.renderDefaultSource();
        }

        const { children, defaultSource, checkNetwork, networkAvailable, downloadInBackground, activityIndicatorProps, ...props } = this.props;
        const style = [activityIndicatorProps.style, this.props.style];
        return (
            <ActivityIndicator {...props} {...activityIndicatorProps} style={style} />
        );
    }

    renderCache() {
        const { children, defaultSource, checkNetwork, networkAvailable, downloadInBackground, activityIndicatorProps, ...props } = this.props;
        return (
            <ResponsiveImage {...props} source={{uri: 'file://'+this.state.cachedImagePath}} ref={component => this._imageComponent = component}>
            {children}
            </ResponsiveImage>
        );
    }

    renderLocal() {
        const { children, defaultSource, checkNetwork, networkAvailable, downloadInBackground, activityIndicatorProps, ...props } = this.props;
        return (
            <ResponsiveImage {...props} ref={component => this._imageComponent = component}>
            {children}
            </ResponsiveImage>
        );
    }

    renderDefaultSource() {
        const { children, defaultSource, checkNetwork, networkAvailable, ...props } = this.props;
        return (
            <CacheableImage {...props} source={defaultSource} checkNetwork={false} networkAvailable={this.networkAvailable} ref={component => this._imageComponent = component}>
            {children}
            </CacheableImage>
        );
    }
}

CacheableImage.propTypes = {
    activityIndicatorProps: React.PropTypes.object,
    defaultSource: Image.propTypes.source,
    useQueryParamsInCacheKey: React.PropTypes.oneOfType([
        React.PropTypes.bool,
        React.PropTypes.array
    ]),
    checkNetwork: React.PropTypes.bool,
    networkAvailable: React.PropTypes.bool,
    downloadInBackground: React.PropTypes.bool
};

console.log(Platform);

CacheableImage.defaultProps = {
    style: { backgroundColor: 'transparent' },
    activityIndicatorProps: {
        style: { backgroundColor: 'transparent', flex: 1 }
    },
    useQueryParamsInCacheKey: false, // bc
    checkNetwork: true,
    networkAvailable: false,
    downloadInBackground: (Platform.OS === 'ios') ? false : true
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name           twRotateImage
// @namespace      http://d.hatena.ne.jp/furyu-tei
// @include        http://twitter.com/*
// @include        https://twitter.com/*
// @description    Rotate image on Twitter. (version 0.1.0.0)
// ==/UserScript==
/*
The MIT License (MIT)

Copyright (c) 2014 furyu <furyutei@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function(w, d){

var main = function(w, d){

    //{ user parameters

    var KEYCODE_ROTATE_CLOCKWISE = 114; // 'r': 時計回りキー
    var KEYCODE_ROTATE_ANTICLOCKWISE = 82; // 'R': 反時計回りキー

    //}

    //{ global variables

    var NAME_SCRIPT = 'twRotateImage';
    var $=w.$;
    if (w[NAME_SCRIPT+'_touched']) return;
    if (!$) {
        var main = arguments.callee; setTimeout(function(){main(w,d);}, 100); return;
    }
    w[NAME_SCRIPT+'_touched'] = true;

    var CLASS_TOUCHED = NAME_SCRIPT+'_touched';
    var CLASS_ROTATION_TARGET = NAME_SCRIPT+'_rotation_target';
    var ATTRNAME_DEGREE = NAME_SCRIPT+'_deg';

    var clockwise_pattern = {0:90, 90:180, 180:270, 270:0}, anticlockwise_pattern = {};
    for (var key in clockwise_pattern) anticlockwise_pattern[clockwise_pattern[key]] = key;
    var jq_tgt_img = null;

    //}

    //{ functions

    var check_parent_container = function(jq_parent){
        return (jq_parent.hasClass('Gallery-media') || (jq_parent.get(0).nodeName == 'DIV' && !jq_parent.attr('class')));
    };  //  end of check_parent_container()

    var rotate_image = function(jq_img, anticlockwise){
        if (!jq_img) jq_img = jq_tgt_img;
        if (!jq_img || !jq_img.hasClass(CLASS_ROTATION_TARGET)) return;

        var pattern = (anticlockwise) ? anticlockwise_pattern : clockwise_pattern, deg = pattern[jq_img.attr(ATTRNAME_DEGREE)];
        if (!deg) deg = 0;

        var width = jq_img.attr('data-width'), height = jq_img.attr('data-height'), swap = (deg/90) % 2;

        for (;;) {
            if (!jq_img.parent().hasClass('Gallery-media')) break;
            var jq_gallery = jq_img.parents('div.Gallery-content:first');
            if (jq_gallery.size() < 1) break;

            jq_img.width(swap ? height : width).height(swap ? width : height);

            using("app/utils/image/image_resizer", function(imageResizer) {
                imageResizer.resizeMedia(jq_img, jq_gallery, !1, '.GalleryTweet', !0);
                width = jq_img.width();
                height = jq_img.height();
                jq_img.width(swap ? height : width).height(swap ? width : height);
                var margin_top = jq_img.css('margin-top'), margin_bottom = jq_img.css('margin-bottom');
                jq_img.css('margin-top','0').css('margin-bottom','0');  // TODO: margin の適切な設定方法がわからない
            });
            break;
        }
        jq_img.parent().each(function(){
            var jq_parent = $(this);
            if (!check_parent_container(jq_parent)) return;
            var width = jq_img.width(), height = jq_img.height(), tmp_value = width;
            if (swap) {
                width = height;
                height = tmp_value;
            }
            var text_align = 'center', origin_x = width / 2, origin_y = height / 2;
            if (swap) {
                if (deg == 90) {
                    origin_y = origin_x;
                }
                else {
                    origin_x = origin_y;
                }
                if (height < width) text_align = 'left';
            }
            //jq_parent.width(width).height(height).css('text-align', text_align);
            //jq_parent.height(height).css('text-align', text_align);
            jq_parent.height(height).css('text-align', 'left'); // TODO: 'center' のままにしたいが、親要素からずれてしまうことがある
            jq_img.css('transform-origin', origin_x+'px '+origin_y+'px');
        });
        jq_img.css('transform', 'rotate('+deg+'deg)');
        jq_img.attr(ATTRNAME_DEGREE, deg);
    };  //  end of rotate_image()

    var set_event_img = function(jq_img){
        if (typeof jq_img.attr(ATTRNAME_DEGREE) !== 'undefined') return;
        jq_img.attr(ATTRNAME_DEGREE, 0);
        jq_img.mouseenter(function(){
            jq_tgt_img = jq_img;
            jq_img.addClass(CLASS_ROTATION_TARGET);
        });
        jq_img.mouseleave(function(){
            jq_img.removeClass(CLASS_ROTATION_TARGET);
        });
        jq_img.parent().each(function(){
            var jq_parent = $(this);
            if (check_parent_container(jq_parent)) {
                jq_parent.css('width', '').css('height', '').css('text-align','');
            }
        });
    };  //  end of set_event_img()

    //}

    //{ main procedure

    $('img').each(function(){set_event_img($(this))});

    $(d).bind('DOMNodeInserted', function(e){
        var jq_target = $(e.target);
        ((jq_target.get(0).nodeName==='IMG')?jq_target:jq_target.find('img')).each(function(){set_event_img($(this))});
    });

    $(d).keypress(function(e){
        var keycode = e.which;
        $('div.Gallery-content').each(function(){
            var jq_gallery = $(this);
            if (jq_gallery.is(':visible')) {
                var jq_img = jq_gallery.find('img.media-image:first');
                jq_tgt_img = jq_img;
                jq_img.addClass(CLASS_ROTATION_TARGET);
            }
            else {
                jq_gallery.find('img.media-image').removeClass(CLASS_ROTATION_TARGET);
            }
        });
        if (!jq_tgt_img || !jq_tgt_img.hasClass(CLASS_ROTATION_TARGET)) return;
        switch (keycode) {
            case    KEYCODE_ROTATE_CLOCKWISE:
                rotate_image(jq_tgt_img);
                return false;
            case    KEYCODE_ROTATE_ANTICLOCKWISE:
                rotate_image(jq_tgt_img, true);
                return false;
        }
    });

    //}

}   //  end of main()

if (typeof w.$ == 'function') {
    main(w, d);
}
else {
    var container = d.documentElement;
    var script = d.createElement('script');
    script.textContent = '('+main.toString()+')(window, document);';
    container.appendChild(script);
}

})(window, document);

// ■ end of file
<commit_msg>0.1.0.1: メディア用IFRAMEでの実行抑制<commit_after>// ==UserScript==
// @name            twRotateImage
// @namespace       http://d.hatena.ne.jp/furyu-tei
// @author          furyu
// @version         0.1.0.1
// @include         http://twitter.com/*
// @include         https://twitter.com/*
// @exclude         https://twitter.com/i/*
// @description     Rotate image on Twitter.
// ==/UserScript==
/*
The MIT License (MIT)

Copyright (c) 2014 furyu <furyutei@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function(w, d){

var main = function(w, d){

    //{ user parameters

    var KEYCODE_ROTATE_CLOCKWISE = 114; // 'r': 時計回りキー
    var KEYCODE_ROTATE_ANTICLOCKWISE = 82; // 'R': 反時計回りキー

    //}

    //{ global variables

    var NAME_SCRIPT = 'twRotateImage';
    var $=w.$;
    if (w[NAME_SCRIPT+'_touched']) return;
    if (!$) {
        var main = arguments.callee; setTimeout(function(){main(w,d);}, 100); return;
    }
    w[NAME_SCRIPT+'_touched'] = true;

    var CLASS_TOUCHED = NAME_SCRIPT+'_touched';
    var CLASS_ROTATION_TARGET = NAME_SCRIPT+'_rotation_target';
    var ATTRNAME_DEGREE = NAME_SCRIPT+'_deg';

    var clockwise_pattern = {0:90, 90:180, 180:270, 270:0}, anticlockwise_pattern = {};
    for (var key in clockwise_pattern) anticlockwise_pattern[clockwise_pattern[key]] = key;
    var jq_tgt_img = null;

    //}

    //{ functions

    var check_parent_container = function(jq_parent){
        return (jq_parent.hasClass('Gallery-media') || (jq_parent.get(0).nodeName == 'DIV' && !jq_parent.attr('class')));
    };  //  end of check_parent_container()

    var rotate_image = function(jq_img, anticlockwise){
        if (!jq_img) jq_img = jq_tgt_img;
        if (!jq_img || !jq_img.hasClass(CLASS_ROTATION_TARGET)) return;

        var pattern = (anticlockwise) ? anticlockwise_pattern : clockwise_pattern, deg = pattern[jq_img.attr(ATTRNAME_DEGREE)];
        if (!deg) deg = 0;

        var width = jq_img.attr('data-width'), height = jq_img.attr('data-height'), swap = (deg/90) % 2;

        for (;;) {
            if (!jq_img.parent().hasClass('Gallery-media')) break;
            var jq_gallery = jq_img.parents('div.Gallery-content:first');
            if (jq_gallery.size() < 1) break;

            jq_img.width(swap ? height : width).height(swap ? width : height);

            using("app/utils/image/image_resizer", function(imageResizer) {
                imageResizer.resizeMedia(jq_img, jq_gallery, !1, '.GalleryTweet', !0);
                width = jq_img.width();
                height = jq_img.height();
                jq_img.width(swap ? height : width).height(swap ? width : height);
                var margin_top = jq_img.css('margin-top'), margin_bottom = jq_img.css('margin-bottom');
                jq_img.css('margin-top','0').css('margin-bottom','0');  // TODO: margin の適切な設定方法がわからない
            });
            break;
        }
        jq_img.parent().each(function(){
            var jq_parent = $(this);
            if (!check_parent_container(jq_parent)) return;
            var width = jq_img.width(), height = jq_img.height(), tmp_value = width;
            if (swap) {
                width = height;
                height = tmp_value;
            }
            var text_align = 'center', origin_x = width / 2, origin_y = height / 2;
            if (swap) {
                if (deg == 90) {
                    origin_y = origin_x;
                }
                else {
                    origin_x = origin_y;
                }
                if (height < width) text_align = 'left';
            }
            //jq_parent.width(width).height(height).css('text-align', text_align);
            //jq_parent.height(height).css('text-align', text_align);
            jq_parent.height(height).css('text-align', 'left'); // TODO: 'center' のままにしたいが、親要素からずれてしまうことがある
            jq_img.css('transform-origin', origin_x+'px '+origin_y+'px');
        });
        jq_img.css('transform', 'rotate('+deg+'deg)');
        jq_img.attr(ATTRNAME_DEGREE, deg);
    };  //  end of rotate_image()

    var set_event_img = function(jq_img){
        if (typeof jq_img.attr(ATTRNAME_DEGREE) !== 'undefined') return;
        jq_img.attr(ATTRNAME_DEGREE, 0);
        jq_img.mouseenter(function(){
            jq_tgt_img = jq_img;
            jq_img.addClass(CLASS_ROTATION_TARGET);
        });
        jq_img.mouseleave(function(){
            jq_img.removeClass(CLASS_ROTATION_TARGET);
        });
        jq_img.parent().each(function(){
            var jq_parent = $(this);
            if (check_parent_container(jq_parent)) {
                jq_parent.css('width', '').css('height', '').css('text-align','');
            }
        });
    };  //  end of set_event_img()

    //}

    //{ main procedure

    $('img').each(function(){set_event_img($(this))});

    $(d).bind('DOMNodeInserted', function(e){
        var jq_target = $(e.target);
        ((jq_target.get(0).nodeName==='IMG')?jq_target:jq_target.find('img')).each(function(){set_event_img($(this))});
    });

    $(d).keypress(function(e){
        var keycode = e.which;
        $('div.Gallery-content').each(function(){
            var jq_gallery = $(this);
            if (jq_gallery.is(':visible')) {
                var jq_img = jq_gallery.find('img.media-image:first');
                jq_tgt_img = jq_img;
                jq_img.addClass(CLASS_ROTATION_TARGET);
            }
            else {
                jq_gallery.find('img.media-image').removeClass(CLASS_ROTATION_TARGET);
            }
        });
        if (!jq_tgt_img || !jq_tgt_img.hasClass(CLASS_ROTATION_TARGET)) return;
        switch (keycode) {
            case    KEYCODE_ROTATE_CLOCKWISE:
                rotate_image(jq_tgt_img);
                return false;
            case    KEYCODE_ROTATE_ANTICLOCKWISE:
                rotate_image(jq_tgt_img, true);
                return false;
        }
    });

    //}

}   //  end of main()

if (typeof w.$ == 'function') {
    main(w, d);
}
else {
    var container = d.documentElement;
    var script = d.createElement('script');
    script.textContent = '('+main.toString()+')(window, document);';
    container.appendChild(script);
}

})(window, document);

// ■ end of file
<|endoftext|>


<commit_before>module.exports = function(data) {
  var rx = /^---[\s]+([\s\S]*?)[\s]+---[\s]*([\s\S]*?)[\s]*$/;
  var rxFm = /(.*?)\s*:\s*(?:(?:\[\s*(.*?)(?=\s*\]))|(.*))/g;
  var rxArr = /\s?,\s?/g;
  var res = rx.exec(data) || [null, null, data];
  var tmp;
  var fm = {};
  fm.attributes = {};
  fm.body = res[2];

  while (tmp = rxFm.exec(res[1]), tmp) {
    fm.attributes[tmp[1]] = tmp[2] ? tmp[2].split(rxArr) : tmp[3];
    elapsedTime('rx');
  }

  return fm;
};
<commit_msg>removed unused line<commit_after>module.exports = function(data) {
  var rx = /^---[\s]+([\s\S]*?)[\s]+---[\s]*([\s\S]*?)[\s]*$/;
  var rxFm = /(.*?)\s*:\s*(?:(?:\[\s*(.*?)(?=\s*\]))|(.*))/g;
  var rxArr = /\s?,\s?/g;
  var res = rx.exec(data) || [null, null, data];
  var tmp;
  var fm = {};
  fm.attributes = {};
  fm.body = res[2];

  while (tmp = rxFm.exec(res[1]), tmp) {
    fm.attributes[tmp[1]] = tmp[2] ? tmp[2].split(rxArr) : tmp[3];
  }

  return fm;
};
<|endoftext|>