<commit_before>/*jslint nomen: true, todo: true */
"use 6to5";
"use strict";

var flux = require("flukes"),
  fs = require("fs"),
  _ = require('lodash'),
  path = require("path"),
  prefs = require("./common/userPref_model"),
  DMP = require("diff_match_patch"),
  editorAction = require('./common/editor_action'),
  bufferAction = require('./common/buffer_action'),
  Range = require("atom").Range,
  atomUtils = require("./atom_utils"),
  CompositeDisposable = require('atom').CompositeDisposable,
  util = require('util');

var floop = require("./common/floop"),
  utils = require("./utils");

var AtomListener = function (bufs, users) {
  this.bufs = bufs;
  this.users = users;
  this.editorID_ =  0;
  this.atomEditors = {};
  this.atomBuffers = {};
  this.disposables = null;
  this.send_patch_for = {};
  this.highlights = {};
  this.highlightToSend = null;
  this.lastHighlightReceived = null;
};

AtomListener.prototype.onDidChange = function (buffer, change) {
  console.log("changed");
  if (this.ignore_events) {
    return;
  }

  if (!buffer.floobitsID) {
    console.warn(buffer.getPath(), " is in project but not shared (and it changed).");
  }
  this.send_patch_for[buffer.floobitsID] = true;
};

AtomListener.prototype.onDidStopChanging = function (buffer) {
  if (!floop.connected || !this.send_patch_for[buffer.floobitsID]) {
    return;
  }

  this.send_patch_for[buffer.floobitsID] = false;

  const fluffer = this.bufs.get(buffer.floobitsID);

  if (!fluffer || !fluffer.populated) {
    return;
  }

  console.log("sending patch");

  if (this.highlightToSend) {
    floop.send_highlight(this.highlightToSend);
    this.highlightToSend = null;
  }

  fluffer.send_patch([buffer.getText()]);
};

AtomListener.prototype.onDidSave = function (buffer) {
  var self = this,
    fluffer = this.bufs.get(buffer.floobitsID);

  if (self.ignore_events || !fluffer || !fluffer.populated) {
    return;
  }

  floop.send_saved({id: fluffer.id});
};

AtomListener.prototype.onDidChangeSelectionRange = function (editor) {
  if (self.ignore_events) {
    return;
  }

  const fluffer = this.bufs.findFluffer(editor);
  if (!fluffer || !fluffer.populated) {
    return;
  }

  const ranges = atomUtils.rangeFromEditor(editor);

  const highlight = {
    'id': fluffer.id,
    'ping': false,
    'summon': false,
    'following': false,
    'ranges': ranges,
  };

  if (this.send_patch_for[fluffer.id]) {
    this.highlightToSend = highlight;
  } else {
    floop.send_highlight(highlight);
  }
};

AtomListener.prototype.onDidChangePath = function (buffer) {
  var isEditor = buffer.buffer,
    oldFluffer = this.bufs.get(buffer.floobitsID),
    fluffer = this.bufs.findFluffer(buffer),
    oldPath, floobitsID;

  if (oldFluffer) {
    oldPath = oldFluffer.path;
  }

  buffer.floobitsID = fluffer ? fluffer.id : -1;
  if (isEditor) {
    return;
  }

  if (oldPath) {
    // TODO: send event
    return;
  }
  if (fluffer) {
    // TODO: check to see if its shared
  }
};

AtomListener.prototype.bindTo_ = function (obj, events) {
  var that = this,
    disposables = new CompositeDisposable();

  _.each(events, function (eventName) {
    var d;

    function action () {
      var args = _.toArray(arguments);
      args.unshift(obj);
      that[eventName].apply(that, args);
    }
    disposables.add(obj[eventName](action));
  });

  this.disposables.add(disposables);

  obj.onDidDestroy(function () {
    disposables.dispose();
    that.disposables.remove(disposables);
    delete obj.floobitsID;
  });
};
AtomListener.prototype.stop = function () {
  _.each(this.highlights, function (markers) {
    markers.forEach(function (m) {
      try {
        m.destroy();
      } catch (e) {
        console.log(e);
      }
    });
  });
  this.highlights = {};
  this.disposables.dispose();
  this.disposables = null;
};

AtomListener.prototype.on_floobits_highlight = function (data) {
  var fluffer, editors, highlights = this.highlights;

  if (!data || _.size(data.ranges) === 0) {
    return;
  }

  fluffer = this.bufs.get(data.id);

  if (!fluffer || !fluffer.populated) {
    return;
  }

  var markers = highlights[data.username];
  if (markers) {
    markers.forEach(function (m) {
      try {
        m.destroy();
      } catch (e) {
        console.log(e);
      }
    });
    delete highlights[data.username];
  }

  editors = this.atomEditors[data.id] || [];
  this.atomEditors[data.id] = _.filter(editors, function (e) {
    return e.isAlive();
  });

  let following = prefs.isFollowing(data.username);

  if (!editors || !editors.length) {
    if (following) {
      atom.workspace.open(fluffer.abs_path()).done(function (editor) {
        this.on_floobits_highlight(data);
      }.bind(this));
    }
    return;
  }

  highlights[data.username] = [];

  var klass = "floobits-highlight-dark_" + utils.user_color(data.username);

  var toActivate = [];
  _.each(editors, function (editor) {
    var buffer, offsetIndex;

    buffer = editor.buffer;
    if (!buffer) {
      return;
    }

    if (!buffer.isAlive()) {
      console.log("dead buffer?!");
      return;
    }

    offsetIndex = buffer.offsetIndex;

    _.map(data.ranges, function (range) {
      var startRow, endRow, colStart, colEnd, line;
      startRow = offsetIndex.totalTo(range[0], 'characters').rows;
      colStart = range[0] - offsetIndex.totalTo(startRow, "rows").characters;
      endRow   = offsetIndex.totalTo(range[1], 'characters').rows;
      colEnd   = range[1] - offsetIndex.totalTo(endRow, "rows").characters;
      if (startRow === endRow && colStart === colEnd) {
        line = buffer.lineForRow(endRow);
        if (line && line.length == colEnd) {
          colStart -= 1;
        } else {
          colEnd += 1;
        }
      }
      return new Range([startRow, colStart], [endRow, colEnd]);
    }).forEach(function (r) {
      var marker;
      marker = editor.markBufferRange(r, {invalidate: 'never'});
      editor.decorateMarker(marker, {type: "highlight", class: klass});
      if (following) {
        editor.scrollToBufferPosition(r.start);
        toActivate.push(editor.id);
      }
      highlights[data.username].push(marker);
    });
  });
  this.lastHighlightReceived = data;

  var pane = atom.workspace.getActivePane();
  if (!pane || !toActivate || !following) {
    return;
  }

  let position = 0;
  _.each(pane.items, function (item) {
    if (_.contains(toActivate, item.id)) {
      pane.activateItemAtIndex(position);
    }
    position += 1;
  });
};

AtomListener.prototype.observeTextEditors = function (editor) {
  var p, fluffer, buffer, id, that = this;

  // bad stuff happens if we throw here
  try {
    that.bindTo_(editor, ["onDidChangeSelectionRange"]);

    fluffer = that.bufs.findFluffer(editor);

    if (!fluffer) {
      id = -1;
    } else {
      id = fluffer.id;
    }

    if (!(id in that.atomEditors)) {
      that.atomEditors[id] = [];
    }

    that.atomEditors[id].push(editor);
    let d = editor.onDidDestroy(function () {
      let editors = that.atomEditors[id] || [];
      that.atomEditors = editors.filter(function (e) {
        return e.id !== editor.id;
      });
      that.disposables.remove(d);
    });

    that.disposables.add(d);

    buffer = editor.getBuffer();
    // editors to buffers is many to one
    if (_.has(buffer, "floobitsID")) {
      return;
    }

    buffer.floobitsID = id;
    // TODO: maybe check to see if one is there first?
    that.atomBuffers[id] = buffer;

    let dispose = buffer.onDidDestroy(function () {
      delete that.atomBuffers[buffer.floobitsID];
      that.disposables.remove(dispose);
    });

    that.disposables.add(dispose);

    that.bindTo_(buffer, ["onDidChangePath", "onDidSave", "onDidStopChanging", "onDidChange"]);
  } catch (e) {
    console.error(e);
  }

};

