<commit_before>/**
 *
 * scsynth - boots a supercollider synthesis server process
 *
 *  SuperCollider comes with an executable called scsynth
 *  which can be communicated with via udp OSC
 *
 *  The primary way to send messages in with sendMsg
 *  eg. server.sendMsg('/s_new', ['defName', 440])

 *  and the responses are emitted as 'OSC'
 *  eg. server.on('OSC', function(msg) {  ...  });
 *
 * methods:
 *   boot        - boot an scsynth process
 *   quit
 *   connect     - connect via udp OSC
 *   disconnect
 *   sendMsg     - send an OSC message
 *
 * emits:
 *    'out'   - stdout text from the server
 *    'error' - stderr text from the server or OSC error messages
 *    'exit'  - when server exits
 *    'close' - when server closes the UDP connection
 *    'OSC'   - OSC responses from the server
 */

import {EventEmitter} from 'events';
import {Observable, Subject} from 'Rx';
import {spawn} from 'child_process';
import * as _ from 'underscore';
import * as dgram from 'dgram';
import * as osc from 'osc-min';
import * as Q from 'q';
import Immutable from 'immutable';

import * as alloc from './internals/allocators';
import defaultOptions from './default-server-options';
import Logger from '../utils/Logger';
import resolveOptions from '../utils/resolveOptions';

const keys = {
  NODE_IDS: 'nodeAllocator',
  CONTROL_BUSSES: 'controlBusAllocator',
  AUDIO_BUSSES: 'audioBusAllocator',
  BUFFERS: 'bufferAllocator'
};

export class Server extends EventEmitter {

  /**
   * @param {Object} options - server command line options
   */
  constructor(options={}) {
    super();
    this.options = _.defaults(options, defaultOptions);
    this.process = null;
    this.isRunning = false;
    this.log = new Logger(this.options.debug, this.options.echo);

    // receive is subscribeable even if not yet connected to the server
    this.receive  = new Subject();
    this.receive.subscribe((msg) => {
      this.log.rcvosc(msg);
      // deprecated. use subscriptions on this.receive
      this.emit('OSC', msg);
    });

    this.resetState();
  }

  resetState() {
    var state = Immutable.Map();
    state = state.set(keys.NODE_IDS, this.options.initialNodeID - 1);

    var numAudioChannels = this.options.numPrivateAudioBusChannels +
      this.options.numInputBusChannels +
      this.options.numOutputBusChannels;
    var ab = alloc.initialBlockState(numAudioChannels);
    ab = alloc.reserveBlock(ab, 0, this.options.numInputBusChannels + this.options.numOutputBusChannels);
    state = state.set(keys.AUDIO_BUSSES, ab);

    var cb = alloc.initialBlockState(this.options.numControlBusChannels);
    state = state.set(keys.CONTROL_BUSSES, cb);

    var bb = alloc.initialBlockState(this.options.numBuffers);
    state = state.set(keys.BUFFERS, cb);

    this.state = state;
  }

  /**
   * Format command line args for scsynth
   *
   * not yet fully implemented
   *
   * @return {array} list of non-default args
   */
  args() {
    var o = [];
    o.push(this.options.protocol === 'udp' ? '-u' : '-t');
    o.push(this.options.serverPort);
    return o;
  }

  /**
   * boot
   *
   * start scsynth and establish a pipe connection
   * to receive stdout and stderr
   *
   * listen for system events and emit: exit out error
   */
  boot() {
    var
      self = this,
      execPath = this.options.scsynth,
      args = this.args(),
      d = Q.defer();

    this.log.dbug(execPath + ' ' + args.join(' '));
    this.process = spawn(execPath, args,
      {
        cwd: this.options.cwd
      });
    this.log.dbug('Spawned pid: ' + this.process.pid);

    this.process.on('error', (err) => {
      this.log.err('Server error ' + err);
      this.emit('exit', err);
      // this.isRunning = false;
    });
    this.process.on('close', (code, signal) => {
      this.log.dbug('Server closed ' + code);
      this.emit('exit', code);
      this.isRunning = false;
    });
    this.process.on('exit', (code, signal) => {
      this.log.dbug('Server exited ' + code);
      this.emit('exit', code);
      this.isRunning = false;
    });

    this.process.stdout.on('data', (data) => {
      this.log.stdout('' + data);
      this.emit('out', data);
      // should say the magical words:
      // "SuperCollider 3 server ready"
      // and resolve if not already
      this.isRunning = true;
    });
    this.process.stderr.on('data', (data) => {
      this.log.stderr('' + data);
      this.emit('stderr', data);
      this.isRunning = true;
    });

    setTimeout(() => {
      if (this.isRunning) {
        d.resolve();
      } else {
        d.reject();
      }
    }, 300);

    return d.promise;
  }

  /**
   * quit
   *
   * kill scsynth process
   */
  quit() {
    if (this.process) {
      this.process.kill('SIGTERM');
      this.process = null;
    }
  }

  /**
   * Establish connection to scsynth via OSC socket
   */
  connect() {
    var self = this;
    this.osc = dgram.createSocket('udp4');

    // pipe events to this.receive
    this._oscIn = Observable.fromEvent(this.osc, 'message', (msgbuf) => {
      return osc.fromBuffer(msgbuf);
    });
    this._oscIn.subscribe((e) => {
      this.receive.onNext(e);
    }, this.receive.onError, this.receive.onCompleted);

    this.osc.on('error', function(e) {
      self.log.err(e);
      self.emit('error', e);
    });
    this.osc.on('listening', function() {
      self.log.dbug('udp is listening');
    });
    this.osc.on('close', function(e) {
      self.log.dbug('udp close' + e);
      self.emit('close', e);
    });
  }

  disconnect() {
    if (this.osc) {
      this.osc.close();
      delete this.osc;
    }
    if (this._oscIn) {
      this._oscIn.dispose();
      delete this._oscIn;
    }
  }

  /**
   * @param {String} address - OSC command, referred to as address
   * @param {Array} args
   */
  sendMsg(address, args) {
    var buf = osc.toBuffer({
      address: address,
      args: args
    });
    this.log.sendosc(address + ' ' + args.join(' '));
    this.osc.send(buf, 0, buf.length, this.options.serverPort, this.options.host);
  }

  nextNodeID() {
    return this._mutateState(keys.NODE_IDS, alloc.increment);
  }

  // temporary raw allocator calls
  allocAudioBus(numChannels=1) {
    return this._allocBlock(keys.AUDIO_BUSSES, numChannels);
  }
  allocControlBus(numChannels=1) {
    return this._allocBlock(keys.CONTROL_BUSSES, numChannels);
  }
  /**
   * Allocate a buffer id.
   *
   * Note that numChannels is specified when creating the buffer.
   *
   * @param {int} numConsecutive - consecutively numbered buffers are needed by VOsc and VOsc3.
   * @returns {int}
   */
  allocBufferID(numConsecutive=1) {
    return this._allocBlock(keys.BUFFERS, numConsecutive);
  }

  // these require you to remember the channels and mess it up
  // if you free it wrong
  freeAudioBus(index, numChannels) {
    return this._freeBlock(keys.AUDIO_BUSSES, index, numChannels);
  }
  freeControlBus(index, numChannels) {
    return this._freeBlock(keys.CONTROL_BUSSES, index, numChannels);
  }
  freeBuffer(index, numChannels) {
    return this._freeBlock(keys.BUFFERS, index, numChannels);
  }

  // private
  /**
   * Fetch one part of the state,
   * mutate it with the callback,
   * save state and return the result.
   *
   * @returns {any} result
   */
  _mutateState(key, fn) {
    var result, state;
    [result, state] = fn(this.state.get(key));
    this.state = this.state.set(key, state);
    return result;
  }
  _mutateStateNoReturn(key, fn) {
    var state = fn(this.state.get(key));
    this.state = this.state.set(key, state);
  }
  _allocBlock(key, numChannels) {
    return this._mutateState(key,
      (state) => alloc.allocBlock(state, numChannels));
  }
  _freeBlock(key, index, numChannels) {
    return this._mutateStateNoReturn(key,
      (state) => alloc.freeBlock(state, index, numChannels));
  }
}

/**
 * boot a server with options
 * @returns {Promise}
 */
export function boot(options) {
  return resolveOptions(null, options).then(function(opts) {
    var s = new Server(opts);
    return s.boot().then(function() {
      s.connect();
      return s;
    });
  });
}
<commit_msg>refactor: move all events to public Rx subscribeable streams<commit_after>/**
 *
 * scsynth - boots a supercollider synthesis server process
 *
 *  SuperCollider comes with an executable called scsynth
 *  which can be communicated with via udp OSC
 *
 *  The primary way to send messages in with sendMsg
 *  eg. server.sendMsg('/s_new', ['defName', 440])

 *  and the responses are emitted as 'OSC'
 *  eg. server.on('OSC', function(msg) {  ...  });
 *
 * methods:
 *   boot        - boot an scsynth process
 *   quit
 *   connect     - connect via udp OSC
 *   disconnect
 *   sendMsg     - send an OSC message
 *
 * emits:
 *    'out'   - stdout text from the server
 *    'error' - stderr text from the server or OSC error messages
 *    'exit'  - when server exits
 *    'close' - when server closes the UDP connection
 *    'OSC'   - OSC responses from the server
 */