AtomListener.prototype.start = function () {
  var that = this, d;

  this.disposables = new CompositeDisposable();

  // TODO: unbind these
  editorAction.onJUMP_TO_USER(function (username) {
    let data;
    if (username === "floobits") {
      data = this.lastHighlightReceived;
    } else {
      data = this.highlights[username];
    }
    if (!data) {
      return;
    }
    this.on_floobits_highlight(data);
  }, this);

  editorAction.onHANDLE_CONFLICTS(function (newFiles, different, missing) {
    let View = require("./build/conflicts");
    let view = View({newFiles: newFiles, different: different, missing: missing});
    let wrapper = require("./react_wrapper");
    let conflicts = wrapper.create_node('conflicts', view,
      {width: "100%", height: "100%", overflow: "auto"}
    );

    let P = require('../templates/pane');
    let p = new P("Floobits Conflicts", "", conflicts);
    atom.workspace.getActivePane().activateItem(p);
  });

  d = atom.workspace.observeTextEditors(this.observeTextEditors.bind(this));
  this.disposables.add(d);
  floop.onHIGHLIGHT(this.on_floobits_highlight.bind(this));
  bufferAction.onDELETED(function (b, force) {
    // TODO: unbind
    delete that.atomBuffers[b.id];
    if (!force) {
      return;
    }
    that.ignore_events = true;
    try {
      fs.unlinkSync(b.abs_path());
    } catch (e) {
      console.error(e);
    }
    that.ignore_events = false;
  });
  bufferAction.onSAVED(function (b) {
    var buffer = that.atomBuffers[b.id];
    if (!buffer || !buffer.isAlive()) {
      return;
    }
    that.ignore_events = true;
    buffer.save();
    that.ignore_events = false;
  });
  bufferAction.onCHANGED(function (b) {
    var buffer = that.atomBuffers[b.id];

    // TODO: renames can happen even when not populated
    if (!b.populated) {
      return;
    }

    that.ignore_events = true;

    try {
      if (!buffer || !buffer.isAlive()) {
        console.log("writing", b.path);
        fs.writeFileSync(b.abs_path(), b.buf, {encoding: b.encoding});
      } else {
        console.log("updating", b.path);
        buffer.setText(b.buf);
        if (b.shouldSave) {
          buffer.save();
        }
      }
    } catch (e) {
      console.error(e);
    }

    b.shouldSave = false;
    that.ignore_events = false;
  });
};

module.exports = AtomListener;
<commit_msg>fixes #2<commit_after>/*jslint nomen: true, todo: true */
"use 6to5";
"use strict";

var flux = require("flukes"),
  fs = require("fs"),
  _ = require('lodash'),
  path = require("path"),
  prefs = require("./common/userPref_model"),
  DMP = require("diff_match_patch"),
  editorAction = require('./common/editor_action'),
  bufferAction = require('./common/buffer_action'),
  Range = require("atom").Range,
  atomUtils = require("./atom_utils"),
  CompositeDisposable = require('atom').CompositeDisposable,
  util = require('util');

var floop = require("./common/floop"),
  utils = require("./utils");

var AtomListener = function (bufs, users) {
  this.bufs = bufs;
  this.users = users;
  this.editorID_ =  0;
  this.atomEditors = {};
  this.atomBuffers = {};
  this.disposables = null;
  this.send_patch_for = {};
  this.highlights = {};
  this.highlightToSend = null;
  this.lastHighlightReceived = null;
};

AtomListener.prototype.onDidChange = function (buffer, change) {
  console.log("changed");
  if (this.ignore_events) {
    return;
  }

  if (!buffer.floobitsID) {
    console.warn(buffer.getPath(), " is in project but not shared (and it changed).");
  }
  this.send_patch_for[buffer.floobitsID] = true;
};

AtomListener.prototype.onDidStopChanging = function (buffer) {
  if (!floop.connected || !this.send_patch_for[buffer.floobitsID]) {
    return;
  }

  this.send_patch_for[buffer.floobitsID] = false;

  const fluffer = this.bufs.get(buffer.floobitsID);

  if (!fluffer || !fluffer.populated) {
    return;
  }

  console.log("sending patch");

  if (this.highlightToSend) {
    floop.send_highlight(this.highlightToSend);
    this.highlightToSend = null;
  }

  fluffer.send_patch([buffer.getText()]);
};

AtomListener.prototype.onDidSave = function (buffer) {
  var self = this,
    fluffer = this.bufs.get(buffer.floobitsID);

  if (self.ignore_events || !fluffer || !fluffer.populated) {
    return;
  }

  floop.send_saved({id: fluffer.id});
};

AtomListener.prototype.onDidChangeSelectionRange = function (editor) {
  if (self.ignore_events) {
    return;
  }

  const fluffer = this.bufs.findFluffer(editor);
  if (!fluffer || !fluffer.populated) {
    return;
  }

  const ranges = atomUtils.rangeFromEditor(editor);

  const highlight = {
    'id': fluffer.id,
    'ping': false,
    'summon': false,
    'following': false,
    'ranges': ranges,
  };

  if (this.send_patch_for[fluffer.id]) {
    this.highlightToSend = highlight;
  } else {
    floop.send_highlight(highlight);
  }
};

AtomListener.prototype.onDidChangePath = function (buffer) {
  var isEditor = buffer.buffer,
    oldFluffer = this.bufs.get(buffer.floobitsID),
    fluffer = this.bufs.findFluffer(buffer),
    oldPath, floobitsID;

  if (oldFluffer) {
    oldPath = oldFluffer.path;
  }

  buffer.floobitsID = fluffer ? fluffer.id : -1;
  if (isEditor) {
    return;
  }

  if (oldPath) {
    // TODO: send event
    return;
  }
  if (fluffer) {
    // TODO: check to see if its shared
  }
};

AtomListener.prototype.bindTo_ = function (obj, events) {
  var that = this,
    disposables = new CompositeDisposable();

  _.each(events, function (eventName) {
    var d;

    function action () {
      var args = _.toArray(arguments);
      args.unshift(obj);
      that[eventName].apply(that, args);
    }
    disposables.add(obj[eventName](action));
  });

  this.disposables.add(disposables);

  obj.onDidDestroy(function () {
    disposables.dispose();
    that.disposables.remove(disposables);
    delete obj.floobitsID;
  });
};
AtomListener.prototype.stop = function () {
  _.each(this.highlights, function (markers) {
    markers.forEach(function (m) {
      try {
        m.destroy();
      } catch (e) {
        console.log(e);
      }
    });
  });
  this.highlights = {};
  this.disposables.dispose();
  this.disposables = null;
};

AtomListener.prototype.on_floobits_highlight = function (data) {
  var fluffer, editors, highlights = this.highlights;

  if (!data || _.size(data.ranges) === 0) {
    return;
  }

  fluffer = this.bufs.get(data.id);

  if (!fluffer || !fluffer.populated) {
    return;
  }

  var markers = highlights[data.username];
  if (markers) {
    markers.forEach(function (m) {
      try {
        m.destroy();
      } catch (e) {
        console.log(e);
      }
    });
    delete highlights[data.username];
  }

  editors = this.atomEditors[data.id] || [];
  this.atomEditors[data.id] = _.filter(editors, function (e) {
    return e.isAlive();
  });

  let following = prefs.isFollowing(data.username);

  if (!editors || !editors.length) {
    if (following) {
      atom.workspace.open(fluffer.abs_path()).done(function (editor) {
        this.on_floobits_highlight(data);
      }.bind(this));
    }
    return;
  }

  highlights[data.username] = [];

  var klass = "floobits-highlight-dark_" + utils.user_color(data.username);

  var toActivate = [];
  _.each(editors, function (editor) {
    var buffer, offsetIndex;

    buffer = editor.buffer;
    if (!buffer) {
      return;
    }

    if (!buffer.isAlive()) {
      console.log("dead buffer?!");
      return;
    }

    offsetIndex = buffer.offsetIndex;

    _.map(data.ranges, function (range) {
      var startRow, endRow, colStart, colEnd, line;
      startRow = offsetIndex.totalTo(range[0], 'characters').rows;
      colStart = range[0] - offsetIndex.totalTo(startRow, "rows").characters;
      endRow   = offsetIndex.totalTo(range[1], 'characters').rows;
      colEnd   = range[1] - offsetIndex.totalTo(endRow, "rows").characters;
      if (startRow === endRow && colStart === colEnd) {
        line = buffer.lineForRow(endRow);
        if (line && line.length == colEnd) {
          colStart -= 1;
        } else {
          colEnd += 1;
        }
      }
      return new Range([startRow, colStart], [endRow, colEnd]);
    }).forEach(function (r) {
      var marker;
      marker = editor.markBufferRange(r, {invalidate: 'never'});
      editor.decorateMarker(marker, {type: "highlight", class: klass});
      if (following) {
        editor.scrollToBufferPosition(r.start);
        toActivate.push(editor.id);
      }
      highlights[data.username].push(marker);
    });
  });
  this.lastHighlightReceived = data;

  var pane = atom.workspace.getActivePane();
  if (!pane || !toActivate || !following) {
    return;
  }

  let position = 0;
  _.each(pane.items, function (item) {
    if (_.contains(toActivate, item.id)) {
      pane.activateItemAtIndex(position);
    }
    position += 1;
  });
};

AtomListener.prototype.observeTextEditors = function (editor) {
  var p, fluffer, buffer, id, that = this;

  // bad stuff happens if we throw here
  try {
    that.bindTo_(editor, ["onDidChangeSelectionRange"]);

    fluffer = that.bufs.findFluffer(editor);

    if (!fluffer) {
      id = -1;
    } else {
      id = fluffer.id;
    }

    if (!(id in that.atomEditors)) {
      that.atomEditors[id] = [];
    }

    that.atomEditors[id].push(editor);
    let d = editor.onDidDestroy(function () {
      let editors = that.atomEditors[id] || [];
      that.atomEditors = editors.filter(function (e) {
        return e.id !== editor.id;
      });
      that.disposables.remove(d);
    });

    that.disposables.add(d);

    buffer = editor.getBuffer();
    // editors to buffers is many to one
    if (_.has(buffer, "floobitsID")) {
      return;
    }

    buffer.floobitsID = id;
    // TODO: maybe check to see if one is there first?
    that.atomBuffers[id] = buffer;

    let dispose = buffer.onDidDestroy(function () {
      delete that.atomBuffers[buffer.floobitsID];
      that.disposables.remove(dispose);
    });

    that.disposables.add(dispose);

    that.bindTo_(buffer, ["onDidChangePath", "onDidSave", "onDidStopChanging", "onDidChange"]);
  } catch (e) {
    console.error(e);
  }

};