import {EventEmitter} from 'events';
import {Observable, Subject} from 'Rx';
import {spawn} from 'child_process';
import * as _ from 'underscore';
import * as dgram from 'dgram';
import * as osc from 'osc-min';
import * as Q from 'q';
import Immutable from 'immutable';

import * as alloc from './internals/allocators';
import defaultOptions from './default-server-options';
import Logger from '../utils/Logger';
import resolveOptions from '../utils/resolveOptions';

const keys = {
  NODE_IDS: 'nodeAllocator',
  CONTROL_BUSSES: 'controlBusAllocator',
  AUDIO_BUSSES: 'audioBusAllocator',
  BUFFERS: 'bufferAllocator'
};

class SendOSC extends EventEmitter {

  msg(m) {
    this.emit('msg', m);
  }

  bundle(b) {
    throw new Error('Not yet implemented');
    // not yet implemented
    // this will need a time
    // this.emit('bundle', b);
  }

  /**
   * Subscribe to monitor messages and bundles sent.
   *
   * Events are: {type: msg|bundle: payload: Array}
   *
   * @returns {Rx.Disposable} - `thing.dispose();` to unsubscribe
   */
  subscribe(onNext, onError, onComplete) {
    var msgs = Observable.fromEvent(this, 'msg', (msg) => {
      return {type: 'msg', payload: msg};
    });
    var bundles = Observable.fromEvent(this, 'bundle', (msg) => {
      return {type: 'bundle', payload: msg};
    });
    var combo = msgs.merge(bundles);
    return combo.subscribe(onNext, onError, onComplete);
  }
}

function _noop() {}

export class Server extends EventEmitter {

  /**
   * @param {Object} options - command line options for scsynth
   */
  constructor(options={}) {
    super();
    this.options = _.defaults(options, defaultOptions);
    this.process = null;
    this.isRunning = false;

    // subscribeable streams
    this.send = new SendOSC();
    this.receive  = new Subject();
    this.stdout = new Subject();
    this.processEvents = new Subject();

    this._initLogger();
    this._initEmitter();
    this._initSender();

    this._serverObservers = {};
    this.resetState();
  }

  _initLogger() {
    this.log = new Logger(this.options.debug, this.options.echo);
    this.send.subscribe((event) => {
      // will be a type:msg or type:bundle
      var out;
      if (event.type === 'msg') {
        out = event.payload.join(' ');
      } else {
        out = String(event.payload);
      }
      if (!this.osc) {
        out = '[NOT CONNECTED] ' + out;
      }
      this.log.sendosc(out);
    });
    this.receive.subscribe((o) => this.log.rcvosc(o));
    this.stdout.subscribe((o) => this.log.stdout(o), (o) => this.log.stderr(o));
    this.processEvents.subscribe((o) => this.log.dbug(o), (o) => this.log.err(o));
  }
  _initEmitter() {
    // emit signals are deprecated.
    // use server.{channel}.subscribe((event) => { })
    this.receive.subscribe((msg) => {
      this.emit('OSC', msg);
    });
    this.processEvents.subscribe(_noop, (err) => this.emit('exit', err));
    this.stdout.subscribe((out) => this.emit('out', out), (out) => this.emit('stderr', out));
  }
  _initSender() {
    this.send.on('msg', (msg) => {
      if (this.osc) {
        var buf = osc.toBuffer({
          address: msg[0],
          args: msg.slice(1)
        });
        this.osc.send(buf, 0, buf.length, this.options.serverPort, this.options.host);
      }
    });
  }

  resetState() {
    var state = Immutable.Map();
    state = state.set(keys.NODE_IDS, this.options.initialNodeID - 1);

    var numAudioChannels = this.options.numPrivateAudioBusChannels +
      this.options.numInputBusChannels +
      this.options.numOutputBusChannels;
    var ab = alloc.initialBlockState(numAudioChannels);
    ab = alloc.reserveBlock(ab, 0, this.options.numInputBusChannels + this.options.numOutputBusChannels);
    state = state.set(keys.AUDIO_BUSSES, ab);

    var cb = alloc.initialBlockState(this.options.numControlBusChannels);
    state = state.set(keys.CONTROL_BUSSES, cb);

    var bb = alloc.initialBlockState(this.options.numBuffers);
    state = state.set(keys.BUFFERS, cb);

    this.state = state;
  }

  /**
   * Format command line args for scsynth
   *
   * not yet fully implemented
   *
   * @return {array} list of non-default args
   */
  args() {
    var o = [];
    o.push(this.options.protocol === 'udp' ? '-u' : '-t');
    o.push(this.options.serverPort);
    return o;
  }

  /**
   * boot
   *
   * start scsynth and establish a pipe connection
   * to receive stdout and stderr
   *
   * listen for system events and emit: exit out error
   */
  boot() {
    var
      self = this,
      execPath = this.options.scsynth,
      args = this.args(),
      d = Q.defer();

    this.isRunning = false;

    this.processEvents.onNext(execPath + ' ' + args.join(' '));

    this.process = spawn(execPath, args, {
        cwd: this.options.cwd
      });
    this.processEvents.onNext('pid: ' + this.process.pid);

    this.process.on('error', (err) => {
      this.processEvents.onError(err);
      this.isRunning = false;
      // this.disconnect()
    });
    this.process.on('close', (code, signal) => {
      this.processEvents.onError('Server closed. Exit code: ' + code + ' signal: ' + signal);
      this.isRunning = false;
      // this.disconnect()
    });
    this.process.on('exit', (code, signal) => {
      this.processEvents.onError('Server exited. Exit code: ' + code + ' signal: ' + signal);
      this.isRunning = false;
      // this.disconnect()
    });

    this._serverObservers.stdout = Observable.fromEvent(this.process.stdout, 'data', (data) => String(data));
    this._serverObservers.stdout.subscribe((e) => this.stdout.onNext(e));

    this._serverObservers.stderr = Observable.fromEvent(this.process.stderr, 'data')
      .subscribe((out) => {
        console.log('stderr', out);
        // just pipe it into the stdout object's error stream
        this.stdout.onError(out);
      });

    // watch for ready message
    this._serverObservers.stdout.takeWhile((text) => (text.match(/SuperCollider 3 server ready/) !== null))
      .subscribe((next) => {},
        this.log.err,
        () => { // onComplete
          this.isRunning = true;
          d.resolve();
        });

    setTimeout(() => {
      if (!this.isRunning) {
        d.reject();
      }
    }, 3000);

    return d.promise;
  }

  /**
   * quit
   *
   * kill scsynth process
   */
  quit() {
    if (this.process) {
      this.process.kill('SIGTERM');
      this.process = null;
    }
  }

  /**
   * Establish connection to scsynth via OSC socket
   */
  connect() {
    var self = this;
    this.osc = dgram.createSocket('udp4');

    // pipe events to this.receive
    this._serverObservers.oscMessage = Observable.fromEvent(this.osc, 'message', (msgbuf) => osc.fromBuffer(msgbuf));
    this._serverObservers.oscMessage.subscribe((e) => this.receive.onNext(e));

    this._serverObservers.oscError = Observable.fromEvent(this.osc, 'error');
    this._serverObservers.oscError.subscribe((e) => this.receive.onError(e));

    this.osc.on('listening', () => {
      this.processEvents.onNext('udp is listening');
    });
    this.osc.on('close', (e) => {
      this.processEvents.onNext('udp closed: ' + e);
      // destroy and unsub
    });
  }

  disconnect() {
    if (this.osc) {
      this.osc.close();
      delete this.osc;
    }
    this._serverObservers.forEach((obs, k) => {
      obs.dispose();
    });
    this._serverObservers = {};
  }

  /**
   * Send OSC message to server
   *
   * @deprecated - use: `server.send.msg([address, arg1, arg2])``
   * @param {String} address - OSC command, referred to as address
   * @param {Array} args
   */
  sendMsg(address, args) {
    this.send.msg([address].concat(args));
  }

  nextNodeID() {
    return this._mutateState(keys.NODE_IDS, alloc.increment);
  }

  // temporary raw allocator calls
  allocAudioBus(numChannels=1) {
    return this._allocBlock(keys.AUDIO_BUSSES, numChannels);
  }
  allocControlBus(numChannels=1) {
    return this._allocBlock(keys.CONTROL_BUSSES, numChannels);
  }
  /**
   * Allocate a buffer id.
   *
   * Note that numChannels is specified when creating the buffer.
   *
   * @param {int} numConsecutive - consecutively numbered buffers are needed by VOsc and VOsc3.
   * @returns {int}
   */
  allocBufferID(numConsecutive=1) {
    return this._allocBlock(keys.BUFFERS, numConsecutive);
  }

  // these require you to remember the channels and mess it up
  // if you free it wrong
  freeAudioBus(index, numChannels) {
    return this._freeBlock(keys.AUDIO_BUSSES, index, numChannels);
  }
  freeControlBus(index, numChannels) {
    return this._freeBlock(keys.CONTROL_BUSSES, index, numChannels);
  }
  freeBuffer(index, numChannels) {
    return this._freeBlock(keys.BUFFERS, index, numChannels);
  }

  // private
  /**
   * Fetch one part of the state,
   * mutate it with the callback,
   * save state and return the result.
   *
   * @returns {any} result
   */
  _mutateState(key, fn) {
    var result, state;
    [result, state] = fn(this.state.get(key));
    this.state = this.state.set(key, state);
    return result;
  }
  _mutateStateNoReturn(key, fn) {
    var state = fn(this.state.get(key));
    this.state = this.state.set(key, state);
  }
  _allocBlock(key, numChannels) {
    return this._mutateState(key,
      (state) => alloc.allocBlock(state, numChannels));
  }
  _freeBlock(key, index, numChannels) {
    return this._mutateStateNoReturn(key,
      (state) => alloc.freeBlock(state, index, numChannels));
  }
}

/**
 * boot a server with options
 * @returns {Promise}
 */
export function boot(options) {
  return resolveOptions(null, options).then(function(opts) {
    var s = new Server(opts);
    return s.boot().then(function() {
      s.connect();
      return s;
    });
  });
}
<|endoftext|>


<commit_before>import GitHub from 'github-api';
import trim from 'lodash/trim';
import isEmpty from 'lodash/isEmpty';
import promiseRetry from 'promise-retry';
const anonymousGithub = new GitHub({});

export function EmptyGistError(message) {
  this.name = 'EmptyGistError';
  this.message = message;
  this.stack = new Error().stack;
}
EmptyGistError.prototype = Object.create(Error.prototype);

function createGistFromProject(project) {
  const files = {};
  if (trim(project.sources.html)) {
    files['index.html'] = {
      content: project.sources.html,
      language: 'HTML',
    };
  }
  if (trim(project.sources.css)) {
    files['styles.css'] = {
      content: project.sources.css,
      language: 'CSS',
    };
  }
  if (trim(project.sources.javascript)) {
    files['script.js'] = {
      content: project.sources.javascript,
      language: 'JavaScript',
    };
  }
  if (project.enabledLibraries.length) {
    files['popcode.json'] = {
      content: createPopcodeJson(project),
      language: 'JSON',
    };
  }

  return {
    description: 'Exported from Popcode.',
    'public': true,
    files,
  };
}

function clientForUser(user) {
  if (user.authenticated && user.provider === 'github') {
    return new GitHub({
      token: user.info.accessToken,
      auth: 'oauth',
    });
  }

  return anonymousGithub;
}

function canUpdateGist(user) {
  return user.authenticated && user.provider === 'github';
}

function updateGistWithImportUrl(github, gistData) {
  const gist = github.getGist(gistData.id);
  const uri = document.createElement('a');
  uri.setAttribute('href', '/');
  uri.search = `gist=${gistData.id}`;

  return gist.update({
    description: `${gistData.description} Click to import: ${uri.href}`,
  }).then((response) => response.data);
}

function createPopcodeJson(project) {
  const json = {
    enabledLibraries: project.enabledLibraries,
  };
  return JSON.stringify(json);
}

const Gists = {
  createFromProject(project, user) {
    const github = clientForUser(user);

    const gist = createGistFromProject(project);
    if (isEmpty(gist.files)) {
      return Promise.reject(new EmptyGistError());
    }

    return github.getGist().create(gist).
      then((response) => {
        const gistData = response.data;
        if (canUpdateGist(user)) {
          return updateGistWithImportUrl(github, gistData);
        }
        return gistData;
      }).then((gistData) => promiseRetry(
        (retry) => this.loadFromId(gistData.id, user).catch(retry),
        {
          retries: 5,
          factor: 2,
          minTimeout: 1000,
          maxTimeout: 10000,
        }
      ));
  },

  loadFromId(gistId, user) {
    const github = clientForUser(user);
    const gist = github.getGist(gistId);
    return gist.read().then((response) => response.data);
  },
};

export default Gists;
<commit_msg>Retry loading gist if request fails<commit_after>import GitHub from 'github-api';
import trim from 'lodash/trim';
import isEmpty from 'lodash/isEmpty';
import promiseRetry from 'promise-retry';
const anonymousGithub = new GitHub({});

export function EmptyGistError(message) {
  this.name = 'EmptyGistError';
  this.message = message;
  this.stack = new Error().stack;
}
EmptyGistError.prototype = Object.create(Error.prototype);

function createGistFromProject(project) {
  const files = {};
  if (trim(project.sources.html)) {
    files['index.html'] = {
      content: project.sources.html,
      language: 'HTML',
    };
  }
  if (trim(project.sources.css)) {
    files['styles.css'] = {
      content: project.sources.css,
      language: 'CSS',
    };
  }
  if (trim(project.sources.javascript)) {
    files['script.js'] = {
      content: project.sources.javascript,
      language: 'JavaScript',
    };
  }
  if (project.enabledLibraries.length) {
    files['popcode.json'] = {
      content: createPopcodeJson(project),
      language: 'JSON',
    };
  }

  return {
    description: 'Exported from Popcode.',
    'public': true,
    files,
  };
}

function clientForUser(user) {
  if (user.authenticated && user.provider === 'github') {
    return new GitHub({
      token: user.info.accessToken,
      auth: 'oauth',
    });
  }

  return anonymousGithub;
}

function canUpdateGist(user) {
  return user.authenticated && user.provider === 'github';
}

function updateGistWithImportUrl(github, gistData) {
  const gist = github.getGist(gistData.id);
  const uri = document.createElement('a');
  uri.setAttribute('href', '/');
  uri.search = `gist=${gistData.id}`;

  return gist.update({
    description: `${gistData.description} Click to import: ${uri.href}`,
  }).then((response) => response.data);
}

function createPopcodeJson(project) {
  const json = {
    enabledLibraries: project.enabledLibraries,
  };
  return JSON.stringify(json);
}

const Gists = {
  createFromProject(project, user) {
    const github = clientForUser(user);

    const gist = createGistFromProject(project);
    if (isEmpty(gist.files)) {
      return Promise.reject(new EmptyGistError());
    }

    return github.getGist().create(gist).
      then((response) => {
        const gistData = response.data;
        if (canUpdateGist(user)) {
          return updateGistWithImportUrl(github, gistData);
        }
        return gistData;
      }).then((gistData) => promiseRetry(
        (retry) => this.loadFromId(gistData.id, user).catch(retry),
        {
          retries: 5,
          factor: 2,
          minTimeout: 1000,
          maxTimeout: 10000,
        }
      ));
  },

  loadFromId(gistId, user) {
    const github = clientForUser(user);
    const gist = github.getGist(gistId);
    return promiseRetry((retry) => gist.read().catch(retry), {retries: 3}).
      then((response) => response.data);
  },
};

export default Gists;
<|endoftext|>


<commit_before>/**
 * File operations service
 * @module arpen/services/filer
 */
const fs = require('fs-ext');
const path = require('path');
const rimraf = require('rimraf');

/**
 * Buffer updater callback
 * @callback BufferFileUpdater
 * @param {Buffer} buffer       Previous file contents (Buffer)
 * @return {Promise}            Returns promise resolving to new file contents (Buffer)
 */

/**
 * String updater callback
 * @callback StringFileUpdater
 * @param {string} contents     Previous file contents (string)
 * @return {Promise}            Returns promise resolving to new file contents (string)
 */

/**
 * Callback for processing a file
 * @callback ProcessFileCallback
 * @param {string} filename     Path of the file
 * @return {Promise}            Should return a Promise
 */

/**
 * File operations service
 */
class Filer {
    /**
     * Create the service
     */
    constructor() {
    }

    /**
     * Service name is 'filer'
     * @type {string}
     */
    static get provides() {
        return 'filer';
    }

    /**
     * Dependencies as constructor arguments
     * @type {string[]}
     */
    static get requires() {
        return [];
    }

    /**
     * Read file descriptor
     * @param {number} fd       File descriptor
     * @return {Promise}        Resolves to file contents as Buffer
     */
    read(fd) {
        return new Promise((resolve, reject) => {
                fs.fstat(fd, (err, stats) => {
                    if (err)
                        return reject(err);

                    if (stats.size === 0)
                        return resolve(Buffer.from(''));

                    let buffer = Buffer.allocUnsafe(stats.size);
                    fs.read(
                        fd,
                        buffer,
                        0,
                        buffer.length,
                        null,
                        (err, bytesRead, buffer) => {
                            if (err)
                                return reject(err);
                            if (bytesRead != stats.size)
                                return reject(new Error(`Only ${bytesRead} out of ${stats.size} has been read on fd ${fd}`));

                            resolve(buffer);
                        }
                    );
                });
            });
    }