AtomListener.prototype.start = function () {
  var that = this, d;

  this.disposables = new CompositeDisposable();

  // TODO: unbind these
  editorAction.onJUMP_TO_USER(function (username) {
    let data;
    if (username === "floobits") {
      data = this.lastHighlightReceived;
    } else {
      data = this.highlights[username];
    }
    if (!data) {
      return;
    }
    this.on_floobits_highlight(data);
  }, this);

  editorAction.onHANDLE_CONFLICTS(function (newFiles, different, missing) {
    let View = require("./build/conflicts");
    let view = View({newFiles: newFiles, different: different, missing: missing});
    let wrapper = require("./react_wrapper");
    let conflicts = wrapper.create_node('conflicts', view,
      {width: "100%", height: "100%", overflow: "auto"}
    );

    let P = require('../templates/pane');
    let p = new P("Floobits Conflicts", "", conflicts);
    atom.workspace.getActivePane().activateItem(p);
  });

  d = atom.workspace.observeTextEditors(this.observeTextEditors.bind(this));
  this.disposables.add(d);
  floop.onHIGHLIGHT(this.on_floobits_highlight.bind(this));
  bufferAction.onDELETED(function (b, force) {
    // TODO: unbind
    delete that.atomBuffers[b.id];
    if (!force) {
      return;
    }
    that.ignore_events = true;
    try {
      fs.unlinkSync(b.abs_path());
    } catch (e) {
      console.error(e);
    }
    that.ignore_events = false;
  });
  bufferAction.onSAVED(function (b) {
    const buffer = that.atomBuffers[b.id];
    that.ignore_events = true;
    try {
      if (buffer && buffer.isAlive()) {
        buffer.save();
      } else {
        fs.writeFileSync(b.abs_path(), buf.buf, {encoding: b.encoding});
      }
    } catch (e) {
      console.error(e);
    }
    that.ignore_events = false;
  });
  bufferAction.onCHANGED(function (b) {
    var buffer = that.atomBuffers[b.id];

    // TODO: renames can happen even when not populated
    if (!b.populated) {
      return;
    }

    that.ignore_events = true;

    try {
      if (!buffer || !buffer.isAlive()) {
        console.log("writing", b.path);
        fs.writeFileSync(b.abs_path(), b.buf, {encoding: b.encoding});
      } else {
        console.log("updating", b.path);
        buffer.setText(b.buf);
        if (b.shouldSave) {
          buffer.save();
        }
      }
    } catch (e) {
      console.error(e);
    }

    b.shouldSave = false;
    that.ignore_events = false;
  });
};

module.exports = AtomListener;
<|endoftext|>


<commit_before>'use strict';

describe('Stimuli.core.Recorder', function() {

    var a, recorder, clock;

    beforeEach(function(){
        var DeferableClass = function() {};

        Stimuli.core.Class.mix(DeferableClass, Stimuli.core.Deferable);

        a = new DeferableClass();

        recorder = new Stimuli.core.Recorder();

    });

    afterEach(function() {
        a = null;
        recorder = null;
    });

    it('should be able to record and replay a entire sequence of a deferable', function() {
        var clock = sinon.useFakeTimers();
        var str = '';
        var previousTime;

        a.synchronize(recorder);

        recorder
            .start('foobar');

        a
            .then(function() {
                str += '1';
            })
            .sleep(1)
            .then(function() {
                str += '2';
            })
            .sleep(2)
            .then(function() {
                str += '3';
            });

        recorder
            .stop('foobar')
            .replay('foobar', 2);

        clock.tick(9);
        expect(str).to.be('123123123');
        clock.restore();
    });

    it('should be able to record two sequences in a deferable even if they cross each others', function() {
        var clock = sinon.useFakeTimers();
        var str = '';
        var previousTime;

        a.synchronize(recorder);

        recorder.start('foobar');

        a
        .then(function() {
            str += '1';
        })
        .sleep(1)
        .then(function() {
            str += '2';
        });

        recorder.start('foobar2');

        a
        .sleep(2)
        .then(function() {
            str += '3';
        });

        recorder
            .stop('foobar')
            .stop('foobar2')
            .replay('foobar', 2)
            .replay('foobar2', 2)

        clock.tick(13);
        expect(str).to.be('12312312333');
        clock.restore();
    });

});
<commit_msg>lint: missing comma in recorder spec<commit_after>'use strict';

describe('Stimuli.core.Recorder', function() {

    var a, recorder, clock;

    beforeEach(function(){
        var DeferableClass = function() {};

        Stimuli.core.Class.mix(DeferableClass, Stimuli.core.Deferable);

        a = new DeferableClass();

        recorder = new Stimuli.core.Recorder();

    });

    afterEach(function() {
        a = null;
        recorder = null;
    });

    it('should be able to record and replay a entire sequence of a deferable', function() {
        var clock = sinon.useFakeTimers();
        var str = '';
        var previousTime;

        a.synchronize(recorder);

        recorder
            .start('foobar');

        a
            .then(function() {
                str += '1';
            })
            .sleep(1)
            .then(function() {
                str += '2';
            })
            .sleep(2)
            .then(function() {
                str += '3';
            });

        recorder
            .stop('foobar')
            .replay('foobar', 2);

        clock.tick(9);
        expect(str).to.be('123123123');
        clock.restore();
    });

    it('should be able to record two sequences in a deferable even if they cross each others', function() {
        var clock = sinon.useFakeTimers();
        var str = '';
        var previousTime;

        a.synchronize(recorder);

        recorder.start('foobar');

        a
        .then(function() {
            str += '1';
        })
        .sleep(1)
        .then(function() {
            str += '2';
        });

        recorder.start('foobar2');

        a
        .sleep(2)
        .then(function() {
            str += '3';
        });

        recorder
            .stop('foobar')
            .stop('foobar2')
            .replay('foobar', 2)
            .replay('foobar2', 2);

        clock.tick(13);
        expect(str).to.be('12312312333');
        clock.restore();
    });

});
<|endoftext|>


<commit_before>const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const path = require('path');
const fs = require('fs-extra');
const async = require('async');
const wrap = require('word-wrap');
const util = require('util');
const glob = require('glob');
const sqlite3 = require('sqlite3');
const entries = require('object.entries');
const moment = require('moment');
const request = require('request');

const reports = require('./lib/reports');
const utils = require('./lib/utils');
const dashboards = require('./lib/dashboards');
const pages = require('./lib/pages');
const loads = require('./lib/loads');
const checking = require('./lib/checking');
const runtime_auth = require('./lib/runtime_auth');
const NICE_ERRORS = require('./lib/nice_errors');

if (!Object.entries) { entries.shim(); }

var db;

function connectToDB(options) {
    /*
    Normal connection to MongoDB. This is the MongoDB being run by ghcrawler.
    */
    return new Promise((resolve, reject) => {
        var url = 'mongodb://localhost:27017/ghcrawler';
        MongoClient.connect(url, {connectTimeoutMS: 6000, keepAlive: 5000}, function(err, mdb) {
            if (err) return reject(NICE_ERRORS.NO_MONGO_ERROR(err));
            db = mdb;
            //console.log("Connected correctly to server.");
            return resolve(Object.assign({db: mdb}, options));
        });
    })
}

function getAllOrgUsers(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            var questionmarks = [];
            for (i=0; i<options.userConfig.my_organizations.length; i++) {
                questionmarks.push("?");
            }
            var sql = "select p.login, p.joined, p.left, o.name, o.id as orgid " +
                "from orgs o left outer join people2org p " +
                "on o.id = p.org";
            db.all(sql, [], (err, results) => {
                db.close();
                if (err) return reject(err);
                var org2People = {}, orgDetails = {};
                results.forEach(function(r) {
                    if (!org2People[r.name.toLowerCase()]) org2People[r.name.toLowerCase()] = [];
                    if (r.login) {
                        org2People[r.name.toLowerCase()].push({login: r.login, joined: r.joined, left: r.left});
                    }
                    orgDetails[r.name.toLowerCase()] = r.orgid;
                })
                options.org2People = org2People;
                options.orgDetails = orgDetails;

                options.userConfig.my_organizationsTitle = "Measure";
                if (options.userConfig.my_organizations.length > 0) {
                    options.userConfig.my_organizationsTitle = options.userConfig.my_organizations[0];
                }

                // and override config to have orgnames in lowercase as well, so that when
                // widgets use them to look up entries in org2People, it works
                options.userConfig.my_organizations = options.userConfig.my_organizations.map(n => n.toLowerCase());

                return resolve(options);
            })
        });
    });
}

function getMyOrgUsers(options) {
    return new Promise((resolve, reject) => {
        if (!options.userConfig.my_organizations || options.userConfig.my_organizations.length === 0) {
            // we don't have any orgs defined as ours, so skip
            return resolve(options);
        }
        options.myOrgUsers = [];
        options.userConfig.my_organizations.forEach(o => {
            var people = options.org2People[o.toLowerCase()];
            if (people) {
                options.myOrgUsers = options.myOrgUsers.concat(people);
            }
        });
        return resolve(options);
    });
}