    /**
     * Write to file descriptor
     * @param {number} fd           File descriptor
     * @param {Buffer} buffer       New contents of the file
     * @return {Promise}            Resolves to true on success
     */
    write(fd, buffer) {
        return new Promise((resolve, reject) => {
                fs.write(
                    fd,
                    buffer,
                    0,
                    buffer.length,
                    null,
                    err => {
                        if (err)
                            return reject(err);

                        resolve(true);
                    }
                );
            });
    }

    /**
     * Lock a file (shared) and read it returning as a Buffer. Maximum file size is Buffer.kMaxLength bytes.
     * @param {string} filename     File path and name
     * @return {Promise}            Resolves to Buffer of file contents
     */
    lockReadBuffer(filename) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'r');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'sh', err => {
                    if (err)
                        return reject(err);

                    this.read(fd)
                        .then(data => {
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                     return reject(err);

                                resolve(data);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Do .lockReadBuffer() and return it as UTF8 string
     * @param {string} filename             File path and name
     * @return {Promise}                    Resolves to file contents
     */
    lockRead(filename) {
        return this.lockReadBuffer(filename)
            .then(buffer => {
                return buffer.toString();
            });
    }

    /**
     * Lock a file (exclusively) and write to it
     * @param {string} filename             File path and name
     * @param {Buffer} buffer               New file contents
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockWriteBuffer(filename, buffer, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'w');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'ex', err => {
                    if (err)
                        return reject(err);

                    this.write(fd, buffer)
                        .then(() => {
                            try {
                                if (mode !== null)
                                    fs.chmodSync(filename, mode);
                            } catch (error) {
                                // do nothing
                            }
                            try {
                                if (uid !== null && gid !== null)
                                    fs.chownSync(filename, uid, gid);
                            } catch (error) {
                                // do nothing
                            }
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                    return reject(err);

                                resolve(true);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Convert string to a Buffer and do a .lockWriteBuffer()
     * @param {string} filename             File path and name
     * @param {string} contents             New file contents
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockWrite(filename, contents, { mode = null, uid = null, gid = null } = {}) {
        let buffer = Buffer.from(contents);
        return this.lockWriteBuffer(filename, buffer, { mode, uid, gid });
    }

    /**
     * Lock a file (exclusively) and update it using Buffer
     * @param {string} filename             File path and name
     * @param {BufferFileUpdater} cb        Buffer updater callback
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockUpdateBuffer(filename, cb, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'a+');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'ex', err => {
                    if (err)
                        return reject(err);

                    this.read(fd)
                        .then(buffer => {
                            let result = cb(buffer);
                            if (typeof result != 'object' || result === null || typeof result.then != 'function')
                                throw new Error(`The callback did not return a Promise`);
                            return result;
                        })
                        .then(newBuffer => {
                            fs.ftruncateSync(fd, 0);
                            return this.write(fd, newBuffer);
                        })
                        .then(() => {
                            try {
                                if (mode !== null)
                                    fs.chmodSync(filename, mode);
                            } catch (error) {
                                // do nothing
                            }
                            try {
                                if (uid !== null && gid !== null)
                                    fs.chownSync(filename, uid, gid);
                            } catch (error) {
                                // do nothing
                            }
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                    return reject(err);

                                resolve(true);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Lock a file (exclusively) and update it using string
     * @param {string} filename             File path and name
     * @param {StringFileUpdater} cb        String updater callback
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockUpdate(filename, cb, { mode = null, uid = null, gid = null } = {}) {
        let stringCb = buffer => {
            let result = cb(buffer.toString());
            if (typeof result != 'object' || result === null || typeof result.then != 'function')
                return Promise.reject(new Error(`The callback did not return a Promise`));

            return result
                .then(str => {
                    return Buffer.from(str);
                });
        };
        return this.lockUpdateBuffer(filename, stringCb, { mode, uid, gid });
    }

    /**
     * Create a directory (recursively)
     * @param {string} filename             Absolute path of the directory
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    createDirectory(filename, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                let parts = filename.split('/');
                parts.shift();

                let dirs = [];
                for (let i = 0; i < parts.length; i++) {
                    let dir = '';
                    for (let j = 0; j <= i; j++)
                        dir += '/' + parts[j];
                    dirs.push(dir);
                }

                dirs.reduce(
                        (prev, cur) => {
                            return prev.then(() => {
                                let stats;
                                try {
                                    stats = fs.statSync(cur);
                                } catch (err) {
                                    // do nothing
                                }

                                if (stats) {
                                    if (!stats.isDirectory())
                                        throw new Error(`Path exists and not a directory: ${cur}`);
                                } else {
                                    fs.mkdirSync(cur);
                                    try {
                                        if (mode !== null)
                                            fs.chmodSync(cur, mode);
                                    } catch (error) {
                                        // do nothing
                                    }
                                    try {
                                        if (uid !== null && gid !== null)
                                            fs.chownSync(cur, uid, gid);
                                    } catch (error) {
                                        // do nothing
                                    }
                                }
                            });
                        },
                        Promise.resolve()
                    )
                    .then(() => {
                        resolve(true);
                    })
                    .catch(err => {
                        reject(err);
                    });
            });
    }

    /**
     * Create a file (its base dir must exist)
     * @param {string} filename             Absolute path of the file
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    createFile(filename, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                try {
                    if (!fs.statSync(filename).isFile())
                        return reject(new Error(`Path exists and not a file: ${filename}`));

                    return resolve(true);
                } catch (err) {
                    // do nothing
                }

                try {
                    let fd = fs.openSync(filename, 'a');
                    fs.closeSync(fd);

                    try {
                        if (mode !== null)
                            fs.chmodSync(filename, mode);
                    } catch (error) {
                        // do nothing
                    }
                    try {
                        if (uid !== null && gid !== null)
                            fs.chownSync(filename, uid, gid);
                    } catch (error) {
                        // do nothing
                    }

                    resolve(true);
                } catch (err) {
                    reject(err);
                }
            });
    }

    /**
     * Remove a file or directory recursively
     * @param {string} filename     Path of a file or directory
     * @return {object}             Resolves to true on success
     */
    remove(filename) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                try {
                    fs.lstatSync(filename);
                } catch (err) {
                    return reject(err);
                }

                rimraf(filename, { disableGlob: true }, err => {
                    if (err)
                        return reject(err);

                    resolve(true);
                });
            });
    }

    /**
     * Execute a callback for the filename if it is a file. If it is a directory then execute it for every file in that
     * directory recursively.<br>
     * Execution is chained, if any of the callback invocations rejects then the entire process is rejected.
     * @param {string} filename             Path to the file or directory
     * @param {ProcessFileCallback} cb      The callback
     * @return {Promise}                    Resolves to true on success
     */
    process(filename, cb) {
        try {
            if (!fs.statSync(filename).isDirectory()) {
                let result = cb(filename);
                if (typeof result != 'object' || result === null || typeof result.then != 'function')
                    return Promise.reject(new Error(`The callback did not return a Promise for "${name}"`));
                return result;
            }
        } catch (err) {
            return Promise.resolve();
        }

        let names;
        try {
            names = fs.readdirSync(filename);
        } catch (err) {
            return Promise.resolve();
        }

        names.sort();
        return names.reduce((prev, cur) => {
            let name = path.join(filename, cur);
            try {
                if (fs.statSync(name).isDirectory()) {
                    return prev.then(() => { return this.process(name, cb); });
                }
            } catch (err) {
                return prev;
            }

            return prev.then(() => {
                let result = cb(name);
                if (typeof result != 'object' || result === null || typeof result.then != 'function')
                    throw new Error(`The callback did not return a Promise for "${name}"`);
                return result;
            });
        }, Promise.resolve());
    }
}

module.exports = Filer;<commit_msg>Do not write unnecessary lockUpdate<commit_after>/**
 * File operations service
 * @module arpen/services/filer
 */
const fs = require('fs-ext');
const path = require('path');
const rimraf = require('rimraf');

/**
 * Buffer updater callback
 * @callback BufferFileUpdater
 * @param {Buffer} buffer       Previous file contents (Buffer)
 * @return {Promise}            Returns promise resolving to new file contents (Buffer)
 */

/**
 * String updater callback
 * @callback StringFileUpdater
 * @param {string} contents     Previous file contents (string)
 * @return {Promise}            Returns promise resolving to new file contents (string)
 */

/**
 * Callback for processing a file
 * @callback ProcessFileCallback
 * @param {string} filename     Path of the file
 * @return {Promise}            Should return a Promise
 */

/**
 * File operations service
 */
class Filer {
    /**
     * Create the service
     */
    constructor() {
    }

    /**
     * Service name is 'filer'
     * @type {string}
     */
    static get provides() {
        return 'filer';
    }

    /**
     * Dependencies as constructor arguments
     * @type {string[]}
     */
    static get requires() {
        return [];
    }