function api(options) {
    return new Promise((resolve, reject) => {
        fs.readFile("php/api.php", {encoding: "utf-8"}, (err, data) => {
            options.sqliteDatabase = options.userConfig.database_directory + "/admin.db";
            var rel = path.relative(options.userConfig.output_directory,
                options.sqliteDatabase);
            data = data.replace("$dsn = '';", "$dsn = 'sqlite:' . dirname(__FILE__) . '/" + rel + "';")
            const outputFile = path.join(options.userConfig.output_directory, "api.php");
            fs.writeFile(outputFile, data, {encoding: "utf8"}, err => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_WRITE_API(err, outputFile));
                }
                return resolve(options);
            });
        })
    });
}

function apiSecret(options) {
    return new Promise((resolve, reject) => {
        function apiSecretWrite(secret) {
            fs.readFile("php/secret.php", {encoding: "utf-8"}, (err, data) => {
                data = data.replace(
                    "$secret = 'Eihiqu4a Ma7Ek0ae Hozai5ci eish4Shi phiiw6Un ohD8wi3k';",
                    "$secret = '" + secret + "';"
                );
                const outputFile = path.join(options.userConfig.output_directory, "secret.php");
                fs.writeFile(outputFile, data, {encoding: "utf8"}, err => {
                    if (err) {
                        return reject(NICE_ERRORS.COULD_NOT_WRITE_API(err, outputFile));
                    }
                    return resolve(options);
                });
            })
        }

        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            db.all("select * from secret", [], (err, rows) => {
                if (err) { return reject(NICE_ERRORS.COULD_NOT_READ_DB(err)); }
                if (rows.length > 0) {
                    return apiSecretWrite(rows[0].secret);
                } else {
                    var secret = utils.randomString(50);
                    db.run("insert into secret (secret) values (?)", [secret], (err) => {
                        if (err) return reject(NICE_ERRORS.COULD_NOT_WRITE_SECRET(err));
                        apiSecretWrite(secret);
                    })
                }
            })
        });
    });
}

const tableDefinitions = [
    "notes (id INTEGER PRIMARY KEY, login TEXT, note TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)",
    "orgs (id INTEGER PRIMARY KEY, name TEXT)",
    "people2org (id INTEGER PRIMARY KEY, org INTEGER, login TEXT, joined DATETIME DEFAULT CURRENT_TIMESTAMP, left DATETIME)",
    "orgChanges (id INTEGER PRIMARY KEY, org INTEGER, change TEXT, destination INTEGER)",
    "secret (secret TEXT)",
    "bio (login TEXT PRIMARY KEY, name TEXT, company TEXT, blog TEXT, location TEXT, email TEXT, hireable TEXT)"
];
function apidb(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            async.each(tableDefinitions, (td, done) => {
                db.run("CREATE TABLE IF NOT EXISTS " + td, [], done);
            }, (err) => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_CREATE_TABLES(err));
                }
                db.close();
                return resolve(options);
            })
        });
    });
}

/* Look for changes marked in the database -- orgs deleted or merged -- and actually execute them */
function apidbActionChanges(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            db.all("select id, org, change, destination from orgChanges", [], (err, results) => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_READ_ORG_CHANGES(err))
                }
                async.eachSeries(results, (row, done) => {
                    if (row.change == "delete") {
                        // just blow away that org, and remove everyone from it
                        db.run("delete from orgs where id = ?", [row.org], (err) => {
                            if (err) return done(err);
                            return db.run("delete from people2org where org = ?", [row.org], done);
                        })
                    } else if (row.change == "merge") {
                        // move everyone into the destination, and then remove the org
                        db.run("update people2org set org = ? where org = ?", [row.destination, row.org], (err) => {
                            if (err) return done(err);
                            return db.run("delete from orgs where id = ?", [row.org], done);
                        })
                    }
                }, (err) => {
                    if (err) return reject(err);
                    // remove all orgChanges rows, because they've been processed
                    db.run("delete from orgChanges", [], (err) => {
                        if (err) return reject(err);
                        db.close();
                        return resolve(options);
                    });
                })
            });
        });
    });
}

function createMongoIndexes(options) {
    let indexes = [
        {collection: "pull_request", fields: {"updated_at":1}}
    ];
    return new Promise((resolve, reject) => {
        async.each(indexes, (index, done) => {
            options.db.collection(index.collection).ensureIndex(index.fields, {}, done);
        }, err => {
            if (err) return reject(err);
            resolve(options);
        })
    })
}

function leave(options) {
    /* And shut all our stuff down. Don't close down ghcrawler itself. */
    options.db.close();
    if (options.userConfig.debug) {
        console.log(`Dashboards generated OK in directory '${options.userConfig.output_directory}'.`);
        console.log(`Database ensured in directory '${options.userConfig.database_directory}'.`);
        var dur = (new Date()).getTime() - startupTime;
        console.log("Time taken:", moment.duration(dur).as("seconds"), "seconds");
        var timestaken = [];
        for (var widgetname in options.times) {
            timestaken.push([
                widgetname,
                options.times[widgetname].reduce(function (a, b) { return a + b; }, 0),
                options.times[widgetname].length
            ]);
        }
        timestaken.sort((a,b) => { return a[1] - b[1]; })
        console.log(timestaken.map(n => {
            return n[0] + ": " + Math.round(n[1] / 1000) + "s total in " +
                n[2] + " iterations, " + Math.round(n[1] / n[2]) + "ms/iteration";
        }).join("\n"));
    }
}

var startupTime = (new Date()).getTime();
loads.loadTemplates()
    .then(loads.readConfig)
    .then(loads.loadWidgets)
    .then(connectToDB)
    .then(checking.confirmCrawler)
    .then(checking.confirmTokens)
    .then(checking.confirmActivity)
    .then(pages.calculateCodeHash)
    .then(pages.previousGeneratedAtAndHash)
    .then(createMongoIndexes)
    .then(api)
    .then(apidb)
    .then(apiSecret)
    .then(apidbActionChanges)
    .then(getAllOrgUsers)
    .then(getMyOrgUsers)
    .then(runtime_auth.setupRuntimeAuth)
    .then(runtime_auth.copyAuthPHP)
    .then(dashboards.dashboardForEachTeam)
    .then(dashboards.dashboardForEachRepo)
    .then(dashboards.changedContributors)
    .then(dashboards.dashboardForEachContributor)
    .then(dashboards.dashboardForEachOrg)
    .then(reports)
    .then(pages.frontPage)
    .then(pages.indexPages)
    .then(pages.generatedAtAndHash)
    .then(pages.searchPage)
    .then(leave)
    .catch(e => {
        if (db) db.close();
        if (e.isNiceError) {
            console.error("Problem message:")
            console.error(e.message);
        } else {
            console.error("Internal error. (Internal errors are a bug in the code and should be reported.)");
            console.error(e.message);
            console.error(e.stack);
        }
    });
<commit_msg>Add more indices for sorted data<commit_after>const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const path = require('path');
const fs = require('fs-extra');
const async = require('async');
const wrap = require('word-wrap');
const util = require('util');
const glob = require('glob');
const sqlite3 = require('sqlite3');
const entries = require('object.entries');
const moment = require('moment');
const request = require('request');

const reports = require('./lib/reports');
const utils = require('./lib/utils');
const dashboards = require('./lib/dashboards');
const pages = require('./lib/pages');
const loads = require('./lib/loads');
const checking = require('./lib/checking');
const runtime_auth = require('./lib/runtime_auth');
const NICE_ERRORS = require('./lib/nice_errors');

if (!Object.entries) { entries.shim(); }

var db;

function connectToDB(options) {
    /*
    Normal connection to MongoDB. This is the MongoDB being run by ghcrawler.
    */
    return new Promise((resolve, reject) => {
        var url = 'mongodb://localhost:27017/ghcrawler';
        MongoClient.connect(url, {connectTimeoutMS: 6000, keepAlive: 5000}, function(err, mdb) {
            if (err) return reject(NICE_ERRORS.NO_MONGO_ERROR(err));
            db = mdb;
            //console.log("Connected correctly to server.");
            return resolve(Object.assign({db: mdb}, options));
        });
    })
}

function getAllOrgUsers(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            var questionmarks = [];
            for (i=0; i<options.userConfig.my_organizations.length; i++) {
                questionmarks.push("?");
            }
            var sql = "select p.login, p.joined, p.left, o.name, o.id as orgid " +
                "from orgs o left outer join people2org p " +
                "on o.id = p.org";
            db.all(sql, [], (err, results) => {
                db.close();
                if (err) return reject(err);
                var org2People = {}, orgDetails = {};
                results.forEach(function(r) {
                    if (!org2People[r.name.toLowerCase()]) org2People[r.name.toLowerCase()] = [];
                    if (r.login) {
                        org2People[r.name.toLowerCase()].push({login: r.login, joined: r.joined, left: r.left});
                    }
                    orgDetails[r.name.toLowerCase()] = r.orgid;
                })
                options.org2People = org2People;
                options.orgDetails = orgDetails;

                options.userConfig.my_organizationsTitle = "Measure";
                if (options.userConfig.my_organizations.length > 0) {
                    options.userConfig.my_organizationsTitle = options.userConfig.my_organizations[0];
                }

                // and override config to have orgnames in lowercase as well, so that when
                // widgets use them to look up entries in org2People, it works
                options.userConfig.my_organizations = options.userConfig.my_organizations.map(n => n.toLowerCase());

                return resolve(options);
            })
        });
    });
}

function getMyOrgUsers(options) {
    return new Promise((resolve, reject) => {
        if (!options.userConfig.my_organizations || options.userConfig.my_organizations.length === 0) {
            // we don't have any orgs defined as ours, so skip
            return resolve(options);
        }
        options.myOrgUsers = [];
        options.userConfig.my_organizations.forEach(o => {
            var people = options.org2People[o.toLowerCase()];
            if (people) {
                options.myOrgUsers = options.myOrgUsers.concat(people);
            }
        });
        return resolve(options);
    });
}

function api(options) {
    return new Promise((resolve, reject) => {
        fs.readFile("php/api.php", {encoding: "utf-8"}, (err, data) => {
            options.sqliteDatabase = options.userConfig.database_directory + "/admin.db";
            var rel = path.relative(options.userConfig.output_directory,
                options.sqliteDatabase);
            data = data.replace("$dsn = '';", "$dsn = 'sqlite:' . dirname(__FILE__) . '/" + rel + "';")
            const outputFile = path.join(options.userConfig.output_directory, "api.php");
            fs.writeFile(outputFile, data, {encoding: "utf8"}, err => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_WRITE_API(err, outputFile));
                }
                return resolve(options);
            });
        })
    });
}

function apiSecret(options) {
    return new Promise((resolve, reject) => {
        function apiSecretWrite(secret) {
            fs.readFile("php/secret.php", {encoding: "utf-8"}, (err, data) => {
                data = data.replace(
                    "$secret = 'Eihiqu4a Ma7Ek0ae Hozai5ci eish4Shi phiiw6Un ohD8wi3k';",
                    "$secret = '" + secret + "';"
                );
                const outputFile = path.join(options.userConfig.output_directory, "secret.php");
                fs.writeFile(outputFile, data, {encoding: "utf8"}, err => {
                    if (err) {
                        return reject(NICE_ERRORS.COULD_NOT_WRITE_API(err, outputFile));
                    }
                    return resolve(options);
                });
            })
        }

        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            db.all("select * from secret", [], (err, rows) => {
                if (err) { return reject(NICE_ERRORS.COULD_NOT_READ_DB(err)); }
                if (rows.length > 0) {
                    return apiSecretWrite(rows[0].secret);
                } else {
                    var secret = utils.randomString(50);
                    db.run("insert into secret (secret) values (?)", [secret], (err) => {
                        if (err) return reject(NICE_ERRORS.COULD_NOT_WRITE_SECRET(err));
                        apiSecretWrite(secret);
                    })
                }
            })
        });
    });
}

const tableDefinitions = [
    "notes (id INTEGER PRIMARY KEY, login TEXT, note TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)",
    "orgs (id INTEGER PRIMARY KEY, name TEXT)",
    "people2org (id INTEGER PRIMARY KEY, org INTEGER, login TEXT, joined DATETIME DEFAULT CURRENT_TIMESTAMP, left DATETIME)",
    "orgChanges (id INTEGER PRIMARY KEY, org INTEGER, change TEXT, destination INTEGER)",
    "secret (secret TEXT)",
    "bio (login TEXT PRIMARY KEY, name TEXT, company TEXT, blog TEXT, location TEXT, email TEXT, hireable TEXT)"
];
function apidb(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            async.each(tableDefinitions, (td, done) => {
                db.run("CREATE TABLE IF NOT EXISTS " + td, [], done);
            }, (err) => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_CREATE_TABLES(err));
                }
                db.close();
                return resolve(options);
            })
        });
    });
}

/* Look for changes marked in the database -- orgs deleted or merged -- and actually execute them */
function apidbActionChanges(options) {
    return new Promise((resolve, reject) => {
        var sqlite3 = require('sqlite3').verbose();
        var db = new sqlite3.Database(options.sqliteDatabase, (err) => {
            if (err) return reject(NICE_ERRORS.COULD_NOT_OPEN_DB(err, options.sqliteDatabase));
            db.all("select id, org, change, destination from orgChanges", [], (err, results) => {
                if (err) {
                    return reject(NICE_ERRORS.COULD_NOT_READ_ORG_CHANGES(err))
                }
                async.eachSeries(results, (row, done) => {
                    if (row.change == "delete") {
                        // just blow away that org, and remove everyone from it
                        db.run("delete from orgs where id = ?", [row.org], (err) => {
                            if (err) return done(err);
                            return db.run("delete from people2org where org = ?", [row.org], done);
                        })
                    } else if (row.change == "merge") {
                        // move everyone into the destination, and then remove the org
                        db.run("update people2org set org = ? where org = ?", [row.destination, row.org], (err) => {
                            if (err) return done(err);
                            return db.run("delete from orgs where id = ?", [row.org], done);
                        })
                    }
                }, (err) => {
                    if (err) return reject(err);
                    // remove all orgChanges rows, because they've been processed
                    db.run("delete from orgChanges", [], (err) => {
                        if (err) return reject(err);
                        db.close();
                        return resolve(options);
                    });
                })
            });
        });
    });
}

function createMongoIndexes(options) {
    let indexes = [
        {collection: "pull_request", fields: {"updated_at":1}},
        {collection: "issue_comment", fields: {"updated_at":1}},
        {collection: "issue", fields: {"updated_at":1}}
    ];
    return new Promise((resolve, reject) => {
        async.each(indexes, (index, done) => {
            options.db.collection(index.collection).ensureIndex(index.fields, {}, done);
        }, err => {
            if (err) return reject(err);
            resolve(options);
        })
    })
}

function leave(options) {
    /* And shut all our stuff down. Don't close down ghcrawler itself. */
    options.db.close();
    if (options.userConfig.debug) {
        console.log(`Dashboards generated OK in directory '${options.userConfig.output_directory}'.`);
        console.log(`Database ensured in directory '${options.userConfig.database_directory}'.`);
        var dur = (new Date()).getTime() - startupTime;
        console.log("Time taken:", moment.duration(dur).as("seconds"), "seconds");
        var timestaken = [];
        for (var widgetname in options.times) {
            timestaken.push([
                widgetname,
                options.times[widgetname].reduce(function (a, b) { return a + b; }, 0),
                options.times[widgetname].length
            ]);
        }
        timestaken.sort((a,b) => { return a[1] - b[1]; })
        console.log(timestaken.map(n => {
            return n[0] + ": " + Math.round(n[1] / 1000) + "s total in " +
                n[2] + " iterations, " + Math.round(n[1] / n[2]) + "ms/iteration";
        }).join("\n"));
    }
}

var startupTime = (new Date()).getTime();
loads.loadTemplates()
    .then(loads.readConfig)
    .then(loads.loadWidgets)
    .then(connectToDB)
    .then(checking.confirmCrawler)
    .then(checking.confirmTokens)
    .then(checking.confirmActivity)
    .then(pages.calculateCodeHash)
    .then(pages.previousGeneratedAtAndHash)
    .then(createMongoIndexes)
    .then(api)
    .then(apidb)
    .then(apiSecret)
    .then(apidbActionChanges)
    .then(getAllOrgUsers)
    .then(getMyOrgUsers)
    .then(runtime_auth.setupRuntimeAuth)
    .then(runtime_auth.copyAuthPHP)
    .then(dashboards.dashboardForEachTeam)
    .then(dashboards.dashboardForEachRepo)
    .then(dashboards.changedContributors)
    .then(dashboards.dashboardForEachContributor)
    .then(dashboards.dashboardForEachOrg)
    .then(reports)
    .then(pages.frontPage)
    .then(pages.indexPages)
    .then(pages.generatedAtAndHash)
    .then(pages.searchPage)
    .then(leave)
    .catch(e => {
        if (db) db.close();
        if (e.isNiceError) {
            console.error("Problem message:")
            console.error(e.message);
        } else {
            console.error("Internal error. (Internal errors are a bug in the code and should be reported.)");
            console.error(e.message);
            console.error(e.stack);
        }
    });
<|endoftext|>


<commit_before>var gulp = require('gulp');
var webpack = require('webpack-stream');
var merge = require('merge2');
var rev = require('gulp-rev');
var revReplace = require("gulp-rev-replace");
var clean = require('gulp-clean');
var sourcemaps = require('gulp-sourcemaps');
var argv = require('yargs').argv;
var fs = require('fs');

gulp.task('drop-cache', function(){
     return gulp.src(['./src/main/resources/public/dist'], { read: false })
		.pipe(clean());
});

gulp.task('copy-files', ['drop-cache'], () => {
    var html = gulp.src('./node_modules/entcore/src/template/**/*.html')
        .pipe(gulp.dest('./src/main/resources/public/template/entcore'));
    var bundle = gulp.src('./node_modules/entcore/bundle/*')
        .pipe(gulp.dest('./src/main/resources/public/dist/entcore'));

    return merge(html, bundle);
})

gulp.task('webpack', ['copy-files'], () => {
    return gulp.src('./src/main/resources/public')
        .pipe(webpack(require('./webpack.config.js')))
        .on('error', function handleError() {
            this.emit('end'); // Recover from errors
        })
        .pipe(gulp.dest('./src/main/resources/public/dist'));
});

gulp.task('rev', ['webpack'], () => {
    return gulp.src('./src/main/resources/public/dist/**/*.js')
        .pipe(rev())
        .pipe(gulp.dest('./src/main/resources/public/dist'))
        .pipe(rev.manifest())
        .pipe(gulp.dest('./'));
});