    /**
     * Read file descriptor
     * @param {number} fd       File descriptor
     * @return {Promise}        Resolves to file contents as Buffer
     */
    read(fd) {
        return new Promise((resolve, reject) => {
                fs.fstat(fd, (err, stats) => {
                    if (err)
                        return reject(err);

                    if (stats.size === 0)
                        return resolve(Buffer.from(''));

                    let buffer = Buffer.allocUnsafe(stats.size);
                    fs.read(
                        fd,
                        buffer,
                        0,
                        buffer.length,
                        null,
                        (err, bytesRead, buffer) => {
                            if (err)
                                return reject(err);
                            if (bytesRead != stats.size)
                                return reject(new Error(`Only ${bytesRead} out of ${stats.size} has been read on fd ${fd}`));

                            resolve(buffer);
                        }
                    );
                });
            });
    }

    /**
     * Write to file descriptor
     * @param {number} fd           File descriptor
     * @param {Buffer} buffer       New contents of the file
     * @return {Promise}            Resolves to true on success
     */
    write(fd, buffer) {
        return new Promise((resolve, reject) => {
                fs.write(
                    fd,
                    buffer,
                    0,
                    buffer.length,
                    null,
                    err => {
                        if (err)
                            return reject(err);

                        resolve(true);
                    }
                );
            });
    }

    /**
     * Lock a file (shared) and read it returning as a Buffer. Maximum file size is Buffer.kMaxLength bytes.
     * @param {string} filename     File path and name
     * @return {Promise}            Resolves to Buffer of file contents
     */
    lockReadBuffer(filename) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'r');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'sh', err => {
                    if (err)
                        return reject(err);

                    this.read(fd)
                        .then(data => {
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                     return reject(err);

                                resolve(data);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Do .lockReadBuffer() and return it as UTF8 string
     * @param {string} filename             File path and name
     * @return {Promise}                    Resolves to file contents
     */
    lockRead(filename) {
        return this.lockReadBuffer(filename)
            .then(buffer => {
                return buffer.toString();
            });
    }

    /**
     * Lock a file (exclusively) and write to it
     * @param {string} filename             File path and name
     * @param {Buffer} buffer               New file contents
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockWriteBuffer(filename, buffer, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'w');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'ex', err => {
                    if (err)
                        return reject(err);

                    this.write(fd, buffer)
                        .then(() => {
                            try {
                                if (mode !== null)
                                    fs.chmodSync(filename, mode);
                            } catch (error) {
                                // do nothing
                            }
                            try {
                                if (uid !== null && gid !== null)
                                    fs.chownSync(filename, uid, gid);
                            } catch (error) {
                                // do nothing
                            }
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                    return reject(err);

                                resolve(true);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Convert string to a Buffer and do a .lockWriteBuffer()
     * @param {string} filename             File path and name
     * @param {string} contents             New file contents
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockWrite(filename, contents, { mode = null, uid = null, gid = null } = {}) {
        let buffer = Buffer.from(contents);
        return this.lockWriteBuffer(filename, buffer, { mode, uid, gid });
    }

    /**
     * Lock a file (exclusively) and update it using Buffer
     * @param {string} filename             File path and name
     * @param {BufferFileUpdater} cb        Buffer updater callback
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockUpdateBuffer(filename, cb, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                let fd;
                try {
                    fd = fs.openSync(filename, 'a+');
                } catch (err) {
                    return reject(err);
                }

                fs.flock(fd, 'ex', err => {
                    if (err)
                        return reject(err);

                    let oldBuffer;
                    this.read(fd)
                        .then(buffer => {
                            oldBuffer = buffer;
                            let result = cb(buffer);
                            if (typeof result != 'object' || result === null || typeof result.then != 'function')
                                throw new Error(`The callback did not return a Promise`);
                            return result;
                        })
                        .then(newBuffer => {
                            if (oldBuffer.equals(newBuffer))
                                return;

                            fs.ftruncateSync(fd, 0);
                            return this.write(fd, newBuffer);
                        })
                        .then(() => {
                            try {
                                if (mode !== null)
                                    fs.chmodSync(filename, mode);
                            } catch (error) {
                                // do nothing
                            }
                            try {
                                if (uid !== null && gid !== null)
                                    fs.chownSync(filename, uid, gid);
                            } catch (error) {
                                // do nothing
                            }
                            fs.flock(fd, 'un', err => {
                                if (fd) {
                                    fs.closeSync(fd);
                                    fd = null;
                                }

                                if (err)
                                    return reject(err);

                                resolve(true);
                            });
                        })
                        .catch(err => {
                            if (fd) {
                                fs.closeSync(fd);
                                fd = null;
                            }

                            reject(err);
                        });
                });
            });
    }

    /**
     * Lock a file (exclusively) and update it using string
     * @param {string} filename             File path and name
     * @param {StringFileUpdater} cb        String updater callback
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    lockUpdate(filename, cb, { mode = null, uid = null, gid = null } = {}) {
        let stringCb = buffer => {
            let result = cb(buffer.toString());
            if (typeof result != 'object' || result === null || typeof result.then != 'function')
                return Promise.reject(new Error(`The callback did not return a Promise`));

            return result
                .then(str => {
                    return Buffer.from(str);
                });
        };
        return this.lockUpdateBuffer(filename, stringCb, { mode, uid, gid });
    }

    /**
     * Create a directory (recursively)
     * @param {string} filename             Absolute path of the directory
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    createDirectory(filename, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                let parts = filename.split('/');
                parts.shift();

                let dirs = [];
                for (let i = 0; i < parts.length; i++) {
                    let dir = '';
                    for (let j = 0; j <= i; j++)
                        dir += '/' + parts[j];
                    dirs.push(dir);
                }

                dirs.reduce(
                        (prev, cur) => {
                            return prev.then(() => {
                                let stats;
                                try {
                                    stats = fs.statSync(cur);
                                } catch (err) {
                                    // do nothing
                                }

                                if (stats) {
                                    if (!stats.isDirectory())
                                        throw new Error(`Path exists and not a directory: ${cur}`);
                                } else {
                                    fs.mkdirSync(cur);
                                    try {
                                        if (mode !== null)
                                            fs.chmodSync(cur, mode);
                                    } catch (error) {
                                        // do nothing
                                    }
                                    try {
                                        if (uid !== null && gid !== null)
                                            fs.chownSync(cur, uid, gid);
                                    } catch (error) {
                                        // do nothing
                                    }
                                }
                            });
                        },
                        Promise.resolve()
                    )
                    .then(() => {
                        resolve(true);
                    })
                    .catch(err => {
                        reject(err);
                    });
            });
    }

    /**
     * Create a file (its base dir must exist)
     * @param {string} filename             Absolute path of the file
     * @param {object} [params]             File parameters (not changed if omitted)
     * @param {number} [params.mode=null]   Mode
     * @param {number} [params.uid=null]    UID
     * @param {number} [params.gid=null]    GID
     * @return {Promise}                    Resolves to true on success
     */
    createFile(filename, { mode = null, uid = null, gid = null } = {}) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                try {
                    if (!fs.statSync(filename).isFile())
                        return reject(new Error(`Path exists and not a file: ${filename}`));

                    return resolve(true);
                } catch (err) {
                    // do nothing
                }

                try {
                    let fd = fs.openSync(filename, 'a');
                    fs.closeSync(fd);

                    try {
                        if (mode !== null)
                            fs.chmodSync(filename, mode);
                    } catch (error) {
                        // do nothing
                    }
                    try {
                        if (uid !== null && gid !== null)
                            fs.chownSync(filename, uid, gid);
                    } catch (error) {
                        // do nothing
                    }

                    resolve(true);
                } catch (err) {
                    reject(err);
                }
            });
    }

    /**
     * Remove a file or directory recursively
     * @param {string} filename     Path of a file or directory
     * @return {object}             Resolves to true on success
     */
    remove(filename) {
        return new Promise((resolve, reject) => {
                if (filename.length < 2 || filename[0] != '/')
                    return reject(`Invalid path: ${filename}`);

                try {
                    fs.lstatSync(filename);
                } catch (err) {
                    return reject(err);
                }

                rimraf(filename, { disableGlob: true }, err => {
                    if (err)
                        return reject(err);

                    resolve(true);
                });
            });
    }

    /**
     * Execute a callback for the filename if it is a file. If it is a directory then execute it for every file in that
     * directory recursively.<br>
     * Execution is chained, if any of the callback invocations rejects then the entire process is rejected.
     * @param {string} filename             Path to the file or directory
     * @param {ProcessFileCallback} cb      The callback
     * @return {Promise}                    Resolves to true on success
     */
    process(filename, cb) {
        try {
            if (!fs.statSync(filename).isDirectory()) {
                let result = cb(filename);
                if (typeof result != 'object' || result === null || typeof result.then != 'function')
                    return Promise.reject(new Error(`The callback did not return a Promise for "${name}"`));
                return result;
            }
        } catch (err) {
            return Promise.resolve();
        }

        let names;
        try {
            names = fs.readdirSync(filename);
        } catch (err) {
            return Promise.resolve();
        }

        names.sort();
        return names.reduce((prev, cur) => {
            let name = path.join(filename, cur);
            try {
                if (fs.statSync(name).isDirectory()) {
                    return prev.then(() => { return this.process(name, cb); });
                }
            } catch (err) {
                return prev;
            }

            return prev.then(() => {
                let result = cb(name);
                if (typeof result != 'object' || result === null || typeof result.then != 'function')
                    throw new Error(`The callback did not return a Promise for "${name}"`);
                return result;
            });
        }, Promise.resolve());
    }
}