gulp.task('build', ['rev'], () => {
    var refs = gulp.src("./src/main/resources/view-src/**/*.html")
        .pipe(revReplace({manifest: gulp.src("./rev-manifest.json") }))
        .pipe(gulp.dest("./src/main/resources/view"));

    var copyBehaviours = gulp.src('./src/main/resources/public/dist/behaviours.js')
        .pipe(gulp.dest('./src/main/resources/public/js'));

    return merge[refs, copyBehaviours];
});

function getModName(fileContent){
    var getProp = function(prop){
        return fileContent.split(prop + '=')[1].split(/\r?\n/)[0];
    }
    return getProp('modowner') + '~' + getProp('modname') + '~' + getProp('version');
}

gulp.task('watch', () => {
    var springboard = argv.springboard;
    if(!springboard){
        springboard = '../springboard-open-ent/';
    }
    if(springboard[springboard.length - 1] !== '/'){
        springboard += '/';
    }

    gulp.watch('./src/main/resources/public/ts/**/*.ts', () => gulp.start('build'));

    fs.readFile("./gradle.properties", "utf8", function(error, content){
        var modName = getModName(content);
        gulp.watch(['./src/main/resources/public/template/**/*.html', '!./src/main/resources/public/template/entcore/*.html'], () => {
            console.log('Copying resources to ' + springboard + 'mods/' + modName);
            gulp.src('./src/main/resources/**/*')
                .pipe(gulp.dest(springboard + 'mods/' + modName));
        });

        gulp.watch('./src/main/resources/view/**/*.html', () => {
            console.log('Copying resources to ' + springboard + 'mods/' + modName);
            gulp.src('./src/main/resources/**/*')
                .pipe(gulp.dest(springboard + 'mods/' + modName));
        });
    });
});<commit_msg>[Fix] Added notify jsons support in build<commit_after>var gulp = require('gulp');
var webpack = require('webpack-stream');
var merge = require('merge2');
var rev = require('gulp-rev');
var revReplace = require("gulp-rev-replace");
var clean = require('gulp-clean');
var sourcemaps = require('gulp-sourcemaps');
var argv = require('yargs').argv;
var fs = require('fs');

gulp.task('drop-cache', function(){
     return gulp.src(['./src/main/resources/public/dist'], { read: false })
		.pipe(clean());
});

gulp.task('copy-files', ['drop-cache'], () => {
    var html = gulp.src('./node_modules/entcore/src/template/**/*.html')
        .pipe(gulp.dest('./src/main/resources/public/template/entcore'));
    var bundle = gulp.src('./node_modules/entcore/bundle/*')
        .pipe(gulp.dest('./src/main/resources/public/dist/entcore'));

    return merge(html, bundle);
})

gulp.task('webpack', ['copy-files'], () => {
    return gulp.src('./src/main/resources/public')
        .pipe(webpack(require('./webpack.config.js')))
        .on('error', function handleError() {
            this.emit('end'); // Recover from errors
        })
        .pipe(gulp.dest('./src/main/resources/public/dist'));
});

gulp.task('rev', ['webpack'], () => {
    return gulp.src('./src/main/resources/public/dist/**/*.js')
        .pipe(rev())
        .pipe(gulp.dest('./src/main/resources/public/dist'))
        .pipe(rev.manifest())
        .pipe(gulp.dest('./'));
});

gulp.task('build', ['rev'], () => {
    var refs = gulp.src("./src/main/resources/view-src/**/*.+(html|json)")
        .pipe(revReplace({manifest: gulp.src("./rev-manifest.json") }))
        .pipe(gulp.dest("./src/main/resources/view"));

    var copyBehaviours = gulp.src('./src/main/resources/public/dist/behaviours.js')
        .pipe(gulp.dest('./src/main/resources/public/js'));

    return merge[refs, copyBehaviours];
});

function getModName(fileContent){
    var getProp = function(prop){
        return fileContent.split(prop + '=')[1].split(/\r?\n/)[0];
    }
    return getProp('modowner') + '~' + getProp('modname') + '~' + getProp('version');
}

gulp.task('watch', () => {
    var springboard = argv.springboard;
    if(!springboard){
        springboard = '../springboard-open-ent/';
    }
    if(springboard[springboard.length - 1] !== '/'){
        springboard += '/';
    }

    gulp.watch('./src/main/resources/public/ts/**/*.ts', () => gulp.start('build'));

    fs.readFile("./gradle.properties", "utf8", function(error, content){
        var modName = getModName(content);
        gulp.watch(['./src/main/resources/public/template/**/*.html', '!./src/main/resources/public/template/entcore/*.html'], () => {
            console.log('Copying resources to ' + springboard + 'mods/' + modName);
            gulp.src('./src/main/resources/**/*')
                .pipe(gulp.dest(springboard + 'mods/' + modName));
        });

        gulp.watch('./src/main/resources/view/**/*.html', () => {
            console.log('Copying resources to ' + springboard + 'mods/' + modName);
            gulp.src('./src/main/resources/**/*')
                .pipe(gulp.dest(springboard + 'mods/' + modName));
        });
    });
});<|endoftext|>


<commit_before>"use strict";

(function () {
    angular
        .module("argo")
        .directive("argoCharts", argoCharts);

    argoCharts.$inject = [];
    function argoCharts() {
        var directive = {
            restrict: "E",
            scope: {
                data: "=",
                feed: "="
            },
            link: link
        };

        return directive;

        function link(scope, element) {
            var data,
                refreshChart,
                lastHistUpdate,
                lastData,
                lastClose,
                feedVolume = 0;

            scope.$watch("data", function (csv) {
                if (csv && csv.length > 0) {
                    refreshChart = drawChart(element[0], csv);

                    lastData = data && data[data.length - 1];
                    lastClose = lastData.close;
                    feedVolume = lastData.volume;
                    lastHistUpdate = getLastHistUpdate("M5");
                }
            });

            scope.$watch("feed", function (feed) {
                var tick = feed.EUR_USD,
                    nextHistUpdate = getLastHistUpdate("M5", tick),
                    midPrice;

                if (tick && data && lastHistUpdate !== nextHistUpdate) {
                    data.shift();
                    midPrice = (tick.bid + tick.ask) / 2;
                    feedVolume = 0;
                    data.push({
                        open: midPrice,
                        close: midPrice,
                        high: midPrice,
                        low: midPrice,
                        date: new Date(),
                        volume: feedVolume
                    });

                    lastHistUpdate = nextHistUpdate;
                }

                if (tick && data) {

                    if (lastData.close !== lastClose) {
                        feedVolume += 1;
                    }

                    midPrice = (tick.bid + tick.ask) / 2;

                    lastData = data && data[data.length - 1];
                    lastClose = lastData.close;
                    lastData.close = midPrice;
                    lastData.volume = feedVolume;

                    if (lastData.close > lastData.high) {
                        lastData.high = lastData.close;
                    }

                    if (lastData.close < lastData.low) {
                        lastData.low = lastData.close;
                    }

                    refreshChart();
                }

            }, true);

            function getLastHistUpdate(granularity, tick) {
                var time = tick && tick.time,
                    now = time ? new Date(time) : new Date(),
                    value;

                if (granularity === "M5") {
                    value = Math.floor(now.getMinutes() / 5);
                }

                return value;
            }

            function drawChart(el, csv) {
                var margin = {
                        top: 0,
                        right: 20,
                        bottom: 30,
                        left: 75
                    },
                    width = 960 - margin.left - margin.right,
                    height = 450 - margin.top - margin.bottom;

                var x = techan.scale.financetime()
                    .range([0, width]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var yVolume = d3.scale.linear()
                    .range([y(0), y(0.2)]);

                var ohlc = techan.plot.ohlc()
                    .xScale(x)
                    .yScale(y);

                var sma0 = techan.plot.sma()
                    .xScale(x)
                    .yScale(y);

                var sma0Calculator = techan.indicator.sma()
                    .period(10);

                var sma1 = techan.plot.sma()
                    .xScale(x)
                    .yScale(y);

                var sma1Calculator = techan.indicator.sma()
                    .period(20);

                var volume = techan.plot.volume()
                    .accessor(ohlc.accessor())
                    .xScale(x)
                    .yScale(yVolume);

                var xAxis = d3.svg.axis()
                    .scale(x)
                    .orient("bottom");

                var yAxis = d3.svg.axis()
                    .scale(y)
                    .orient("left");

                var volumeAxis = d3.svg.axis()
                    .scale(yVolume)
                    .orient("right")
                    .ticks(3)
                    .tickFormat(d3.format(",.3s"));

                var timeAnnotation = techan.plot.axisannotation()
                    .axis(xAxis)
                    .format(d3.time.format("%Y-%m-%d %H:%M"))
                    .width(80)
                    .translate([0, height]);

                var ohlcAnnotation = techan.plot.axisannotation()
                    .axis(yAxis)
                    .format(d3.format(",.4fs"));

                var volumeAnnotation = techan.plot.axisannotation()
                    .axis(volumeAxis)
                    .width(35);

                var crosshair = techan.plot.crosshair()
                    .xScale(x)
                    .yScale(y)
                    .xAnnotation(timeAnnotation)
                    .yAnnotation([ohlcAnnotation, volumeAnnotation]);

                var svg = d3.select(el).append("svg")
                    .attr("width", width + margin.left + margin.right)
                    .attr("height", height + margin.top + margin.bottom);

                var defs = svg.append("defs");

                svg = svg.append("g")
                    .attr("transform",
                        "translate(" + margin.left + "," + margin.top + ")");

                defs
                    .append("clipPath")
                        .attr("id", "ohlcClip")
                    .append("rect")
                        .attr("x", 0)
                        .attr("y", 0)
                        .attr("width", width)
                        .attr("height", height);

                var ohlcSelection = svg.append("g")
                    .attr("class", "ohlc")
                    .attr("transform", "translate(0,0)");

                ohlcSelection.append("g")
                    .attr("class", "volume")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "candlestick")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "indicator sma ma-0")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "indicator sma ma-1")
                    .attr("clip-path", "url(#ohlcClip)");

                svg.append("g")
                    .attr("class", "x axis")
                    .attr("transform", "translate(0," + height + ")");

                svg
                    .append("g")
                        .attr("class", "y axis")
                    .append("text")
                        .attr("transform", "rotate(-90)")
                        .attr("y", 6)
                        .attr("dy", ".71em")
                        .style("text-anchor", "end")
                        .text("Price (EUR_USD / M5)");

                svg.append("g")
                    .attr("class", "volume axis");

                svg.append("g")
                    .attr("class", "crosshair ohlc");

                data = d3.csv.parse(csv).map(function (d) {
                    return {
                        date: new Date(d.Date),
                        open: +d.Open,
                        high: +d.High,
                        low: +d.Low,
                        close: +d.Close,
                        volume: +d.Volume
                    };
                });

                svg.select("g.candlestick").datum(data);
                svg.select("g.sma.ma-0").datum(sma0Calculator(data));
                svg.select("g.sma.ma-1").datum(sma1Calculator(data));
                svg.select("g.volume").datum(data);

                redraw();

                function refreshIndicator(selection, indicator, data2) {
                    var datum = selection.datum();

                    // Some trickery to remove old and insert new without
                    // changing array reference, so no need to update __data__
                    // in the DOM
                    datum.splice.apply(datum, [0, datum.length].concat(data2));
                    selection.call(indicator);
                }

                function redraw() {
                    var accessor = ohlc.accessor();

                    x.domain(data.map(accessor.d));
                    x.zoomable().domain([data.length - 130, data.length]);

                    y.domain(techan.scale.plot.ohlc(
                        data.slice(data.length - 130, data.length)).domain());
                    yVolume.domain(techan.scale.plot.volume(
                        data.slice(data.length - 130, data.length)).domain());

                    svg.select("g.x.axis").call(xAxis);
                    svg.select("g.y.axis").call(yAxis);
                    svg.select("g.volume.axis").call(volumeAxis);

                    svg.select("g.candlestick").call(ohlc);
                    // Recalculate indicators and update the SAME array and
                    // redraw moving average
                    refreshIndicator(svg.select("g.sma.ma-0"), sma0,
                        sma0Calculator(data));
                    refreshIndicator(svg.select("g.sma.ma-1"), sma1,
                        sma1Calculator(data));

                    svg.select("g.volume").call(volume);

                    svg.select("g.crosshair.ohlc").call(crosshair);
                }

                return redraw;
            }

        }
    }

}());
<commit_msg>Comment x axis in the chart as workaround.<commit_after>"use strict";

(function () {
    angular
        .module("argo")
        .directive("argoCharts", argoCharts);

    argoCharts.$inject = [];
    function argoCharts() {
        var directive = {
            restrict: "E",
            scope: {
                data: "=",
                feed: "="
            },
            link: link
        };

        return directive;

        function link(scope, element) {
            var data,
                refreshChart,
                lastHistUpdate,
                lastData,
                lastClose,
                feedVolume = 0;

            scope.$watch("data", function (csv) {
                if (csv && csv.length > 0) {
                    refreshChart = drawChart(element[0], csv);

                    lastData = data && data[data.length - 1];
                    lastClose = lastData.close;
                    feedVolume = lastData.volume;
                    lastHistUpdate = getLastHistUpdate("M5");
                }
            });

            scope.$watch("feed", function (feed) {
                var tick = feed.EUR_USD,
                    nextHistUpdate = getLastHistUpdate("M5", tick),
                    midPrice;

                if (tick && data && lastHistUpdate !== nextHistUpdate) {
                    data.shift();
                    midPrice = (tick.bid + tick.ask) / 2;
                    feedVolume = 0;
                    data.push({
                        open: midPrice,
                        close: midPrice,
                        high: midPrice,
                        low: midPrice,
                        date: new Date(),
                        volume: feedVolume
                    });

                    lastHistUpdate = nextHistUpdate;
                }

                if (tick && data) {

                    if (lastData.close !== lastClose) {
                        feedVolume += 1;
                    }

                    midPrice = (tick.bid + tick.ask) / 2;

                    lastData = data && data[data.length - 1];
                    lastClose = lastData.close;
                    lastData.close = midPrice;
                    lastData.volume = feedVolume;

                    if (lastData.close > lastData.high) {
                        lastData.high = lastData.close;
                    }

                    if (lastData.close < lastData.low) {
                        lastData.low = lastData.close;
                    }

                    refreshChart();
                }

            }, true);

            function getLastHistUpdate(granularity, tick) {
                var time = tick && tick.time,
                    now = time ? new Date(time) : new Date(),
                    value;

                if (granularity === "M5") {
                    value = Math.floor(now.getMinutes() / 5);
                }

                return value;
            }

            function drawChart(el, csv) {
                var margin = {
                        top: 0,
                        right: 20,
                        bottom: 30,
                        left: 75
                    },
                    width = 960 - margin.left - margin.right,
                    height = 450 - margin.top - margin.bottom;

                var x = techan.scale.financetime()
                    .range([0, width]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var yVolume = d3.scale.linear()
                    .range([y(0), y(0.2)]);

                var ohlc = techan.plot.ohlc()
                    .xScale(x)
                    .yScale(y);

                var sma0 = techan.plot.sma()
                    .xScale(x)
                    .yScale(y);

                var sma0Calculator = techan.indicator.sma()
                    .period(10);

                var sma1 = techan.plot.sma()
                    .xScale(x)
                    .yScale(y);

                var sma1Calculator = techan.indicator.sma()
                    .period(20);

                var volume = techan.plot.volume()
                    .accessor(ohlc.accessor())
                    .xScale(x)
                    .yScale(yVolume);

                var xAxis = d3.svg.axis()
                    .scale(x)
                    .orient("bottom");

                var yAxis = d3.svg.axis()
                    .scale(y)
                    .orient("left");

                var volumeAxis = d3.svg.axis()
                    .scale(yVolume)
                    .orient("right")
                    .ticks(3)
                    .tickFormat(d3.format(",.3s"));

                var timeAnnotation = techan.plot.axisannotation()
                    .axis(xAxis)
                    .format(d3.time.format("%Y-%m-%d %H:%M"))
                    .width(80)
                    .translate([0, height]);

                var ohlcAnnotation = techan.plot.axisannotation()
                    .axis(yAxis)
                    .format(d3.format(",.4fs"));

                var volumeAnnotation = techan.plot.axisannotation()
                    .axis(volumeAxis)
                    .width(35);

                var crosshair = techan.plot.crosshair()
                    .xScale(x)
                    .yScale(y)
                    .xAnnotation(timeAnnotation)
                    .yAnnotation([ohlcAnnotation, volumeAnnotation]);

                var svg = d3.select(el).append("svg")
                    .attr("width", width + margin.left + margin.right)
                    .attr("height", height + margin.top + margin.bottom);

                var defs = svg.append("defs");

                svg = svg.append("g")
                    .attr("transform",
                        "translate(" + margin.left + "," + margin.top + ")");

                defs
                    .append("clipPath")
                        .attr("id", "ohlcClip")
                    .append("rect")
                        .attr("x", 0)
                        .attr("y", 0)
                        .attr("width", width)
                        .attr("height", height);

                var ohlcSelection = svg.append("g")
                    .attr("class", "ohlc")
                    .attr("transform", "translate(0,0)");

                ohlcSelection.append("g")
                    .attr("class", "volume")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "candlestick")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "indicator sma ma-0")
                    .attr("clip-path", "url(#ohlcClip)");

                ohlcSelection.append("g")
                    .attr("class", "indicator sma ma-1")
                    .attr("clip-path", "url(#ohlcClip)");

                svg.append("g")
                    .attr("class", "x axis")
                    .attr("transform", "translate(0," + height + ")");

                svg
                    .append("g")
                        .attr("class", "y axis")
                    .append("text")
                        .attr("transform", "rotate(-90)")
                        .attr("y", 6)
                        .attr("dy", ".71em")
                        .style("text-anchor", "end")
                        .text("Price (EUR_USD / M5)");

                svg.append("g")
                    .attr("class", "volume axis");

                svg.append("g")
                    .attr("class", "crosshair ohlc");

                data = d3.csv.parse(csv).map(function (d) {
                    return {
                        date: new Date(d.Date),
                        open: +d.Open,
                        high: +d.High,
                        low: +d.Low,
                        close: +d.Close,
                        volume: +d.Volume
                    };
                });

                svg.select("g.candlestick").datum(data);
                svg.select("g.sma.ma-0").datum(sma0Calculator(data));
                svg.select("g.sma.ma-1").datum(sma1Calculator(data));
                svg.select("g.volume").datum(data);

                redraw();

                function refreshIndicator(selection, indicator, data2) {
                    var datum = selection.datum();

                    // Some trickery to remove old and insert new without
                    // changing array reference, so no need to update __data__
                    // in the DOM
                    datum.splice.apply(datum, [0, datum.length].concat(data2));
                    selection.call(indicator);
                }

                function redraw() {
                    var accessor = ohlc.accessor();

                    x.domain(data.map(accessor.d));
                    x.zoomable().domain([data.length - 130, data.length]);

                    y.domain(techan.scale.plot.ohlc(
                        data.slice(data.length - 130, data.length)).domain());
                    yVolume.domain(techan.scale.plot.volume(
                        data.slice(data.length - 130, data.length)).domain());

                    // FIXME
                    // svg.select("g.x.axis").call(xAxis);
                    svg.select("g.y.axis").call(yAxis);
                    svg.select("g.volume.axis").call(volumeAxis);

                    svg.select("g.candlestick").call(ohlc);
                    // Recalculate indicators and update the SAME array and
                    // redraw moving average
                    refreshIndicator(svg.select("g.sma.ma-0"), sma0,
                        sma0Calculator(data));
                    refreshIndicator(svg.select("g.sma.ma-1"), sma1,
                        sma1Calculator(data));

                    svg.select("g.volume").call(volume);

                    svg.select("g.crosshair.ohlc").call(crosshair);
                }

                return redraw;
            }

        }
    }

}());
<|endoftext|>