module.exports = Filer;<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier BIOT
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * an ellipse Object
     * @class
     * @extends Object
     * @memberOf me
     * @constructor
     * @param {Number} x the center x coordinate of the ellipse
     * @param {Number} y the center y coordinate of the ellipse
     * @param {Number} w width (diameter) of the ellipse
     * @param {Number} h height (diameter) of the ellipse
     */
    me.Ellipse = Object.extend(
    {
        /** @scope me.Ellipse.prototype */
        /** @ignore */
        init : function (x, y, w, h) {
            /**
             * the center coordinates of the ellipse
             * @public
             * @type {me.Vector2d}
             * @name pos
             * @memberOf me.Ellipse
             */
            this.pos = new me.Vector2d();

            /**
             * The bounding rectangle for this shape
             * @protected
             * @type {me.Rect}
             * @name bounds
             * @memberOf me.Ellipse
             */
            this.bounds = undefined;

            /**
             * Maximum radius of the ellipse
             * @public
             * @type {Number}
             * @name radius
             * @memberOf me.Ellipse
             */
            this.radius = NaN;

            /**
             * Pre-scaled radius vector for ellipse
             * @public
             * @type {me.Vector2d}
             * @name radiusV
             * @memberOf me.Ellipse
             */
            this.radiusV = new me.Vector2d();

            /**
             * Radius squared, for pythagorean theorom
             * @public
             * @type {me.Vector2d}
             * @name radiusSq
             * @memberOf me.Ellipse
             */
            this.radiusSq = new me.Vector2d();

            /**
             * x/y scaling ratio for ellipse
             * @public
             * @type {me.Vector2d}
             * @name ratio
             * @memberOf me.Ellipse
             */
            this.ratio = new me.Vector2d();

            // the shape type
            this.shapeType = "Ellipse";
            this.setShape(x, y, w, h);
        },

        /**
         * set new value to the Ellipse shape
         * @name setShape
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x position of the ellipse
         * @param {Number} y position of the ellipse
         * @param {Number} w width (diameter) of the ellipse
         * @param {Number} h height (diameter) of the ellipse
         */
        setShape : function (x, y, w, h) {
            var hW = w / 2;
            var hH = h / 2;
            this.pos.set(x, y);
            this.radius = Math.max(hW, hH);
            this.ratio.set(hW / this.radius, hH / this.radius);
            this.radiusV.set(this.radius, this.radius).scaleV(this.ratio);
            var r = this.radius * this.radius;
            this.radiusSq.set(r, r).scaleV(this.ratio);
            this.updateBounds();
            return this;
        },

        /**
         * Rotate this Ellipse (counter-clockwise) by the specified angle (in radians).
         * @name rotate
         * @memberOf me.Ellipse
         * @function
         * @param {Number} angle The angle to rotate (in radians)
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        rotate : function (/*angle*/) {
            // TODO
            return this;
        },

        /**
         * Scale this Ellipse by the specified scalar.
         * @name scale
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x
         * @param {Number} [y=x]
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        scale : function (/*x, y*/) {
            // TODO
            return this;
        },

        /**
         * Scale this Ellipse by the specified vector.
         * @name scale
         * @memberOf me.Ellipse
         * @function
         * @param {me.Vector2d} v
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        scaleV : function (/*v*/) {
            // TODO
            return this;
        },

        /**
         * translate the circle/ellipse by the specified offset
         * @name translate
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x x offset
         * @param {Number} y y offset
         * @return {me.Ellipse} this ellipse
         */
        translate : function (x, y) {
            this.pos.x += x;
            this.pos.y += y;
            this.bounds.translate(x, y);
            return this;
        },

        /**
         * translate the circle/ellipse by the specified vector
         * @name translateV
         * @memberOf me.Ellipse
         * @function
         * @param {me.Vector2d} v vector offset
         * @return {me.Rect} this ellipse
         */
        translateV : function (v) {
            this.pos.add(v);
            this.bounds.translateV(v);
            return this;
        },

        /**
         * check if this circle/ellipse contains the specified point
         * @name containsPointV
         * @memberOf me.Ellipse
         * @function
         * @param  {me.Vector2d} point
         * @return {boolean} true if contains
         */
        containsPointV: function (v) {
            return this.containsPoint(v.x, v.y);
        },

        /**
         * check if this circle/ellipse contains the specified point
         * @name containsPoint
         * @memberOf me.Ellipse
         * @function
         * @param  {Number} x x coordinate
         * @param  {Number} y y coordinate
         * @return {boolean} true if contains
         */
        containsPoint: function (x, y) {
            // Make position relative to object center point.
            x -= this.pos.x;
            y -= this.pos.y;
            // Pythagorean theorem.
            return (
                ((x * x) / this.radiusSq.x) +
                ((y * y) / this.radiusSq.y)
            ) <= 1.0;
        },

        /**
         * returns the bounding box for this shape, the smallest Rectangle object completely containing this shape.
         * @name getBounds
         * @memberOf me.Ellipse
         * @function
         * @return {me.Rect} this shape bounding box Rectangle object
         */
        getBounds : function () {
            return this.bounds;
        },

        /**
         * update the bounding box for this shape.
         * @name updateBounds
         * @memberOf me.Ellipse
         * @function
         * @return {me.Rect} this shape bounding box Rectangle object
         */
        updateBounds : function () {
            var rx = this.radiusV.x,
                ry = this.radiusV.y,
                x = this.pos.x - rx,
                y = this.pos.y - ry,
                w = rx * 2,
                h = ry * 2;

            if (!this.bounds) {
                this.bounds = new me.Rect(x, y, w, h);
            }  else {
                this.bounds.setShape(x, y, w, h);
            }
            return this.bounds;
        },

        /**
         * clone this Ellipse
         * @name clone
         * @memberOf me.Ellipse
         * @function
         * @return {me.Ellipse} new Ellipse
         */
        clone : function () {
            return new me.Ellipse(
                this.pos.x,
                this.pos.y,
                this.radiusV.x * 2,
                this.radiusV.y * 2
            );
        }
    });
})();
<commit_msg>added missing `scale` function to the ellipse shape<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier BIOT
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * an ellipse Object
     * @class
     * @extends Object
     * @memberOf me
     * @constructor
     * @param {Number} x the center x coordinate of the ellipse
     * @param {Number} y the center y coordinate of the ellipse
     * @param {Number} w width (diameter) of the ellipse
     * @param {Number} h height (diameter) of the ellipse
     */
    me.Ellipse = Object.extend(
    {
        /** @scope me.Ellipse.prototype */
        /** @ignore */
        init : function (x, y, w, h) {
            /**
             * the center coordinates of the ellipse
             * @public
             * @type {me.Vector2d}
             * @name pos
             * @memberOf me.Ellipse
             */
            this.pos = new me.Vector2d();

            /**
             * The bounding rectangle for this shape
             * @protected
             * @type {me.Rect}
             * @name bounds
             * @memberOf me.Ellipse
             */
            this.bounds = undefined;

            /**
             * Maximum radius of the ellipse
             * @public
             * @type {Number}
             * @name radius
             * @memberOf me.Ellipse
             */
            this.radius = NaN;

            /**
             * Pre-scaled radius vector for ellipse
             * @public
             * @type {me.Vector2d}
             * @name radiusV
             * @memberOf me.Ellipse
             */
            this.radiusV = new me.Vector2d();

            /**
             * Radius squared, for pythagorean theorom
             * @public
             * @type {me.Vector2d}
             * @name radiusSq
             * @memberOf me.Ellipse
             */
            this.radiusSq = new me.Vector2d();

            /**
             * x/y scaling ratio for ellipse
             * @public
             * @type {me.Vector2d}
             * @name ratio
             * @memberOf me.Ellipse
             */
            this.ratio = new me.Vector2d();

            // the shape type
            this.shapeType = "Ellipse";
            this.setShape(x, y, w, h);
        },

        /**
         * set new value to the Ellipse shape
         * @name setShape
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x position of the ellipse
         * @param {Number} y position of the ellipse
         * @param {Number} w width (diameter) of the ellipse
         * @param {Number} h height (diameter) of the ellipse
         */
        setShape : function (x, y, w, h) {
            var hW = w / 2;
            var hH = h / 2;
            this.pos.set(x, y);
            this.radius = Math.max(hW, hH);
            this.ratio.set(hW / this.radius, hH / this.radius);
            this.radiusV.set(this.radius, this.radius).scaleV(this.ratio);
            var r = this.radius * this.radius;
            this.radiusSq.set(r, r).scaleV(this.ratio);
            this.updateBounds();
            return this;
        },

        /**
         * Rotate this Ellipse (counter-clockwise) by the specified angle (in radians).
         * @name rotate
         * @memberOf me.Ellipse
         * @function
         * @param {Number} angle The angle to rotate (in radians)
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        rotate : function (/*angle*/) {
            // TODO
            return this;
        },

        /**
         * Scale this Ellipse by the specified scalar.
         * @name scale
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x
         * @param {Number} [y=x]
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        scale : function (x, y) {
            y = typeof (y) !== "undefined" ? y : x;
            return this.setShape(
                this.pos.x,
                this.pos.y,
                this.radiusV.x * 2 * x,
                this.radiusV.y * 2 * y
            );
        },

        /**
         * Scale this Ellipse by the specified vector.
         * @name scale
         * @memberOf me.Ellipse
         * @function
         * @param {me.Vector2d} v
         * @return {me.Ellipse} Reference to this object for method chaining
         */
        scaleV : function (v) {
            return this.scale(v.x, v.y);
        },

        /**
         * translate the circle/ellipse by the specified offset
         * @name translate
         * @memberOf me.Ellipse
         * @function
         * @param {Number} x x offset
         * @param {Number} y y offset
         * @return {me.Ellipse} this ellipse
         */
        translate : function (x, y) {
            this.pos.x += x;
            this.pos.y += y;
            this.bounds.translate(x, y);
            return this;
        },

        /**
         * translate the circle/ellipse by the specified vector
         * @name translateV
         * @memberOf me.Ellipse
         * @function
         * @param {me.Vector2d} v vector offset
         * @return {me.Rect} this ellipse
         */
        translateV : function (v) {
            this.pos.add(v);
            this.bounds.translateV(v);
            return this;
        },

        /**
         * check if this circle/ellipse contains the specified point
         * @name containsPointV
         * @memberOf me.Ellipse
         * @function
         * @param  {me.Vector2d} point
         * @return {boolean} true if contains
         */
        containsPointV: function (v) {
            return this.containsPoint(v.x, v.y);
        },

        /**
         * check if this circle/ellipse contains the specified point
         * @name containsPoint
         * @memberOf me.Ellipse
         * @function
         * @param  {Number} x x coordinate
         * @param  {Number} y y coordinate
         * @return {boolean} true if contains
         */
        containsPoint: function (x, y) {
            // Make position relative to object center point.
            x -= this.pos.x;
            y -= this.pos.y;
            // Pythagorean theorem.
            return (
                ((x * x) / this.radiusSq.x) +
                ((y * y) / this.radiusSq.y)
            ) <= 1.0;
        },

        /**
         * returns the bounding box for this shape, the smallest Rectangle object completely containing this shape.
         * @name getBounds
         * @memberOf me.Ellipse
         * @function
         * @return {me.Rect} this shape bounding box Rectangle object
         */
        getBounds : function () {
            return this.bounds;
        },

        /**
         * update the bounding box for this shape.
         * @name updateBounds
         * @memberOf me.Ellipse
         * @function
         * @return {me.Rect} this shape bounding box Rectangle object
         */
        updateBounds : function () {
            var rx = this.radiusV.x,
                ry = this.radiusV.y,
                x = this.pos.x - rx,
                y = this.pos.y - ry,
                w = rx * 2,
                h = ry * 2;

            if (!this.bounds) {
                this.bounds = new me.Rect(x, y, w, h);
            }  else {
                this.bounds.setShape(x, y, w, h);
            }
            return this.bounds;
        },

        /**
         * clone this Ellipse
         * @name clone
         * @memberOf me.Ellipse
         * @function
         * @return {me.Ellipse} new Ellipse
         */
        clone : function () {
            return new me.Ellipse(
                this.pos.x,
                this.pos.y,
                this.radiusV.x * 2,
                this.radiusV.y * 2
            );
        }
    });
})();
<|endoftext|>