<commit_before>(function($) {
    // Backbone.sync
    // -------------

    // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
    var methodMap = {
      'create': 'POST',
      'update': 'PUT',
      'patch':  'PATCH',
      'delete': 'DELETE',
      'read':   'GET'
    };

    var getUrl = function(object) {
      if (!(object && object.url)) return null;
      return _.isFunction(object.url) ? object.url() : object.url;
    };

    var urlError = function() {
      throw new Error("A 'url' property or function must be specified");
    };

    // Override this function to change the manner in which Backbone persists
    // models to the server. You will be passed the type of request, and the
    // model in question. By default, makes a RESTful Ajax request
    // to the model's `url()`. Some possible customizations could be:
    //
    // * Use `setTimeout` to batch rapid-fire updates into a single request.
    // * Send up the models as XML instead of JSON.
    // * Persist models via WebSockets instead of Ajax.
    //
    // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
    // as `POST`, with a `_method` parameter containing the true HTTP method,
    // as well as all requests with the body as `application/x-www-form-urlencoded`
    // instead of `application/json` with the model in a param named `model`.
    // Useful when interfacing with server-side languages like **PHP** that make
    // it difficult to read the body of `PUT` requests.
    Backbone.sync = function(method, model, options) {
      var type = methodMap[method];

      // Default options, unless specified.
      _.defaults(options || (options = {}), {
        emulateHTTP: Backbone.emulateHTTP,
        emulateJSON: Backbone.emulateJSON
      });

      // Default JSON-request options.
      var params = {
          type: type,
          dataType: 'json',
          beforeSend: function( xhr ) {
              var token = $('meta[name="csrf-token"]').attr('content');
              if (token) xhr.setRequestHeader('X-CSRF-Token', token);
          }
      };

      // Ensure that we have a URL.
      if (!options.url) {
        params.url = _.result(model, 'url') || urlError();
      }

      // Ensure that we have the appropriate request data.
      if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
        params.contentType = 'application/json';
        params.data = {};
        params.data[_.result(model, 'paramRoot')] = model.toJSON(options);
        params.data = JSON.stringify(params.data);
      }

      // For older servers, emulate JSON by encoding the request into an HTML-form.
      if (options.emulateJSON) {
        params.contentType = 'application/x-www-form-urlencoded';
        params.data = params.data ? {model: params.data} : {};
      }

      // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
      // And an `X-HTTP-Method-Override` header.
      if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
        params.type = 'POST';
        if (options.emulateJSON) params.data._method = type;
        var beforeSend = options.beforeSend;
        options.beforeSend = function(xhr) {
          xhr.setRequestHeader('X-HTTP-Method-Override', type);
          if (beforeSend) return beforeSend.apply(this, arguments);
        };
      }

      // Don't process data on a non-GET request.
      // if (params.type !== 'GET' && !options.emulateJSON) {
      //   params.processData = false;
      // }

      var success = options.success;
      options.success = function(resp) {
        if (success) success(model, resp, options);
        model.trigger('sync', model, resp, options);
      };

      var error = options.error;
      var invalid = options.invalid;
      options.error = function(xhr) {
          if(xhr.status >= 400 && xhr.status <= 400) {
              var resp = JSON.parse(xhr.responseText);
              if (invalid) invalid(model, resp, options);
              model.trigger('invalid', model, resp, options);
          } else {
              if (error) error(model, xhr, options);
              model.trigger('error', model, xhr, options);
          }
      };

      // Make the request, allowing the user to override any Ajax options.
      var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
      model.trigger('request', model, xhr, options);
      return xhr;
    };

    Backbone.Model.prototype.updateAttribute = function (key, value){
        this.set(key, value);
        data = {};
        data[key] = value;
        this.updateAttributes(data);
    };

    Backbone.Model.prototype.updateAttributes = function (data){
        this.set(data);
        var scoped_data = {};
        scoped_data[_.result(this, 'paramRoot')] = data;
        this.sync('update', this, { data: scoped_data });
    };

})(jQuery);
<commit_msg>silenced the lint<commit_after>(function($) {
    // Backbone.sync
    // -------------

    // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
    var methodMap = {
      'create': 'POST',
      'update': 'PUT',
      'patch':  'PATCH',
      'delete': 'DELETE',
      'read':   'GET'
    };

    var getUrl = function(object) {
      if (!(object && object.url)) return null;
      return _.isFunction(object.url) ? object.url() : object.url;
    };

    var urlError = function() {
      throw new Error("A 'url' property or function must be specified");
    };

    // Override this function to change the manner in which Backbone persists
    // models to the server. You will be passed the type of request, and the
    // model in question. By default, makes a RESTful Ajax request
    // to the model's `url()`. Some possible customizations could be:
    //
    // * Use `setTimeout` to batch rapid-fire updates into a single request.
    // * Send up the models as XML instead of JSON.
    // * Persist models via WebSockets instead of Ajax.
    //
    // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
    // as `POST`, with a `_method` parameter containing the true HTTP method,
    // as well as all requests with the body as `application/x-www-form-urlencoded`
    // instead of `application/json` with the model in a param named `model`.
    // Useful when interfacing with server-side languages like **PHP** that make
    // it difficult to read the body of `PUT` requests.
    Backbone.sync = function(method, model, options) {
      var type = methodMap[method];

      // Default options, unless specified.
      _.defaults(options || (options = {}), {
        emulateHTTP: Backbone.emulateHTTP,
        emulateJSON: Backbone.emulateJSON
      });

      // Default JSON-request options.
      var params = {
          type: type,
          dataType: 'json',
          beforeSend: function( xhr ) {
              var token = $('meta[name="csrf-token"]').attr('content');
              if (token) xhr.setRequestHeader('X-CSRF-Token', token);
          }
      };

      // Ensure that we have a URL.
      if (!options.url) {
        params.url = _.result(model, 'url') || urlError();
      }

      // Ensure that we have the appropriate request data.
      if (options.data === null && model && (method === 'create' || method === 'update' || method === 'patch')) {
        params.contentType = 'application/json';
        params.data = {};
        params.data[_.result(model, 'paramRoot')] = model.toJSON(options);
        params.data = JSON.stringify(params.data);
      }

      // For older servers, emulate JSON by encoding the request into an HTML-form.
      if (options.emulateJSON) {
        params.contentType = 'application/x-www-form-urlencoded';
        params.data = params.data ? {model: params.data} : {};
      }

      // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
      // And an `X-HTTP-Method-Override` header.
      if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
        params.type = 'POST';
        if (options.emulateJSON) params.data._method = type;
        var beforeSend = options.beforeSend;
        options.beforeSend = function(xhr) {
          xhr.setRequestHeader('X-HTTP-Method-Override', type);
          if (beforeSend) return beforeSend.apply(this, arguments);
        };
      }

      // Don't process data on a non-GET request.
      // if (params.type !== 'GET' && !options.emulateJSON) {
      //   params.processData = false;
      // }

      var success = options.success;
      options.success = function(resp) {
        if (success) success(model, resp, options);
        model.trigger('sync', model, resp, options);
      };

      var error = options.error;
      var invalid = options.invalid;
      options.error = function(xhr) {
          if(xhr.status >= 400 && xhr.status <= 400) {
              var resp = JSON.parse(xhr.responseText);
              if (invalid) invalid(model, resp, options);
              model.trigger('invalid', model, resp, options);
          } else {
              if (error) error(model, xhr, options);
              model.trigger('error', model, xhr, options);
          }
      };

      // Make the request, allowing the user to override any Ajax options.
      var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
      model.trigger('request', model, xhr, options);
      return xhr;
    };

    Backbone.Model.prototype.updateAttribute = function (key, value){
        this.set(key, value);
        data = {};
        data[key] = value;
        this.updateAttributes(data);
    };

    Backbone.Model.prototype.updateAttributes = function (data){
        this.set(data);
        var scoped_data = {};
        scoped_data[_.result(this, 'paramRoot')] = data;
        this.sync('update', this, { data: scoped_data });
    };

})(jQuery);
<|endoftext|>