<commit_before>var uuid = require('node-uuid'),
	url = require('url'),
	util = require('util'),
	amqp = require('amqp'),
	redis = require('redis'),
	assert = require('assert'),
	events = require('events');

var createMessage = require('./protocol').createMessage;

debug = process.env.NODE_CELERY_DEBUG === '1' ? util.debug : function() {};

function Configuration(options) {
	var self = this;

	for (var o in options) {
		if (options.hasOwnProperty(o)) {
			self[o.replace(/^CELERY_/, '')] = options[o];
		}
	}

	self.BROKER_URL = self.BROKER_URL || 'amqp://';
	self.DEFAULT_QUEUE = self.DEFAULT_QUEUE || 'celery';
	self.RESULT_EXCHANGE = self.RESULT_EXCHANGE || 'celeryresults';
	self.TASK_RESULT_EXPIRES = self.TASK_RESULT_EXPIRES || 86400000; // 1 day

	if (self.RESULT_BACKEND && self.RESULT_BACKEND.toLowerCase() === 'amqp') {
		self.backend_type = 'amqp';
	} else if (self.RESULT_BACKEND && url.parse(self.RESULT_BACKEND).protocol === 'redis:') {
		self.backend_type = 'redis';
	}
}

function Client(conf) {
	var self = this;

	self.conf = new Configuration(conf);

	self.ready = false;
	self.broker_connected = false;
	self.backend_connected = false;

	self.broker = amqp.createConnection({
		url: self.conf.BROKER_URL
	});

	if (self.conf.backend_type === 'amqp') {
		self.backend = self.broker;
		self.backend_connected = true;
	} else if (self.conf.backend_type === 'redis') {
		var purl = url.parse(self.conf.RESULT_BACKEND);
		self.backend = redis.createClient(purl.port, purl.hostname);

		var on_ready = function() {
			self.backend_connected = true;
			if (self.broker_connected) {
				self.ready = true;
				debug('Emiting connect event...');
				self.emit('connect');
			}
		};

		self.backend.on('connect', function() {
			debug('Backend connected...');
			if (purl.auth) {
				self.backend.auth(purl.auth.split(':')[1], on_ready);
			} else {
				on_ready();
			}
		});

		self.backend.on('error', function(err) {
			self.emit('error', err);
		});
	} else {
		self.backend_connected = true;
	}

	self.broker.on('ready', function() {
		debug('Broker connected...');
		self.broker_connected = true;
		if (self.backend_connected) {
			self.ready = true;
			self.emit('connect');
		}
	});

	self.broker.on('error', function(err) {
		self.emit('error', err);
	});

	self.broker.on('end', function() {
		self.emit('end');
	});
}

util.inherits(Client, events.EventEmitter);

Client.prototype.createTask = function(name, options) {
	return new Task(this, name, options);
};

Client.prototype.end = function() {
	this.broker.end();
	if (this.broker !== this.backend) {
		this.backend.end();
	}
};

Client.prototype.call = function(name, args, kwargs, options, callback) {
	var task = this.createTask(name),
		result = task.call(args, kwargs, options);

	if (callback) {
		result.on('ready', callback);
	}
	return result;
};

function Task(client, name, options) {
	var self = this;

	self.client = client;
	self.name = name;
	self.options = options || {};

	self.publish = function(args, kwargs, options, callback) {
		var id = uuid.v4();
		self.client.broker.publish(
		options.queue || self.client.conf.DEFAULT_QUEUE,
		createMessage(self.name, args, kwargs, options, id), {
			'contentType': 'application/json'
		},
		callback);
		return new Result(id, self.client);
	};
}

Task.prototype.call = function(args, kwargs, options, callback) {
	var self = this;

	args = args || [];
	kwargs = kwargs || {};
	options = options || {};

	assert(self.client.ready);
	return self.publish(args, kwargs, options, callback);
};

function Result(taskid, client) {
	var self = this;

	events.EventEmitter.call(self);
	self.taskid = taskid;
	self.client = client;
	self.result = null;

	if (self.client.conf.backend_type === 'amqp') {
		debug('Subscribing to result queue...');
		self.client.backend.queue(
		self.taskid.replace(/-/g, ''), {
			"arguments": {
				'x-expires': self.client.conf.TASK_RESULT_EXPIRES
			}
		},

		function(q) {
			q.bind(self.client.conf.RESULT_EXCHANGE, '#');
			q.subscribe(function(message) {
				self.result = message;
				//q.unbind('#');
				debug('Emiting ready event...');
				self.emit('ready', message);
				self.emit(message.status.toLowerCase(), message);
			});
		});
	}
}

util.inherits(Result, events.EventEmitter);

Result.prototype.get = function(callback) {
	var self = this;
	if (callback && self.result === null) {
		self.client.backend.get('celery-task-meta-' + self.taskid, function(err, reply) {
			self.result = JSON.parse(reply);
			callback(self.result);
		});
	} else {
		return self.result;
	}
};

exports.createClient = function(config) {
	return new Client(config);
};
<commit_msg>Client.call arguments can be optional<commit_after>var uuid = require('node-uuid'),
	url = require('url'),
	util = require('util'),
	amqp = require('amqp'),
	redis = require('redis'),
	assert = require('assert'),
	events = require('events');

var createMessage = require('./protocol').createMessage;

debug = process.env.NODE_CELERY_DEBUG === '1' ? util.debug : function() {};

function Configuration(options) {
	var self = this;

	for (var o in options) {
		if (options.hasOwnProperty(o)) {
			self[o.replace(/^CELERY_/, '')] = options[o];
		}
	}

	self.BROKER_URL = self.BROKER_URL || 'amqp://';
	self.DEFAULT_QUEUE = self.DEFAULT_QUEUE || 'celery';
	self.RESULT_EXCHANGE = self.RESULT_EXCHANGE || 'celeryresults';
	self.TASK_RESULT_EXPIRES = self.TASK_RESULT_EXPIRES || 86400000; // 1 day

	if (self.RESULT_BACKEND && self.RESULT_BACKEND.toLowerCase() === 'amqp') {
		self.backend_type = 'amqp';
	} else if (self.RESULT_BACKEND && url.parse(self.RESULT_BACKEND).protocol === 'redis:') {
		self.backend_type = 'redis';
	}
}

function Client(conf) {
	var self = this;

	self.conf = new Configuration(conf);

	self.ready = false;
	self.broker_connected = false;
	self.backend_connected = false;

	self.broker = amqp.createConnection({
		url: self.conf.BROKER_URL
	});

	if (self.conf.backend_type === 'amqp') {
		self.backend = self.broker;
		self.backend_connected = true;
	} else if (self.conf.backend_type === 'redis') {
		var purl = url.parse(self.conf.RESULT_BACKEND);
		self.backend = redis.createClient(purl.port, purl.hostname);

		var on_ready = function() {
			self.backend_connected = true;
			if (self.broker_connected) {
				self.ready = true;
				debug('Emiting connect event...');
				self.emit('connect');
			}
		};

		self.backend.on('connect', function() {
			debug('Backend connected...');
			if (purl.auth) {
				self.backend.auth(purl.auth.split(':')[1], on_ready);
			} else {
				on_ready();
			}
		});

		self.backend.on('error', function(err) {
			self.emit('error', err);
		});
	} else {
		self.backend_connected = true;
	}

	self.broker.on('ready', function() {
		debug('Broker connected...');
		self.broker_connected = true;
		if (self.backend_connected) {
			self.ready = true;
			self.emit('connect');
		}
	});

	self.broker.on('error', function(err) {
		self.emit('error', err);
	});

	self.broker.on('end', function() {
		self.emit('end');
	});
}

util.inherits(Client, events.EventEmitter);

Client.prototype.createTask = function(name, options) {
	return new Task(this, name, options);
};

Client.prototype.end = function() {
	this.broker.end();
	if (this.broker !== this.backend) {
		this.backend.end();
	}
};

Client.prototype.call = function(name /*[args], [kwargs], [options], [callback]*/ ) {
	var args, kwargs, options, callback;
	for (var i = arguments.length - 1; i > 0; i--) {
		if (typeof arguments[i] === 'function') {
			callback = arguments[i];
		} else if (Object.prototype.toString.call(arguments[i]) === '[object Array]') {
			args = arguments[i];
		} else if (typeof arguments[i] === 'object') {
			if (kwargs) {
				options = arguments[i];
			} else {
				kwargs = arguments[i];
			}
		}
	}

	var task = this.createTask(name),
		result = task.call(args, kwargs, options);

	if (callback) {
		result.on('ready', callback);
	}
	return result;
};

function Task(client, name, options) {
	var self = this;

	self.client = client;
	self.name = name;
	self.options = options || {};

	self.publish = function(args, kwargs, options, callback) {
		var id = uuid.v4();
		self.client.broker.publish(
		options.queue || self.client.conf.DEFAULT_QUEUE,
		createMessage(self.name, args, kwargs, options, id), {
			'contentType': 'application/json'
		},
		callback);
		return new Result(id, self.client);
	};
}

Task.prototype.call = function(args, kwargs, options, callback) {
	var self = this;

	args = args || [];
	kwargs = kwargs || {};
	options = options || self.options || {};

	assert(self.client.ready);
	return self.publish(args, kwargs, options, callback);
};

function Result(taskid, client) {
	var self = this;

	events.EventEmitter.call(self);
	self.taskid = taskid;
	self.client = client;
	self.result = null;

	if (self.client.conf.backend_type === 'amqp') {
		debug('Subscribing to result queue...');
		self.client.backend.queue(
		self.taskid.replace(/-/g, ''), {
			"arguments": {
				'x-expires': self.client.conf.TASK_RESULT_EXPIRES
			}
		},

		function(q) {
			q.bind(self.client.conf.RESULT_EXCHANGE, '#');
			q.subscribe(function(message) {
				self.result = message;
				//q.unbind('#');
				debug('Emiting ready event...');
				self.emit('ready', message);
				self.emit(message.status.toLowerCase(), message);
			});
		});
	}
}

util.inherits(Result, events.EventEmitter);

Result.prototype.get = function(callback) {
	var self = this;
	if (callback && self.result === null) {
		self.client.backend.get('celery-task-meta-' + self.taskid, function(err, reply) {
			self.result = JSON.parse(reply);
			callback(self.result);
		});
	} else {
		return self.result;
	}
};

exports.createClient = function(config) {
	return new Client(config);
};
<|endoftext|>


<commit_before>"use strict";

const
  request = require('request'),
  fs = require('fs-extra'),
  rm = require('rimraf'),
  path = require('path'),
  AdmZip = require('adm-zip'),
  tmp = require('tmp');

const CESIUM_VERSION = '0.11.4';
const CESIUM_RELEASE = 'https://github.com/duniter/cesium/releases/download/v' + CESIUM_VERSION + '/cesium-v' + CESIUM_VERSION + '-web.zip';
const OUTPUT_ZIP = tmp.fileSync().name;
const EXTRACT_PATH = path.join(__dirname, './cesium');
const EXTRACT_OVERWRITE = true;

const outputStream = fs.createWriteStream(OUTPUT_ZIP);

outputStream.on('close', () => {
  console.log('Extracting Cesium to path %s...', EXTRACT_PATH);
  const zip = new AdmZip(OUTPUT_ZIP);
  zip.extractAllTo(EXTRACT_PATH, EXTRACT_OVERWRITE);

  const cesiumSourceFolder = EXTRACT_PATH;
  const cesiumPublicFolder = path.join(__dirname, 'public', 'cesium');
  if (fs.existsSync(cesiumPublicFolder)) {
    // Remove existing cesium installation
    rm.sync(cesiumPublicFolder);
  }

  // Move extracted Cesium to destination folder
  fs.renameSync(cesiumSourceFolder, cesiumPublicFolder);

  // Configure Cesium
  const cesiumConfigFile = path.join(__dirname, 'public', 'cesium', 'config.js');
  let config = fs.readFileSync(cesiumConfigFile, 'utf8');
  config = config.replace(/"plugins"(\n|.)*"version"/, '"plugins\": {},\n\t"version"');
  config = config.replace(/"compatProtocol_0_80": true,/, '"compatProtocol_0_80": false,');
  fs.writeFileSync(cesiumConfigFile, config);

  process.exit(0);
});

console.log('Downloading file %s...', CESIUM_RELEASE);

request({
  followAllRedirects: true,
  url: CESIUM_RELEASE
}).pipe(outputStream);

<commit_msg>[enh] update cesium to 0.11.8.<commit_after>"use strict";

const
  request = require('request'),
  fs = require('fs-extra'),
  rm = require('rimraf'),
  path = require('path'),
  AdmZip = require('adm-zip'),
  tmp = require('tmp');

const CESIUM_VERSION = '0.11.8';
const CESIUM_RELEASE = 'https://github.com/duniter/cesium/releases/download/v' + CESIUM_VERSION + '/cesium-v' + CESIUM_VERSION + '-web.zip';
const OUTPUT_ZIP = tmp.fileSync().name;
const EXTRACT_PATH = path.join(__dirname, './cesium');
const EXTRACT_OVERWRITE = true;

const outputStream = fs.createWriteStream(OUTPUT_ZIP);

outputStream.on('close', () => {
  console.log('Extracting Cesium to path %s...', EXTRACT_PATH);
  const zip = new AdmZip(OUTPUT_ZIP);
  zip.extractAllTo(EXTRACT_PATH, EXTRACT_OVERWRITE);

  const cesiumSourceFolder = EXTRACT_PATH;
  const cesiumPublicFolder = path.join(__dirname, 'public', 'cesium');
  if (fs.existsSync(cesiumPublicFolder)) {
    // Remove existing cesium installation
    rm.sync(cesiumPublicFolder);
  }

  // Move extracted Cesium to destination folder
  fs.renameSync(cesiumSourceFolder, cesiumPublicFolder);

  // Configure Cesium
  const cesiumConfigFile = path.join(__dirname, 'public', 'cesium', 'config.js');
  let config = fs.readFileSync(cesiumConfigFile, 'utf8');
  config = config.replace(/"plugins"(\n|.)*"version"/, '"plugins\": {},\n\t"version"');
  config = config.replace(/"compatProtocol_0_80": true,/, '"compatProtocol_0_80": false,');
  fs.writeFileSync(cesiumConfigFile, config);

  process.exit(0);
});

console.log('Downloading file %s...', CESIUM_RELEASE);

request({
  followAllRedirects: true,
  url: CESIUM_RELEASE
}).pipe(outputStream);

<|endoftext|>