<commit_before>'use strict';

var addScript = null;
var serverUrl = "api/";

(function() {

var server = new ScriptServer(serverUrl);

var form = $('<form><input id="scriptId" type="text"></form>');
form.submit(function(e) {
  var scriptId = $('#scriptId').attr('value');
  if (!isNaN(scriptId)) {
    console.log(scriptId);
    addScript(parseInt(scriptId));
  }
  return false;
});

$('#top').append(form);

var w = 960;
var h = 400;

var canvas = d3.select('#top').append('div').append('svg:svg')
    .attr('width', w)
    .attr('height', h)
    .attr('id', 'graphSvg');

$('#top').append('
');

var scripts = [];
var nodes = [];
var eventLinks = [];
var causalLinks = [];

var d3ScriptIds = canvas.selectAll('.scriptId').data(scripts);
var d3Nodes = canvas.selectAll('.node').data(nodes);
var d3EventLinks = canvas.selectAll('.eventlink').data(eventLinks);
var d3CausalLinks = canvas.selectAll('.causallink').data(causalLinks);

var getScriptIndex = function _scriptIndex(scriptId) {
  var scriptIndex = -1;
  for (var i = 0, ii = scripts.length; i < ii; ++i) {
    if (scripts[i].id == scriptId || scripts[i].name == scriptId) {
      return i;
    }
  }
  return null;
}

addScript = function _addScript(scriptId) {
  if (typeof getScriptIndex(scriptId) == 'number')
    return;

  scripts.push({id: scriptId});

  server.getScript(scriptId, function(item) {
    var scriptIndex = getScriptIndex(scriptId);

    if (item == null) {
      scripts.splice(scriptIndex, 1);
      return;
    }

    scripts[scriptIndex] = item;
    var id = item.id;
    var events = item.events;

    var newNodes = [];
    var idToNode = {};

    for (var i = 0, ii = events.length; i < ii; ++i) {
      var e = events[i];
      var o = {
        scriptIndex: scriptIndex,
        eventIndex: i,
        maxIndex: ii,
        event: e,
        selected: false,
        x: 0,
        y: 0
      }
      idToNode[e.meta.id] = o;
      newNodes.push(o);

      var waitEvent = e.timing.waitEvent;
      if (waitEvent) {
        causalLinks.push({
          source: o,
          target: idToNode[waitEvent]
        });
      }
    }

    for (var i = 0, ii = nodes.length; i < ii; ++i) {
      for (var j = 0, jj = newNodes.length; j < jj; ++j) {
        var oldNode = nodes[i].event;
        var newNode = newNodes[j].event;
        if (oldNode.type == newNode.type) {
          var type = oldNode.type;
          if (eventEquality[type](oldNode, newNode)) {
            eventLinks.push({node1: nodes[i], node2: newNodes[j]});
          }
        }
      }
    }
    nodes = nodes.concat(newNodes);
    updateD3();
  });
}

function updateD3() {

  d3ScriptIds = canvas.selectAll('.scriptId').data(scripts);

  d3ScriptIds.exit().transition().remove();

  d3ScriptIds.transition()
    .attr('x', function(d) { return 0 })
    .attr('y', function(d, i, t) {
      return Math.round((i + 1) * (h / (scripts.length + 1))) - 5;
    });

  d3ScriptIds.enter().append('text').attr('class', 'scriptId')
    .text(function(d) { return '(' + d.id + ')' + d.name; })
    .attr('x', function(d) { return 0 })
    .attr('y', function(d, i, t) {
      return Math.round((i + 1) * (h / (scripts.length + 1))) - 5;
    })
    .attr('font-family', 'Arial')

  var numScripts = scripts.length;
  for (var i = 0, ii = nodes.length; i < ii; ++i) {
    var n = nodes[i];
    n.x = Math.round((n.eventIndex + 1) * (w / (n.maxIndex + 1)));
    n.y = Math.round((n.scriptIndex + 1) * (h / (numScripts + 1)));
  }

  d3Nodes = canvas.selectAll('.node').data(nodes);

  d3Nodes.exit().transition().remove();

  d3Nodes.select('circle').transition()
    .attr('cx', function(d) { return d.x; })
    .attr('cy', function(d) { return d.y; });

  var node = d3Nodes.enter().append('g').attr('class', 'node');
  node.append('svg:circle')
    .attr('cx', function(d) { return d.x; })
    .attr('cy', function(d) { return d.y; })
    .attr('r', 3)
    .attr('fill', function(d) {
      if (d.event.timing.waitTime === 0) {
        return 'red';
      }
      return 'grey';
    })
    .on('mouseover', function(d) {
      d3.select(this).transition()
          .attr('r', 6);
      showEvent(d.event);
    })
    .on('mouseout', function(d) {
      d3.select(this).transition()
          .attr('r', 3);
    });

  d3CausalLinks = canvas.selectAll('.causalLinks').data(causalLinks);

  d3CausalLinks.exit().transition().remove();

  d3CausalLinks.enter().append('path').attr('class', 'causalLinks')
    .attr('d', function(d) {
      var sourceX = d.source.x;
      var sourceY = d.source.y;
      var targetX = d.target.x;
      var targetY = d.target.y;

      var cX = (sourceX * .9 + targetX * .1);
      var cY = (sourceY * .9 + targetY * .1);

      var dX = (sourceX * .1 + targetX * .9);
      var dY = (sourceY * .1 + targetY * .9);

      cY -= 30;
      dY -= 30;

      var path = ['M', d.source.x, d.source.y, 'C', sourceX, cY, targetX, dY, targetX, targetY].join(' ');
      return path;
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

  d3CausalLinks.transition()
    .attr('d', function(d) {
      var sourceX = d.source.x;
      var sourceY = d.source.y;
      var targetX = d.target.x;
      var targetY = d.target.y;

      var cX = (sourceX * .9 + targetX * .1);
      var cY = (sourceY * .9 + targetY * .1);

      var dX = (sourceX * .1 + targetX * .9);
      var dY = (sourceY * .1 + targetY * .9);

      cY -= 30;
      dY -= 30;

      var path = ['M', d.source.x, d.source.y, 'C', sourceX, cY, targetX, dY, targetX, targetY].join(' ');
      return path;
    });

  d3EventLinks = canvas.selectAll('.eventLinks').data(eventLinks);

  d3EventLinks.exit().transition().remove();

  d3EventLinks.enter().append('line').attr('class', 'eventLinks')
    .attr('x1', function(d) { return d.node1.x; })
    .attr('y1', function(d) { return d.node1.y; })
    .attr('x2', function(d) { return d.node2.x; })
    .attr('y2', function(d) { return d.node2.y; })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

  d3EventLinks.transition()
    .attr('x1', function(d) { return d.node1.x; })
    .attr('y1', function(d) { return d.node1.y; })
    .attr('x2', function(d) { return d.node2.x; })
    .attr('y2', function(d) { return d.node2.y; })
}

function showEvent(orig) {
   function flatten(data, prefix, level) {
    if (level == 0 || typeof data != 'object')
      return [{name: prefix, value: JSON.stringify(data)}];

    var collect = [];
    for (var key in data) {
      collect = collect.concat(flatten(data[key], prefix + '.' + key, level - 1));
    }
    return collect;
  }

  var e = flatten(orig, 'e', 2);

  var newDiv = $("

");
  newDiv.append("type:" + "" + orig.type + "
");
  e = e.sort(function(a,b) {
    a = a.name.toLowerCase();
    b = b.name.toLowerCase();
    if (a  b)
      return 1;
    else
      return 0;
  });

  for (var i = 0, ii = e.length; i < ii; ++i) {
    var param = e[i];
    var name = param.name;
    var value = param.value;

    var newSpan = $("" + name + ":" + "");
    newSpan.addClass(name);

    var cleansedValue = $('').text(value);
    if (cleansedValue.html().length > 500) {
      cleansedValue.css('display', 'none');

      (function() {
        newSpan.click(function(eventObject) {
          cleansedValue.toggle();
        });
      })();
    }
    newSpan.append(cleansedValue);
    newSpan.append('
');

    newDiv.append(newSpan);
  }
  $('#eventDiv').empty().append(newDiv);
}

var eventEquality = {
  dom: function domEqual(e1, e2) {
    return e1.target.xpath == e2.target.xpath &&
      e1.frame.URL == e2.frame.URL &&
      e1.data.type == e2.data.type;
  },
  start: function startEqual(e1, e2) {
    return e1.data.method == e2.data.method &&
      e1.data.url == e2.data.url &&
      e1.data.type == e2.data.type;
  },
  completed: function completedEqual(e1, e2) {
    return e1.data.method == e2.data.method &&
      e1.data.url == e2.data.url &&
      e1.data.type == e2.data.type;
  },
  capture: function captureEqual(e1, e2) {
    return e1.target.xpath == e2.target.xpath &&
      e1.frame.URL == e2.frame.URL;
  }
};

})();

function addScriptGroup(id) {
  var req = $.ajax({
    error: function(jqXHR, textStatus, errorThrown) {
    },
    success: function(data, textStatus, jqXHR) {
      data = data.filter(function(script) {
        return script.name.indexOf(id) == 0;
      });
      for (var i = 0, ii = data.length; i < ii; ++i) {
        addScript(data[i].id);
      }
    },
    complete: function(jqXHR, textSataus) {
    },
    contentType: 'application/json',
    dataType: 'json',
    processData: false,
    type: 'GET',
    timeout: 15000,
    url: serverUrl + 'script/'
  });
}

// var scripts = [1, 31];
// scripts.forEach(function(id) {
//   addScript(id);
// });
<commit_msg>finished visualization<commit_after>'use strict';

var addScript = null;
var serverUrl = "api/";

(function() {

var server = new ScriptServer(serverUrl);

var form = $('<form><input id="scriptId" type="text"></form>');
form.submit(function(e) {
  var scriptId = $('#scriptId').attr('value');
  if (!isNaN(scriptId)) {
    console.log(scriptId);
    addScript(parseInt(scriptId));
  }
  return false;
});

$('#top').append(form);

var w = 960;
var h = 100;

var canvas = d3.select('#top').append('div').append('svg:svg')
    .attr('width', w)
    .attr('height', h)
    .attr('id', 'graphSvg');

$('#top').append('
');

var scripts = [];
var nodes = [];
var eventLinks = [];
var causalLinks = [];

var d3ScriptIds = canvas.selectAll('.scriptId').data(scripts);
var d3Nodes = canvas.selectAll('.node').data(nodes);
var d3EventLinks = canvas.selectAll('.eventlink').data(eventLinks);
var d3CausalLinks = canvas.selectAll('.causallink').data(causalLinks);

var getScriptIndex = function _scriptIndex(scriptId) {
  var scriptIndex = -1;
  for (var i = 0, ii = scripts.length; i < ii; ++i) {
    if (scripts[i].id == scriptId || scripts[i].name == scriptId) {
      return i;
    }
  }
  return null;
}

addScript = function _addScript(scriptId) {
  if (typeof getScriptIndex(scriptId) == 'number')
    return;

  scripts.push({id: scriptId});

  server.getScript(scriptId, function(item) {
    var scriptIndex = getScriptIndex(scriptId);

    if (item == null) {
      scripts.splice(scriptIndex, 1);
      return;
    }

    scripts[scriptIndex] = item;
    var id = item.id;
    var events = item.events;

    var newNodes = [];
    var idToNode = {};

    for (var i = 0, ii = events.length; i < ii; ++i) {
      var e = events[i];
      var o = {
        scriptIndex: scriptIndex,
        eventIndex: i,
        maxIndex: ii,
        event: e,
        selected: false,
        relatedEvents: [],
        x: 0,
        y: 0
      }
      idToNode[e.meta.id] = o;
      newNodes.push(o);

      var waitEvent = e.timing.waitEvent;
      if (waitEvent) {
        causalLinks.push({
          source: o,
          target: idToNode[waitEvent]
        });
      }
    }

    for (var i = 0, ii = nodes.length; i < ii; ++i) {
      for (var j = 0, jj = newNodes.length; j < jj; ++j) {
        var oldNode = nodes[i];
        var newNode = newNodes[j];
        if (oldNode.event.type == newNode.event.type) {
          var type = oldNode.event.type;
          if (eventEquality[type](oldNode.event, newNode.event)) {
            oldNode.relatedEvents.push(newNode);
            newNode.relatedEvents.push(oldNode);
          }
        }
      }
    }
    nodes = nodes.concat(newNodes);
    updateD3();
  });
}

function updateD3() {

  if (h / (scripts.length + 1) < 60) {
    h = (scripts.length + 1) * 60;
    canvas.attr('height', h);
  }

  d3ScriptIds = canvas.selectAll('.scriptId').data(scripts);

  d3ScriptIds.exit().transition().remove();

  d3ScriptIds.transition()
    .attr('transform', function(d, i) {
      return 'translate(0, ' +
        Math.round((i + 1) * (h / (scripts.length + 1))) + ')';
    });

  d3ScriptIds.select('text')
    .text(function(d) {
      return '(' + d.id + ')' + d.name; })
    .select(function() {
      return this.nextSibling; })
    .text(function(d) {
      return d.notes; });

  d3ScriptIds.enter().append('g').attr('class', 'scriptId')
    .attr('transform', function(d, i) {
      return 'translate(0, ' +
        Math.round((i + 1) * (h / (scripts.length + 1))) + ')';
    })
    .attr('font-family', 'Arial')
    .attr('font-size', '.7em')
    .append('text')
    .text(function(d) { return '(' + d.id + ')' + d.name; })
    .attr('dy', '-2.2em')
    .select(function() { return this.parentElement; })
    .append('text')
    .text(function(d) { return d.notes; })
    .attr('dy', '-1.1em');

  var numScripts = scripts.length;
  for (var i = 0, ii = nodes.length; i < ii; ++i) {
    var n = nodes[i];
    n.x = Math.round((n.eventIndex + 1) * (w / (n.maxIndex + 1)));
    n.y = Math.round((n.scriptIndex + 1) * (h / (numScripts + 1)));
  }

  d3Nodes = canvas.selectAll('.node').data(nodes);

  d3Nodes.exit().transition().remove();

  d3Nodes.select('circle').transition()
    .attr('cx', function(d) { return d.x; })
    .attr('cy', function(d) { return d.y; });

  var node = d3Nodes.enter().append('g').attr('class', 'node');
  node.append('svg:circle')
    .attr('cx', function(d) { return d.x; })
    .attr('cy', function(d) { return d.y; })
    .attr('r', 3)
    .attr('fill', function(d) {
      var type = d.event.type;
      if (type in typeColor)
        return typeColor[type];
      return typeColor.default;
    })
    .on('mouseover', function(d) {
      d3.select(this).transition()
          .attr('r', 6);
      showEvent(d.event);
    })
    .on('mouseout', function(d) {
      d3.select(this).transition()
          .attr('r', 3);
    })
    .on('click', function(d) {
      d.selected = !d.selected;

      var c = 'eventLinks-' + d.scriptIndex + '-' + d.eventIndex;
      var links = canvas.selectAll('.' + c).data(d.relatedEvents);

      if (d.selected) {
        links.enter().append('line').attr('class', c)
          .attr('x1', function(d2) { return d.x; })
          .attr('y1', function(d2) { return d.y; })
          .attr('x2', function(d2) { return d2.x; })
          .attr('y2', function(d2) { return d2.y; })
          .attr('stroke', 'black')
          .attr('stroke-width', 1)
          .attr('fill', 'none');
      } else {
        links.remove();
      }
    })
    .filter(function(d) { return d.event.timing.waitTime === 0; })
    .attr('stroke', 'grey')
    .attr('stroke-width', '2');

  d3CausalLinks = canvas.selectAll('.causalLinks').data(causalLinks);

  d3CausalLinks.exit().transition().remove();

  d3CausalLinks.enter().append('path').attr('class', 'causalLinks')
    .attr('d', function(d) {
      var sourceX = d.source.x;
      var sourceY = d.source.y;
      var targetX = d.target.x;
      var targetY = d.target.y;

      var cX = (sourceX * .9 + targetX * .1);
      var cY = (sourceY * .9 + targetY * .1);

      var dX = (sourceX * .1 + targetX * .9);
      var dY = (sourceY * .1 + targetY * .9);

      cY -= 30;
      dY -= 30;

      var path = ['M', d.source.x, d.source.y, 'C', sourceX, cY, targetX, dY, targetX, targetY].join(' ');
      return path;
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

  d3CausalLinks.transition()
    .attr('d', function(d) {
      var sourceX = d.source.x;
      var sourceY = d.source.y;
      var targetX = d.target.x;
      var targetY = d.target.y;

      var cX = (sourceX * .9 + targetX * .1);
      var cY = (sourceY * .9 + targetY * .1);

      var dX = (sourceX * .1 + targetX * .9);
      var dY = (sourceY * .1 + targetY * .9);

      cY -= 30;
      dY -= 30;

      var path = ['M', d.source.x, d.source.y, 'C', sourceX, cY, targetX, dY, targetX, targetY].join(' ');
      return path;
    });

  d3EventLinks = canvas.selectAll('.eventLinks').data(eventLinks);

  d3EventLinks.exit().transition().remove();

  d3EventLinks.enter().append('line').attr('class', 'eventLinks')
    .attr('x1', function(d) { return d.node1.x; })
    .attr('y1', function(d) { return d.node1.y; })
    .attr('x2', function(d) { return d.node2.x; })
    .attr('y2', function(d) { return d.node2.y; })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

  d3EventLinks
    .attr('x1', function(d) { return d.node1.x; })
    .attr('y1', function(d) { return d.node1.y; })
    .attr('x2', function(d) { return d.node2.x; })
    .attr('y2', function(d) { return d.node2.y; })
}

function showEvent(orig) {
   function flatten(data, prefix, level) {
    if (level == 0 || typeof data != 'object')
      return [{name: prefix, value: JSON.stringify(data)}];

    var collect = [];
    for (var key in data) {
      collect = collect.concat(flatten(data[key], prefix + '.' + key, level - 1));
    }
    return collect;
  }

  var e = flatten(orig, 'e', 2);

  var newDiv = $("

");
  newDiv.append("type:" + "" + orig.type + "
");
  e = e.sort(function(a,b) {
    a = a.name.toLowerCase();
    b = b.name.toLowerCase();
    if (a  b)
      return 1;
    else
      return 0;
  });

  for (var i = 0, ii = e.length; i < ii; ++i) {
    var param = e[i];
    var name = param.name;
    var value = param.value;

    var newSpan = $("" + name + ":" + "");
    newSpan.addClass(name);

    var cleansedValue = $('').text(value);
    if (cleansedValue.html().length > 500) {
      cleansedValue.css('display', 'none');

      (function() {
        newSpan.click(function(eventObject) {
          cleansedValue.toggle();
        });
      })();
    }
    newSpan.append(cleansedValue);
    newSpan.append('
');

    newDiv.append(newSpan);
  }
  $('#eventDiv').empty().append(newDiv);
}

var eventEquality = {
  dom: function domEqual(e1, e2) {
    return e1.target.xpath == e2.target.xpath &&
      e1.frame.URL == e2.frame.URL &&
      e1.data.type == e2.data.type;
  },
  load: function loadEqual(e1, e2) {
    return e1.frame.URL == e2.frame.URL;
  },
  start: function startEqual(e1, e2) {
    return e1.data.method == e2.data.method &&
      e1.data.url == e2.data.url &&
      e1.data.type == e2.data.type;
  },
  completed: function completedEqual(e1, e2) {
    return e1.data.method == e2.data.method &&
      e1.data.url == e2.data.url &&
      e1.data.type == e2.data.type;
  },
  capture: function captureEqual(e1, e2) {
    return e1.target.xpath == e2.target.xpath &&
      e1.frame.URL == e2.frame.URL;
  }
};

var typeColor = {
  'dom': 'blue',
  'start': 'red',
  'completed': 'orange',
  'load': 'brown',
  'default': 'black'
};

})();

function addScriptGroup(id) {
  var req = $.ajax({
    error: function(jqXHR, textStatus, errorThrown) {
    },
    success: function(data, textStatus, jqXHR) {
      data = data.filter(function(script) {
        return script.name.indexOf(id) == 0;
      });
      for (var i = 0, ii = data.length; i < ii; ++i) {
        addScript(data[i].id);
      }
    },
    complete: function(jqXHR, textSataus) {
    },
    contentType: 'application/json',
    dataType: 'json',
    processData: false,
    type: 'GET',
    timeout: 15000,
    url: serverUrl + 'script/'
  });
}

// var scripts = [1, 31];
// scripts.forEach(function(id) {
//   addScript(id);
// });
<|endoftext|>


<commit_before>const {assert} = require('./common');
const Passwords = require('../passwords');

describe('Passwords', () => {
  it('should be able to generate and compare hashes', async () => {
    const pass = 'apple';
    const passFake = 'orange';

    const passHash = await Passwords.hash(pass);
    const passHashSync = Passwords.hashSync(pass);
    const passFakeHashSync = Passwords.hashSync(passFake);

    assert(Passwords.compareSync(pass, passHash),
      'hash should compare correctly to original password');
    assert(Passwords.compareSync(pass, passHashSync),
      'hashSync should compare correctly to original password');
    assert(!Passwords.compareSync(passFake, passHash),
      'hash should not match fake password');
    assert(!Passwords.compareSync(pass, passFakeHashSync),
      'fake hash sync should not match original password');
  });
})

<commit_msg>Add test of async compare<commit_after>const {assert} = require('./common');
const Passwords = require('../passwords');

describe('Passwords', () => {
  it('should be able to generate and compare hashes', async () => {
    const pass = 'apple';
    const passFake = 'orange';

    const passHash = await Passwords.hash(pass);
    const passHashSync = Passwords.hashSync(pass);
    const passFakeHashSync = Passwords.hashSync(passFake);

    assert(Passwords.compareSync(pass, passHash),
      'hash should compare correctly to original password');
    const compareAsync = await Passwords.compare(pass, passHash);
    assert(compareAsync,
      'hash should async compare correctly to original password');
    assert(Passwords.compareSync(pass, passHashSync),
      'hashSync should compare correctly to original password');
    assert(!Passwords.compareSync(passFake, passHash),
      'hash should not match fake password');
    assert(!Passwords.compareSync(pass, passFakeHashSync),
      'fake hash sync should not match original password');
  });
})

<|endoftext|>


<commit_before>jQuery(function ($) {

    /* Init */
    var identifier = 0;
    var thisState;
    $('.tree-folder-title>.dropdown-toggle').each(function () {

        // Set element identifier
        $(this).data('uid', identifier);
        $(this).attr('data-uid', identifier);

        // Check for stored toggle state
        thisState = localStorage.getItem('freshrss-toggle-state-' + identifier);
        if (thisState) {
            if ($('.tree-folder-title>.dropdown-toggle[data-uid="' + identifier + '"]').length) {
                $('.tree-folder-title>.dropdown-toggle[data-uid="' + identifier + '"]')[0].click();
            }
        }

        identifier++;

    });

    /* Listener */
    $('#aside_feed').on('click', '.tree-folder>.tree-folder-title>a.dropdown-toggle', function () {

        $(this).children().each(function () {

            // Get element identifier
            identifier = $(this).parent().data('uid');

            if (this.alt === '▽') {

                // Check for stored toggle state
                var thisState = localStorage.getItem('freshrss-toggle-state-' + identifier);
                if (thisState) {

                    localStorage.removeItem('freshrss-toggle-state-' + identifier);

                }

                // console.log('Close');

            } else {

                localStorage.setItem('freshrss-toggle-state-' + identifier, "open");

                // console.log('Open');
            }
        });

    });

});<commit_msg>Folding method changed<commit_after>jQuery(function ($) {

    /* Init */
    var identifier = 0;
    var thisState;
    $('.tree-folder-title>.dropdown-toggle').each(function () {

        // Set element identifier
        $(this).data('uid', identifier);
        $(this).attr('data-uid', identifier);

        // Check for stored toggle state
        thisState = localStorage.getItem('freshrss-toggle-state-' + identifier);
        if (thisState) {
            if ($('.tree-folder-title>.dropdown-toggle[data-uid="' + identifier + '"]').length) {
                $(this).parent().next(".tree-folder-items").show(0, function() {
                    $(document.body).trigger("sticky_kit:recalc");
                });
            }
        }

        identifier++;

    });

    /* Listener */
    $('#aside_feed').on('click', '.tree-folder>.tree-folder-title>a.dropdown-toggle', function () {

        $(this).children().each(function () {

            // Get element identifier
            identifier = $(this).parent().data('uid');

            if (this.alt === '▽') {

                // Check for stored toggle state
                var thisState = localStorage.getItem('freshrss-toggle-state-' + identifier);
                if (thisState) {

                    localStorage.removeItem('freshrss-toggle-state-' + identifier);

                }

                // console.log('Close');

            } else {

                localStorage.setItem('freshrss-toggle-state-' + identifier, "open");

                // console.log('Open');
            }
        });

    });

});<|endoftext|>


<commit_before>;(function() {
  'use strict';

  var React;
  var ModalFormAnchor;
  if (typeof require !== 'undefined') {
    React = require('react');
    ModalFormAnchor = require('./anchor');
  } else if (typeof window !== 'undefined') {
    React = window.React;
    ModalFormAnchor = window.ZUIModalFormAnchor;
  }

  var ESC_KEY = 27;

  var UNDERLAY_STYLE = {
    left: 0,
    position: 'absolute',
    top: 0
  };

  var FORM_STYLE = {
    position: 'absolute'
  };

  function ModalFormBase() {
    React.Component.apply(this, arguments);
    this.reposition = this.reposition.bind(this);
    this.handleGlobalKeyDown = this.handleGlobalKeyDown.bind(this);
    this.handleGlobalNavigation = this.handleGlobalNavigation.bind(this);
    this.state = {
      underlayWidth: 0,
      underlayHeight: 0
    };
  }

  ModalFormBase.locationChangeEvent = 'locationchange';

  ModalFormBase.propTypes = {
    tag: React.PropTypes.node,
    required: React.PropTypes.bool,
    underlayStyle: React.PropTypes.object,
    persistAcrossLocations: React.PropTypes.bool,
    loose: React.PropTypes.bool,
    onReposition: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    onCancel: React.PropTypes.func
  };

  ModalFormBase.defaultProps = {
    tag: 'form',
    required: false,
    underlayStyle: {},
    persistAcrossLocations: false,
    loose: false,
    onReposition: Function.prototype,
    onSubmit: Function.prototype,
    onCancel: Function.prototype
  };

  ModalFormBase.prototype = Object.assign(Object.create(React.Component.prototype), {
    _originalScrollPosition: null,

    componentWillMount: function() {
      if (typeof pageXOffset !== 'undefined') {
        // Mounting a modal with `autoFocus` content scrolls to the top before it repositions.
        this._originalScrollPosition = [pageXOffset, pageYOffset];
      }
    },

    componentDidMount: function() {
      addEventListener('scroll', this.reposition);
      addEventListener('resize', this.reposition);
      addEventListener('keydown', this.handleGlobalKeyDown);
      addEventListener('hashchange', this.handleGlobalNavigation);
      addEventListener(ModalFormBase.locationChangeEvent, this.handleGlobalNavigation);
      this.reposition();
      if (this._originalScrollPosition !== null) {
        scrollTo.apply(null, this._originalScrollPosition);
      }
    },

    componentWillUnmount: function() {
      removeEventListener('scroll', this.reposition);
      removeEventListener('resize', this.reposition);
      removeEventListener('keydown', this.handleGlobalKeyDown);
      removeEventListener('hashchange', this.handleGlobalNavigation);
      removeEventListener(ModalFormBase.locationChangeEvent, this.handleGlobalNavigation);
    },

    handleGlobalKeyDown: function (event) {
      if (event.which === ESC_KEY && !this.props.required) {
        this.props.onCancel.apply(null, arguments);
      }
    },

    handleGlobalNavigation: function() {
      if (!this.props.required && !this.props.persistAcrossLocations) {
        this.props.onCancel.apply(null, arguments);
      }
    },

    getUnderlayChildren: function() {
      var action;
      var onSubmit;
      if (this.props.tag === 'form') {
        action = 'POST';
        onSubmit = this.handleFormSubmit.bind(this)
      }

      return React.createElement(this.props.tag, {
        ref: 'form',
        className: ('modal-form ' + (this.props.className || '')).trim(),
        action: action,
        style: Object.assign({}, FORM_STYLE, this.props.style),
        onSubmit: onSubmit
      }, this.props.children);
    },

    render: function() {
      if (this.props.loose) {
        return this.renderLoose();
      } else {
        return this.renderWithAnchor();
      }
    },

    renderLoose: function() {
      var underlaySize = {
        width: this.state.underlayWidth + 'px',
        height: this.state.underlayHeight + 'px'
      };
      return React.createElement.apply(React, ['div', {
        ref: 'underlay',
        className: ('modal-form-underlay ' + (this.props.className || '')).trim(),
        style: Object.assign({}, UNDERLAY_STYLE, underlaySize, this.props.underlayStyle),
        onClick: this.handleUnderlayClick.bind(this)
      }].concat(this.getUnderlayChildren()));
    },

    renderWithAnchor: function() {
      var looseRenderResult = this.renderLoose.apply(this, arguments);
      return React.createElement(ModalFormAnchor, null, looseRenderResult);
    },

    componentDidUpdate: function() {
      this.reposition();
    },

    reposition: function() {
      var formRect = this.refs.form.getBoundingClientRect();
      var formStyle = getComputedStyle(this.refs.form);
      var formRight = pageXOffset + formRect.right + parseFloat(formStyle.marginRight);
      var formBottom = pageYOffset + formRect.bottom + parseFloat(formStyle.marginBottom);
      var totalWidth = Math.max(document.documentElement.offsetWidth, formRight); // Skip `innerWidth` to avoid counting scrollbar.
      var totalHeight = Math.max(document.documentElement.offsetHeight, innerHeight, formBottom);
      if (totalWidth !== this.state.underlayWidth || totalHeight !== this.state.underlayHeight) {
        this.setState({
          underlayWidth: totalWidth,
          underlayHeight: totalHeight
        });
      }
      this.props.onReposition();
    },

    handleFormSubmit: function(event) {
      event.preventDefault();
      this.props.onSubmit.apply(null, arguments);
    },

    handleUnderlayClick: function(event) {
      if (!this.props.required && event.target === this.refs.underlay) {
        this.props.onCancel.apply(null, arguments);
      }
    }
  });

  if (typeof module !== 'undefined') {
    module.exports = ModalFormBase;
  } else if (typeof window !== 'undefined') {
    window.ZUIModalFormBase = ModalFormBase;
  }
}());
<commit_msg>Method, not action<commit_after>;(function() {
  'use strict';

  var React;
  var ModalFormAnchor;
  if (typeof require !== 'undefined') {
    React = require('react');
    ModalFormAnchor = require('./anchor');
  } else if (typeof window !== 'undefined') {
    React = window.React;
    ModalFormAnchor = window.ZUIModalFormAnchor;
  }

  var ESC_KEY = 27;

  var UNDERLAY_STYLE = {
    left: 0,
    position: 'absolute',
    top: 0
  };

  var FORM_STYLE = {
    position: 'absolute'
  };

  function ModalFormBase() {
    React.Component.apply(this, arguments);
    this.reposition = this.reposition.bind(this);
    this.handleGlobalKeyDown = this.handleGlobalKeyDown.bind(this);
    this.handleGlobalNavigation = this.handleGlobalNavigation.bind(this);
    this.state = {
      underlayWidth: 0,
      underlayHeight: 0
    };
  }

  ModalFormBase.locationChangeEvent = 'locationchange';

  ModalFormBase.propTypes = {
    tag: React.PropTypes.node,
    required: React.PropTypes.bool,
    underlayStyle: React.PropTypes.object,
    persistAcrossLocations: React.PropTypes.bool,
    loose: React.PropTypes.bool,
    onReposition: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    onCancel: React.PropTypes.func
  };

  ModalFormBase.defaultProps = {
    tag: 'form',
    required: false,
    underlayStyle: {},
    persistAcrossLocations: false,
    loose: false,
    onReposition: Function.prototype,
    onSubmit: Function.prototype,
    onCancel: Function.prototype
  };

  ModalFormBase.prototype = Object.assign(Object.create(React.Component.prototype), {
    _originalScrollPosition: null,

    componentWillMount: function() {
      if (typeof pageXOffset !== 'undefined') {
        // Mounting a modal with `autoFocus` content scrolls to the top before it repositions.
        this._originalScrollPosition = [pageXOffset, pageYOffset];
      }
    },

    componentDidMount: function() {
      addEventListener('scroll', this.reposition);
      addEventListener('resize', this.reposition);
      addEventListener('keydown', this.handleGlobalKeyDown);
      addEventListener('hashchange', this.handleGlobalNavigation);
      addEventListener(ModalFormBase.locationChangeEvent, this.handleGlobalNavigation);
      this.reposition();
      if (this._originalScrollPosition !== null) {
        scrollTo.apply(null, this._originalScrollPosition);
      }
    },

    componentWillUnmount: function() {
      removeEventListener('scroll', this.reposition);
      removeEventListener('resize', this.reposition);
      removeEventListener('keydown', this.handleGlobalKeyDown);
      removeEventListener('hashchange', this.handleGlobalNavigation);
      removeEventListener(ModalFormBase.locationChangeEvent, this.handleGlobalNavigation);
    },

    handleGlobalKeyDown: function (event) {
      if (event.which === ESC_KEY && !this.props.required) {
        this.props.onCancel.apply(null, arguments);
      }
    },

    handleGlobalNavigation: function() {
      if (!this.props.required && !this.props.persistAcrossLocations) {
        this.props.onCancel.apply(null, arguments);
      }
    },

    getUnderlayChildren: function() {
      var method;
      var onSubmit;
      if (this.props.tag === 'form') {
        method = 'POST';
        onSubmit = this.handleFormSubmit.bind(this)
      }

      return React.createElement(this.props.tag, {
        ref: 'form',
        className: ('modal-form ' + (this.props.className || '')).trim(),
        method: method,
        style: Object.assign({}, FORM_STYLE, this.props.style),
        onSubmit: onSubmit
      }, this.props.children);
    },

    render: function() {
      if (this.props.loose) {
        return this.renderLoose();
      } else {
        return this.renderWithAnchor();
      }
    },

    renderLoose: function() {
      var underlaySize = {
        width: this.state.underlayWidth + 'px',
        height: this.state.underlayHeight + 'px'
      };
      return React.createElement.apply(React, ['div', {
        ref: 'underlay',
        className: ('modal-form-underlay ' + (this.props.className || '')).trim(),
        style: Object.assign({}, UNDERLAY_STYLE, underlaySize, this.props.underlayStyle),
        onClick: this.handleUnderlayClick.bind(this)
      }].concat(this.getUnderlayChildren()));
    },

    renderWithAnchor: function() {
      var looseRenderResult = this.renderLoose.apply(this, arguments);
      return React.createElement(ModalFormAnchor, null, looseRenderResult);
    },

    componentDidUpdate: function() {
      this.reposition();
    },

    reposition: function() {
      var formRect = this.refs.form.getBoundingClientRect();
      var formStyle = getComputedStyle(this.refs.form);
      var formRight = pageXOffset + formRect.right + parseFloat(formStyle.marginRight);
      var formBottom = pageYOffset + formRect.bottom + parseFloat(formStyle.marginBottom);
      var totalWidth = Math.max(document.documentElement.offsetWidth, formRight); // Skip `innerWidth` to avoid counting scrollbar.
      var totalHeight = Math.max(document.documentElement.offsetHeight, innerHeight, formBottom);
      if (totalWidth !== this.state.underlayWidth || totalHeight !== this.state.underlayHeight) {
        this.setState({
          underlayWidth: totalWidth,
          underlayHeight: totalHeight
        });
      }
      this.props.onReposition();
    },

    handleFormSubmit: function(event) {
      event.preventDefault();
      this.props.onSubmit.apply(null, arguments);
    },

    handleUnderlayClick: function(event) {
      if (!this.props.required && event.target === this.refs.underlay) {
        this.props.onCancel.apply(null, arguments);
      }
    }
  });

  if (typeof module !== 'undefined') {
    module.exports = ModalFormBase;
  } else if (typeof window !== 'undefined') {
    window.ZUIModalFormBase = ModalFormBase;
  }
}());
<|endoftext|>


<commit_before>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

/*
    This package contains general helpers that will be used everywhere else
*/

    var
        _arrayEachWithResult = function (array, memberCallback, defaultResult) {
            var
                result,
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                result = memberCallback.apply(this, [idx, array[idx]]);
                if (undefined !== result) {
                    return result;
                }
            }
            return defaultResult;
        },

        _arrayEach = function (array, memberCallback) {
            var
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                memberCallback.apply(this, [idx, array[idx]]);
            }
        },

        _dictionaryEachWithResult = function (dictionary, memberCallback,
            defaultResult) {
            var
                result,
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    result = memberCallback.apply(this, [member,
                        dictionary[member]]);
                    if (undefined !== result) {
                        return result;
                    }
                }
            }
            return defaultResult;
        },

        _dictionaryEach = function (dictionary, memberCallback) {
            var
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    memberCallback.apply(this, [member, dictionary[member]]);
                }
            }
        },

        _assign = function (member, value) {
            // this = gpf.extend's arguments
            // this[0] is dictionary
            this[0][member] = value;
        },

        _assignOrCall = function (member, value) {
            // this = gpf.extend's arguments
            var
                dictionary = this[0],
                overwriteCallback = this[2];
            // TODO: see if in is faster
            if (undefined !== dictionary[member]) {
                overwriteCallback(dictionary, member, value);
            } else {
                dictionary[member] = value;
            }
        },

        _likeSearchInDone = function /*gpf:inline*/ (array, a, b) {
            var
                idx,
                ia,
                ib,
                len = array.length;
            for (idx = 0; idx < len; ++idx) {
                ia = array[idx].a;
                ib = array[idx].b;
                if ((ia === a && ib === b) || (ib === a && ia === b)) {
                    return idx;
                }
            }
            return undefined;
        },

        _likeTypes = function /*gpf:inline*/ (a, b, alike) {
            if (alike && ("object" === typeof a || "object" === typeof b)) {
                /*
                 One of the two is an object but not the other,
                 Consider downcasting Number and String
                 */
                if (a instanceof String || b instanceof String) {
                    return a.toString() ===  b.toString();
                }
                if (a instanceof Number || b instanceof Number) {
                    return a.valueOf() ===  b.valueOf();
                }
                return false;
            }
            return false;
        },

        _likeCompareMembers = function /*gpf:inline*/ (ma, mb, alike, stacks) {
            if (ma !== mb) {
                if (typeof ma !== typeof mb && !_likeTypes(ma, mb, alike)) {
                    return false;
                }
                if (null === ma || null === mb
                    || "object" !== typeof ma) {
                    return false; // Because we know that ma !== mb
                }
                if (undefined === _likeSearchInDone(stacks.done, ma, mb)) {
                    stacks.todo.push(ma);
                    stacks.todo.push(mb);
                }
            }
            return true;
        },

        _likeMembers = function /*gpf:inline*/ (a, b, alike) {
            var
                member,
                count,
                stacks = {
                    todo: [a, b],
                    done: []
                };
            while (0 !== stacks.todo.length) {
                b = stacks.todo.pop();
                a = stacks.todo.pop();
                if (a.prototype !== b.prototype) {
                    return false;
                }
                stacks.done.push({a: a, b: b });
                count = 0;
                for (member in a) {
                    if (a.hasOwnProperty(member)) {
                        ++count;
                        if (!_likeCompareMembers(a[member], b[member], alike,
                            stacks)) {
                            return false;
                        }
                    }
                }
                for (member in b) {
                    if (b.hasOwnProperty(member)) {
                        --count;
                    }
                }
                if (0 !== count) {
                    return false;
                }
            }
            return true;
        },

        _values = {
            boolean: function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    if ("yes" === value || "true" === value) {
                        return true;
                    }
                    return 0 !== parseInt(value, 10);
                }
                if ("number" === valueType) {
                    return 0 !== value;
                }
                return defaultValue;
            },

            number:  function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    return parseFloat(value);
                }
                return defaultValue;
            },

            string: function (value, valueType, defaultValue) {
                gpf.interfaces.ignoreParameter(valueType);
                gpf.interfaces.ignoreParameter(defaultValue);
                if (value instanceof Date) {
                    return gpf.dateToComparableFormat(value);
                }
                return value.toString();
            },

            object: function (value, valueType, defaultValue) {
                if (defaultValue instanceof Date && "string" === valueType) {
                    return gpf.dateFromComparableFormat(value);
                }
                return defaultValue;
            }
        },

        // https://github.com/jshint/jshint/issues/525
        Func = Function; // avoid JSHint error

    /*jshint unused: false */ // Because of arguments
    /*
     * Enumerate dictionary members and call memberCallback for each of them.
     * If defaultResult is defined, memberCallback may return a result.
     * If memberCallback returns anything, the function stops and returns it.
     * Otherwise, the defaultResult is returned.
     * When defaultResult is not defined, memberCallback result is ignored.
     *
     * @param {Object|Array} dictionary
     * @param {Function} memberCallback
     * @param {*} defaultResult
     * @return {*}
     * @chainable
     * @forwardThis
     */
    gpf.each = function (dictionary, memberCallback, defaultResult) {
        if (undefined === defaultResult) {
            if (dictionary instanceof Array) {
                _arrayEach.apply(this, arguments);
            } else {
                _dictionaryEach.apply(this, arguments);
            }
            return undefined;
        }
        if (dictionary instanceof Array) {
            return _arrayEachWithResult.apply(this, arguments);
        }
        return _dictionaryEachWithResult.apply(this, arguments);
    };
    /*jshint unused: true */

    /*
     * Appends members of additionalProperties to the dictionary object.
     * If a conflict has to be handled (i.e. member exists on both objects),
     * the overwriteCallback has to handle it.
     *
     * @param {Object} dictionary
     * @param {Object} additionalProperties
     * @param {Function} overwriteCallback
     * @return {Object} the modified dictionary
     * @chainable
     */
    gpf.extend = function (dictionary, additionalProperties,
        overwriteCallback) {
        var callbackToUse;
        if (undefined === overwriteCallback) {
            callbackToUse = _assign;
        } else {
            callbackToUse = _assignOrCall;
        }
        gpf.each.apply(arguments, [additionalProperties, callbackToUse]);
        return dictionary;
    };

    gpf.extend(gpf, {

        _alpha: "abcdefghijklmnopqrstuvwxyz",
        _ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",

        _func: function (source) {
            return new Func(source);
        },

        /*
         * Converts the provided value to match the expectedType.
         * If not specified or impossible to do so, defaultValue is returned.
         * When expectedType is not provided, it is deduced from defaultValue.
         *
         * @param {*} value
         * @param {*} default value
         * @param {String} [expectedType=typeof defaultValue] expected type
         * @return {*}
         */
        value: function (value, defaultValue, expectedType) {
            var valueType = typeof value;
            if (!expectedType) {
                expectedType = typeof defaultValue;
            }
            if (expectedType === valueType) {
                return value;
            }
            if ("undefined" === valueType || !value) {
                return defaultValue;
            }
            return _values[expectedType](value, valueType, defaultValue);
        },

        /*
         * Compares a and b and return true if they are look-alike (all members
         * have the same type and same value).
         *
         * NOTES:
         * 14/04/2013 17:19:43
         * Generates too much recursion, changed the algorithm to avoid
         * recursion using document.body (and any kind of object that references
         * other objects) I found that it was necessary to keep track of already
         * processed objects.
         *
         * @param {*} a
         * @param {*} b
         * @param {Boolean} [alike=false] alike Allow to be tolerant on
         *        primitive types compared with their object equivalent
         * @return {Boolean}
         */
        like: function (a, b, alike) {
            if (a === b) {
                return true;
            }
            if (typeof a !== typeof b) {
                return _likeTypes(a, b, alike);
            }
            if (null === a || null === b || "object" !== typeof a) {
                return false;
            }
            return _likeMembers(a, b, alike);
        },

        /**
         * Shallow copy an object
         *
         * @param {Object} obj
         * @return {Object}
         */
        clone: function (obj) {
            /*
             * http://stackoverflow.com/questions/122102/what-is-the-most-
             * efficient-way-to-clone-an-object/5344074#5344074
             */
            return gpf.json.parse(gpf.json.stringify(obj));
        },

        /*
         * Find the first member of dictionary which value equals to value.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {String/number/undefined} undefined if not found
         */
        test: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        return idx;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        return idx;
                    }
                }
            }
            return undefined;
        },

        /*
         * Inserts the value in the array if not already present.
         *
         * @param {Array} array
         * @param {*} value
         * @return {Array}
         * @chainable
         */
        set: function (array, value) {
            gpf.ASSERT(array instanceof Array,
                "gpf.set must be used with an Array");
            var idx = array.length;
            while (idx > 0) {
                if (array[--idx] === value) {
                    return array; // Already set
                }
            }
            array.push(value);
            return array;
        },

        /*
         * Removes the member of 'dictionary' which value equals 'value'.
         * NOTE: that the object is modified.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {Object/array} dictionary
         * @chainable
         */
        clear: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        dictionary.splice(idx, 1);
                        break;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        break;
                    }
                }
            }
            return dictionary;
        },

        /**
         * XOR
         *
         * @param {Boolean} a
         * @param {Boolean} b
         */
        xor: function (a, b) {
            return a && !b || !a && b;
        },

        /**
         * Generic callback handler
         *
         * @param {Function} handler
         * @param {Object} scope
         * @constructor
         * @class gpf.Callback
         */
        Callback: function (handler, scope) {
            if (handler) {
                this._handler = handler;
            }
            if (scope) {
                this._scope = scope;
            }
        }

    });

    gpf.extend(gpf.Callback.prototype, {
        _handler: gpf._func(""),
        _scope: null,

        /**
         * Get the handler function
         *
         * @returns {Function}
         */
        handler: function () {
            return this._handler;
        },

        /**
         * Get the scope
         *
         * @returns {Object}
         */
        scope: function () {
            return this._scope;
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*} ... Forwarded to the callback handler
         * @returns {*}
         */
        call: function() {
            var
                scope = arguments[0],
                args = [],
                len = arguments.length,
                idx;
            for (idx = 1; idx < len; ++idx) {
                args.push(arguments[idx]);
            }
            return this.apply(scope, args);
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*[]} args Forwarded to the callback handler
         * @returns {*}
         */
        apply: function(scope, args) {
            var finalScope = this._scope || scope;
            if (!scope) {
                finalScope = gpf.context();
            }
            return this._handler.apply(finalScope, args);
        }
    });

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<commit_msg>Exposed gpf.Callback.resolveScope<commit_after>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

/*
    This package contains general helpers that will be used everywhere else
*/

    var
        _arrayEachWithResult = function (array, memberCallback, defaultResult) {
            var
                result,
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                result = memberCallback.apply(this, [idx, array[idx]]);
                if (undefined !== result) {
                    return result;
                }
            }
            return defaultResult;
        },

        _arrayEach = function (array, memberCallback) {
            var
                len = array.length,
                idx;
            for (idx = 0; idx < len; ++idx) {
                memberCallback.apply(this, [idx, array[idx]]);
            }
        },

        _dictionaryEachWithResult = function (dictionary, memberCallback,
            defaultResult) {
            var
                result,
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    result = memberCallback.apply(this, [member,
                        dictionary[member]]);
                    if (undefined !== result) {
                        return result;
                    }
                }
            }
            return defaultResult;
        },

        _dictionaryEach = function (dictionary, memberCallback) {
            var
                member;
            for (member in dictionary) {
                if (dictionary.hasOwnProperty(member)) {
                    memberCallback.apply(this, [member, dictionary[member]]);
                }
            }
        },

        _assign = function (member, value) {
            // this = gpf.extend's arguments
            // this[0] is dictionary
            this[0][member] = value;
        },

        _assignOrCall = function (member, value) {
            // this = gpf.extend's arguments
            var
                dictionary = this[0],
                overwriteCallback = this[2];
            // TODO: see if in is faster
            if (undefined !== dictionary[member]) {
                overwriteCallback(dictionary, member, value);
            } else {
                dictionary[member] = value;
            }
        },

        _likeSearchInDone = function /*gpf:inline*/ (array, a, b) {
            var
                idx,
                ia,
                ib,
                len = array.length;
            for (idx = 0; idx < len; ++idx) {
                ia = array[idx].a;
                ib = array[idx].b;
                if ((ia === a && ib === b) || (ib === a && ia === b)) {
                    return idx;
                }
            }
            return undefined;
        },

        _likeTypes = function /*gpf:inline*/ (a, b, alike) {
            if (alike && ("object" === typeof a || "object" === typeof b)) {
                /*
                 One of the two is an object but not the other,
                 Consider downcasting Number and String
                 */
                if (a instanceof String || b instanceof String) {
                    return a.toString() ===  b.toString();
                }
                if (a instanceof Number || b instanceof Number) {
                    return a.valueOf() ===  b.valueOf();
                }
                return false;
            }
            return false;
        },

        _likeCompareMembers = function /*gpf:inline*/ (ma, mb, alike, stacks) {
            if (ma !== mb) {
                if (typeof ma !== typeof mb && !_likeTypes(ma, mb, alike)) {
                    return false;
                }
                if (null === ma || null === mb
                    || "object" !== typeof ma) {
                    return false; // Because we know that ma !== mb
                }
                if (undefined === _likeSearchInDone(stacks.done, ma, mb)) {
                    stacks.todo.push(ma);
                    stacks.todo.push(mb);
                }
            }
            return true;
        },

        _likeMembers = function /*gpf:inline*/ (a, b, alike) {
            var
                member,
                count,
                stacks = {
                    todo: [a, b],
                    done: []
                };
            while (0 !== stacks.todo.length) {
                b = stacks.todo.pop();
                a = stacks.todo.pop();
                if (a.prototype !== b.prototype) {
                    return false;
                }
                stacks.done.push({a: a, b: b });
                count = 0;
                for (member in a) {
                    if (a.hasOwnProperty(member)) {
                        ++count;
                        if (!_likeCompareMembers(a[member], b[member], alike,
                            stacks)) {
                            return false;
                        }
                    }
                }
                for (member in b) {
                    if (b.hasOwnProperty(member)) {
                        --count;
                    }
                }
                if (0 !== count) {
                    return false;
                }
            }
            return true;
        },

        _values = {
            boolean: function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    if ("yes" === value || "true" === value) {
                        return true;
                    }
                    return 0 !== parseInt(value, 10);
                }
                if ("number" === valueType) {
                    return 0 !== value;
                }
                return defaultValue;
            },

            number:  function (value, valueType, defaultValue) {
                if ("string" === valueType) {
                    return parseFloat(value);
                }
                return defaultValue;
            },

            string: function (value, valueType, defaultValue) {
                gpf.interfaces.ignoreParameter(valueType);
                gpf.interfaces.ignoreParameter(defaultValue);
                if (value instanceof Date) {
                    return gpf.dateToComparableFormat(value);
                }
                return value.toString();
            },

            object: function (value, valueType, defaultValue) {
                if (defaultValue instanceof Date && "string" === valueType) {
                    return gpf.dateFromComparableFormat(value);
                }
                return defaultValue;
            }
        },

        // https://github.com/jshint/jshint/issues/525
        Func = Function; // avoid JSHint error

    /*jshint unused: false */ // Because of arguments
    /*
     * Enumerate dictionary members and call memberCallback for each of them.
     * If defaultResult is defined, memberCallback may return a result.
     * If memberCallback returns anything, the function stops and returns it.
     * Otherwise, the defaultResult is returned.
     * When defaultResult is not defined, memberCallback result is ignored.
     *
     * @param {Object|Array} dictionary
     * @param {Function} memberCallback
     * @param {*} defaultResult
     * @return {*}
     * @chainable
     * @forwardThis
     */
    gpf.each = function (dictionary, memberCallback, defaultResult) {
        if (undefined === defaultResult) {
            if (dictionary instanceof Array) {
                _arrayEach.apply(this, arguments);
            } else {
                _dictionaryEach.apply(this, arguments);
            }
            return undefined;
        }
        if (dictionary instanceof Array) {
            return _arrayEachWithResult.apply(this, arguments);
        }
        return _dictionaryEachWithResult.apply(this, arguments);
    };
    /*jshint unused: true */

    /*
     * Appends members of additionalProperties to the dictionary object.
     * If a conflict has to be handled (i.e. member exists on both objects),
     * the overwriteCallback has to handle it.
     *
     * @param {Object} dictionary
     * @param {Object} additionalProperties
     * @param {Function} overwriteCallback
     * @return {Object} the modified dictionary
     * @chainable
     */
    gpf.extend = function (dictionary, additionalProperties,
        overwriteCallback) {
        var callbackToUse;
        if (undefined === overwriteCallback) {
            callbackToUse = _assign;
        } else {
            callbackToUse = _assignOrCall;
        }
        gpf.each.apply(arguments, [additionalProperties, callbackToUse]);
        return dictionary;
    };

    gpf.extend(gpf, {

        _alpha: "abcdefghijklmnopqrstuvwxyz",
        _ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",

        _func: function (source) {
            return new Func(source);
        },

        /*
         * Converts the provided value to match the expectedType.
         * If not specified or impossible to do so, defaultValue is returned.
         * When expectedType is not provided, it is deduced from defaultValue.
         *
         * @param {*} value
         * @param {*} default value
         * @param {String} [expectedType=typeof defaultValue] expected type
         * @return {*}
         */
        value: function (value, defaultValue, expectedType) {
            var valueType = typeof value;
            if (!expectedType) {
                expectedType = typeof defaultValue;
            }
            if (expectedType === valueType) {
                return value;
            }
            if ("undefined" === valueType || !value) {
                return defaultValue;
            }
            return _values[expectedType](value, valueType, defaultValue);
        },

        /*
         * Compares a and b and return true if they are look-alike (all members
         * have the same type and same value).
         *
         * NOTES:
         * 14/04/2013 17:19:43
         * Generates too much recursion, changed the algorithm to avoid
         * recursion using document.body (and any kind of object that references
         * other objects) I found that it was necessary to keep track of already
         * processed objects.
         *
         * @param {*} a
         * @param {*} b
         * @param {Boolean} [alike=false] alike Allow to be tolerant on
         *        primitive types compared with their object equivalent
         * @return {Boolean}
         */
        like: function (a, b, alike) {
            if (a === b) {
                return true;
            }
            if (typeof a !== typeof b) {
                return _likeTypes(a, b, alike);
            }
            if (null === a || null === b || "object" !== typeof a) {
                return false;
            }
            return _likeMembers(a, b, alike);
        },

        /**
         * Shallow copy an object
         *
         * @param {Object} obj
         * @return {Object}
         */
        clone: function (obj) {
            /*
             * http://stackoverflow.com/questions/122102/what-is-the-most-
             * efficient-way-to-clone-an-object/5344074#5344074
             */
            return gpf.json.parse(gpf.json.stringify(obj));
        },

        /*
         * Find the first member of dictionary which value equals to value.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {String/number/undefined} undefined if not found
         */
        test: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        return idx;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        return idx;
                    }
                }
            }
            return undefined;
        },

        /*
         * Inserts the value in the array if not already present.
         *
         * @param {Array} array
         * @param {*} value
         * @return {Array}
         * @chainable
         */
        set: function (array, value) {
            gpf.ASSERT(array instanceof Array,
                "gpf.set must be used with an Array");
            var idx = array.length;
            while (idx > 0) {
                if (array[--idx] === value) {
                    return array; // Already set
                }
            }
            array.push(value);
            return array;
        },

        /*
         * Removes the member of 'dictionary' which value equals 'value'.
         * NOTE: that the object is modified.
         *
         * @param {Object/array} dictionary
         * @param {*} value
         * @return {Object/array} dictionary
         * @chainable
         */
        clear: function (dictionary, value) {
            var idx;
            if (dictionary instanceof Array) {
                idx = dictionary.length;
                while (idx > 0) {
                    if (dictionary[--idx] === value) {
                        dictionary.splice(idx, 1);
                        break;
                    }
                }
            } else {
                for (idx in dictionary) {
                    if (dictionary.hasOwnProperty(idx)
                        && dictionary[idx] === value) {
                        break;
                    }
                }
            }
            return dictionary;
        },

        /**
         * XOR
         *
         * @param {Boolean} a
         * @param {Boolean} b
         */
        xor: function (a, b) {
            return a && !b || !a && b;
        },

        /**
         * Generic callback handler
         *
         * @param {Function} handler
         * @param {Object} scope
         * @constructor
         * @class gpf.Callback
         */
        Callback: function (handler, scope) {
            if (handler) {
                this._handler = handler;
            }
            if (scope) {
                this._scope = scope;
            }
        }

    });

    gpf.extend(gpf.Callback.prototype, {
        _handler: gpf._func(""),
        _scope: null,

        /**
         * Get the handler function
         *
         * @returns {Function}
         */
        handler: function () {
            return this._handler;
        },

        /**
         * Get the scope
         *
         * @returns {Object}
         */
        scope: function () {
            return this._scope;
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*} ... Forwarded to the callback handler
         * @returns {*}
         */
        call: function() {
            var
                scope = arguments[0],
                args = [],
                len = arguments.length,
                idx;
            for (idx = 1; idx < len; ++idx) {
                args.push(arguments[idx]);
            }
            return this.apply(scope, args);
        },

        /**
         * Executes the callback and override the scope if not defined
         *
         * @param {Object} scope Scope to apply if none set in the callback
         * @param {*[]} args Forwarded to the callback handler
         * @returns {*}
         */
        apply: function(scope, args) {
            var finalScope = gpf.Callback.resolveScope(this._scope || scope);
            return this._handler.apply(finalScope, args);
        }
    });

    /**
     * Resolve function scope
     *
     * @static
     */
    gpf.Callback.resolveScope = function (scope) {
        if (!scope) {
            scope = gpf.context();
        }
        return scope;
    };

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<|endoftext|>


<commit_before>export function noop() {}

export const identity = x => x;

export function assign(tar, src) {
	for (const k in src) tar[k] = src[k];
	return tar;
}

export function is_promise(value) {
	return value && typeof value.then === 'function';
}

export function add_location(element, file, line, column, char) {
	element.__svelte_meta = {
		loc: { file, line, column, char }
	};
}

export function run(fn) {
	return fn();
}

export function blank_object() {
	return Object.create(null);
}

export function run_all(fns) {
	fns.forEach(run);
}

export function is_function(thing) {
	return typeof thing === 'function';
}

export function safe_not_equal(a, b) {
	return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}

export function not_equal(a, b) {
	return a != a ? b == b : a !== b;
}

export function validate_store(store, name) {
	if (!store || typeof store.subscribe !== 'function') {
		throw new Error(`'${name}' is not a store with a 'subscribe' method`);
	}
}

export function subscribe(component, store, callback) {
	component.$$.on_destroy.push(store.subscribe(callback));
}

export function create_slot(definition, ctx, fn) {
	if (definition) {
		const slot_ctx = get_slot_context(definition, ctx, fn);
		return definition[0](slot_ctx);
	}
}

export function get_slot_context(definition, ctx, fn) {
	return definition[1]
		? assign({}, assign(ctx.$$scope.ctx, definition[1](fn ? fn(ctx) : {})))
		: ctx.$$scope.ctx;
}

export function get_slot_changes(definition, ctx, changed, fn) {
	return definition[1]
		? assign({}, assign(ctx.$$scope.changed || {}, definition[1](fn ? fn(changed) : {})))
		: ctx.$$scope.changed || {};
}

export function exclude_internal_props(props) {
	const result = {};
	for (const k in props) if (k[0] !== '$') result[k] = props[k];
	return result;
}<commit_msg>fix: Ensure RxJS users don't create memory leaks<commit_after>export function noop() {}

export const identity = x => x;

export function assign(tar, src) {
	for (const k in src) tar[k] = src[k];
	return tar;
}

export function is_promise(value) {
	return value && typeof value.then === 'function';
}

export function add_location(element, file, line, column, char) {
	element.__svelte_meta = {
		loc: { file, line, column, char }
	};
}

export function run(fn) {
	return fn();
}

export function blank_object() {
	return Object.create(null);
}

export function run_all(fns) {
	fns.forEach(run);
}

export function is_function(thing) {
	return typeof thing === 'function';
}

export function safe_not_equal(a, b) {
	return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}

export function not_equal(a, b) {
	return a != a ? b == b : a !== b;
}

export function validate_store(store, name) {
	if (!store || typeof store.subscribe !== 'function') {
		throw new Error(`'${name}' is not a store with a 'subscribe' method`);
	}
}

export function subscribe(component, store, callback) {
	let unsub = store.subscribe(callback);
	// Prevent memory leaks for RxJS users.
	if (typeof unsub === 'object' && typeof unsub.unsubscribe === 'function') {
		unsub = () => unsub.unsubscribe();
	}
	component.$$.on_destroy.push(unsub);
}

export function create_slot(definition, ctx, fn) {
	if (definition) {
		const slot_ctx = get_slot_context(definition, ctx, fn);
		return definition[0](slot_ctx);
	}
}

export function get_slot_context(definition, ctx, fn) {
	return definition[1]
		? assign({}, assign(ctx.$$scope.ctx, definition[1](fn ? fn(ctx) : {})))
		: ctx.$$scope.ctx;
}

export function get_slot_changes(definition, ctx, changed, fn) {
	return definition[1]
		? assign({}, assign(ctx.$$scope.changed || {}, definition[1](fn ? fn(changed) : {})))
		: ctx.$$scope.changed || {};
}

export function exclude_internal_props(props) {
	const result = {};
	for (const k in props) if (k[0] !== '$') result[k] = props[k];
	return result;
}
<|endoftext|>


<commit_before>// Essentially cron for scheduling tasks in Node.
'use strict';
const assert  = require('assert');
const debug   = require('debug')('ironium:schedules');
const ms      = require('ms');
const runJob  = require('./run_job');

// Job schedule consists of three properties:
// jobName  - Schedule name (for reporting)
// when     - Number, string or object with
//   start  - Start running after this time
//   every  - Run every so many seconds
//   end    - Do not run after this time
// handler  - Job to run
//
// A schedule can have a start time, but no other properties, in which case the
// job will run exactly once, if the start time is in the future.
//
// A schedule can have recurrence, in which case the job will run every so many
// seconds, with optional start and end time.
class Schedule {

  constructor(scheduler, jobName, when, handler) {
    assert(jobName, 'Schedule missing job name');
    assert(typeof handler === 'function', 'Schedule missing handler function');

    this._scheduler = scheduler;
    this.jobName    = jobName;
    this.handler    = handler;

    if (when instanceof Date)
      this.startTime  = when.getTime();
    else if (typeof when === 'string')
      this.every      = ms(when);
    else if (typeof when === 'number')
      this.every      = parseInt(when, 0);
    else if (typeof when === 'object') {
      this.startTime  = when.start ? +when.start : undefined;
      this.endTime    = when.end   ? +when.end   : undefined;
      this.every      = (typeof when.every === 'string' ? ms(when.every) : parseInt(when.every, 0));
    } else
      assert(false, 'Schedule time must be Date (instance), Number (interval) or object with start/every/end properties');

    assert(this.startTime || this.every, 'Schedule requires either start time or interval (0 not acceptable)');
    if (this.endTime) {
      assert(this.every, 'Schedule with end time requires interval');
      if (this.startTime)
        assert(this.startTime < this.endTime, 'Schedule must start before it ends');
    }

    if (this.every)
      assert(this.every >= ms('60s'), 'Minimum interval is 60 seconds');

    this._queue = this._scheduler.queue(this.jobName);
    this._queue.eachJob(job => this._processQueuedJob(job));

    this.resetSchedule();
  }

  // Starts the scheduler for this job.  Sets timer/interval to run the job.
  start() {
    const now = Date.now();
    if (this.endTime && now >= this.endTime)
      return;

    const delay = this._next - now;
    this._timeout = setTimeout(() => this._firstIntance(), delay);
  }

  _firstIntance() {
    this._queueNext();
    if (this.every)
      this._interval = setInterval(() => this._everyInterval(), this.every);
  }

  _everyInterval() {
    if (this.endTime && Date.now() >= this.endTime)
      clearInterval(this._interval);
    else
      this._queueNext();
  }

  // Stops the scheduler for this job.  Resets timer/interval.
  stop() {
    clearTimeout(this._timeout);
    clearInterval(this._interval);
  }

  // Run job once.
  runOnce() {
    const runOnSchedule = this._next && (Date.now() >= this._next);
    // Set schedule to next interval, but also deal with case where clock
    // rewinds (e.g. between different scenarios)
    this.resetSchedule();
    if (runOnSchedule)
      return this._queueJob();
    else
      return Promise.resolve();
  }

  // Reset next time job runs
  resetSchedule() {
    this._next = this._getNextSchedule();
    debug('Set schedule %s to fire next at %s', this.jobName, new Date(this._next).toISOString());
  }

  _getNextSchedule() {
    const now       = Date.now();
    const interval  = this.every;

    if (interval && this.startTime) {

      // This is where every 24h start time 5pm becomes tomorrow 5pm
      const offsetFromStart = Math.max(now - this.startTime, 0) + interval - 1;
      const roundToInterval = offsetFromStart - (offsetFromStart % interval);
      const nextTime        = this.startTime + roundToInterval;
      const futureTime      = (!this.endTime || this.endTime > nextTime) ? nextTime : null;
      return futureTime;

    } else if (interval) {

      // This is where every 24h becomes 0:00 tomorrow
      const inNextInterval  = now + interval;
      const roundToInterval = inNextInterval - (inNextInterval % interval);
      const futureTime      = (!this.endTime || this.endTime > roundToInterval) ? roundToInterval : null;
      return futureTime;

    } else {

      // Only if scheduled for the future; end date makes no sense without
      // interval
      const futureTime = (this.startTime >= now) ? this.startTime : null;
      return futureTime;

    }
  }

  // Queue the job to run one more time.  Cancels interval if past end time.
  _queueNext() {
    return this._queueJob()
      .catch(() => {
        // Error queuing job. For a one time job, try to queue again. For
        // period job, only queue again if not scheduled to run soon.
        if (!this.every || this.every > ms('10m')) {
          const timeout = setTimeout(() => this._queueNext(), ms('5s'));
          timeout.unref();
        }
      });
  }

  // Queues the job (if allowed by coordination).
  _queueJob() {
    const time = new Date().toISOString();
    return this._scheduler._canScheduleJob(this.jobName, time)
      .then(canSchedule => {
        if (canSchedule)
          return this._queue.queueJob({ time });
        else
          return null;
      })
      .catch(error => {
        this._scheduler._ironium.reportError(this.jobName, error);
        return Promise.reject(error);
      });
  }

  // Picks up the job from the queue and determines whether it
  // should be run or not. See `shouldRunJob()` below.
  _processQueuedJob(job) {
    const time = job.time;

    if (shouldRunJob(this, job))
      return this._runJob(time);
    else {
      debug('Ignoring this occurrence of %s', this.jobName);
      return Promise.resolve();
    }
  }

  // Run the job when picked up from queue.
  _runJob(time) {
    debug('Processing %s, scheduled for %s', this.jobName, time.toString());
    return runJob(this.jobName, this.handler, [], undefined)
      .then(() => {
        debug('Completed %s, scheduled for %s', this.jobName, time.toString());
      })
      .catch(error => {
        this._scheduler._ironium.reportError(`${this.jobName}@${time}`, error);
        return Promise.reject(error);
      });
  }

}

module.exports = class Scheduler {

  constructor(ironium) {
    // If true, every new job is started automatically.  Necessary in case you
    // call scheduleJob() after calling start().
    this.started      = false;

    this._ironium     = ironium;
    this._schedules   = new Map();
  }

  // Schedules a new job.
  scheduleJob(jobName, when, handler) {
    assert(handler instanceof Function,   'Third argument must be the handler function');
    assert(!this._schedules.has(jobName), `Attempt to schedule multiple jobs with same name (${jobName})`);

    const newSchedule = new Schedule(this, jobName, when, handler);
    this._schedules.set(jobName, newSchedule);

    if (this.started) {
      debug('Starting schedule %s', jobName);
      newSchedule.start();
    }
  }

  // Start all scheduled jobs.
  start() {
    debug('Starting all schedules');
    this.started = true;
    for (const schedule of this._schedules.values())
      schedule.start();
  }

  // Stop all scheduled jobs.
  stop() {
    debug('Stopping all schedules');
    this.started = false;
    for (const schedule of this._schedules.values())
      schedule.stop();
  }

  // Run all schedules jobs in parallel.
  runOnce() {
    const schedules = Array.from(this._schedules.values());
    const promises  = schedules.map(schedule => schedule.runOnce());
    return Promise.all(promises);
  }

  // Returns an array of all schedules.
  get schedules() {
    return Array.from(this._schedules.values());
  }

  // Resets all schedules, used during testing
  resetSchedule() {
    for (const schedule of this._schedules.values())
      schedule.resetSchedule();
  }

  // Use mutex to determine if this process can schedule this job
  _canScheduleJob(jobName, time) {
    return this._ironium.configPromise
      .then(config => config.canScheduleJob || canScheduleJobAlways)
      .then(canScheduleJob => canScheduleJob(jobName, time));
  }

  queue(queueName) {
    return this._ironium.queue(queueName);
  }
};

// canScheduleJob when there is no coordinator, and any worker may schedule the
// job. Applicable for development and testing environments, where you run a
// single worker.
function canScheduleJobAlways() {
  return true;
}

// A scheduled job may be returned to the queue when it fails or times out.
// For recurring jobs, we don't want to run duplicate jobs once the error
// condition is resolved.
// Returns true if the job should be run.
function shouldRunJob(schedule, job) {
  if (schedule) {
    if (schedule.every) {
      const jobTime   = +new Date(job.time);
      const expiredBy = jobTime + schedule.every;
      const isExpired = Date.now() > expiredBy;
      return !isExpired;
    } else {
      // One-offs never expire (i.e. they must be retried until successful).
      return true;
    }
  } else
    return false;
}
<commit_msg>Refactor schedule handler into a single _runJob() function.<commit_after>// Essentially cron for scheduling tasks in Node.
'use strict';
const assert  = require('assert');
const debug   = require('debug')('ironium:schedules');
const ms      = require('ms');
const runJob  = require('./run_job');

// Job schedule consists of three properties:
// jobName  - Schedule name (for reporting)
// when     - Number, string or object with
//   start  - Start running after this time
//   every  - Run every so many seconds
//   end    - Do not run after this time
// handler  - Job to run
//
// A schedule can have a start time, but no other properties, in which case the
// job will run exactly once, if the start time is in the future.
//
// A schedule can have recurrence, in which case the job will run every so many
// seconds, with optional start and end time.
class Schedule {

  constructor(scheduler, jobName, when, handler) {
    assert(jobName, 'Schedule missing job name');
    assert(typeof handler === 'function', 'Schedule missing handler function');

    this._scheduler = scheduler;
    this.jobName    = jobName;
    this.handler    = handler;

    if (when instanceof Date)
      this.startTime  = when.getTime();
    else if (typeof when === 'string')
      this.every      = ms(when);
    else if (typeof when === 'number')
      this.every      = parseInt(when, 0);
    else if (typeof when === 'object') {
      this.startTime  = when.start ? +when.start : undefined;
      this.endTime    = when.end   ? +when.end   : undefined;
      this.every      = (typeof when.every === 'string' ? ms(when.every) : parseInt(when.every, 0));
    } else
      assert(false, 'Schedule time must be Date (instance), Number (interval) or object with start/every/end properties');

    assert(this.startTime || this.every, 'Schedule requires either start time or interval (0 not acceptable)');
    if (this.endTime) {
      assert(this.every, 'Schedule with end time requires interval');
      if (this.startTime)
        assert(this.startTime < this.endTime, 'Schedule must start before it ends');
    }

    if (this.every)
      assert(this.every >= ms('60s'), 'Minimum interval is 60 seconds');

    this._queue = this._scheduler.queue(this.jobName);
    this._queue.eachJob(job => this._runJob(job));

    this.resetSchedule();
  }

  // Starts the scheduler for this job.  Sets timer/interval to run the job.
  start() {
    const now = Date.now();
    if (this.endTime && now >= this.endTime)
      return;

    const delay = this._next - now;
    this._timeout = setTimeout(() => this._firstIntance(), delay);
  }

  _firstIntance() {
    this._queueNext();
    if (this.every)
      this._interval = setInterval(() => this._everyInterval(), this.every);
  }

  _everyInterval() {
    if (this.endTime && Date.now() >= this.endTime)
      clearInterval(this._interval);
    else
      this._queueNext();
  }

  // Stops the scheduler for this job.  Resets timer/interval.
  stop() {
    clearTimeout(this._timeout);
    clearInterval(this._interval);
  }

  // Run job once.
  runOnce() {
    const runOnSchedule = this._next && (Date.now() >= this._next);
    // Set schedule to next interval, but also deal with case where clock
    // rewinds (e.g. between different scenarios)
    this.resetSchedule();
    if (runOnSchedule)
      return this._queueJob();
    else
      return Promise.resolve();
  }

  // Reset next time job runs
  resetSchedule() {
    this._next = this._getNextSchedule();
    debug('Set schedule %s to fire next at %s', this.jobName, new Date(this._next).toISOString());
  }

  _getNextSchedule() {
    const now       = Date.now();
    const interval  = this.every;

    if (interval && this.startTime) {

      // This is where every 24h start time 5pm becomes tomorrow 5pm
      const offsetFromStart = Math.max(now - this.startTime, 0) + interval - 1;
      const roundToInterval = offsetFromStart - (offsetFromStart % interval);
      const nextTime        = this.startTime + roundToInterval;
      const futureTime      = (!this.endTime || this.endTime > nextTime) ? nextTime : null;
      return futureTime;

    } else if (interval) {

      // This is where every 24h becomes 0:00 tomorrow
      const inNextInterval  = now + interval;
      const roundToInterval = inNextInterval - (inNextInterval % interval);
      const futureTime      = (!this.endTime || this.endTime > roundToInterval) ? roundToInterval : null;
      return futureTime;

    } else {

      // Only if scheduled for the future; end date makes no sense without
      // interval
      const futureTime = (this.startTime >= now) ? this.startTime : null;
      return futureTime;

    }
  }

  // Queue the job to run one more time.  Cancels interval if past end time.
  _queueNext() {
    return this._queueJob()
      .catch(() => {
        // Error queuing job. For a one time job, try to queue again. For
        // period job, only queue again if not scheduled to run soon.
        if (!this.every || this.every > ms('10m')) {
          const timeout = setTimeout(() => this._queueNext(), ms('5s'));
          timeout.unref();
        }
      });
  }

  // Queues the job (if allowed by coordination).
  _queueJob() {
    const time = new Date().toISOString();
    return this._scheduler._canScheduleJob(this.jobName, time)
      .then(canSchedule => {
        if (canSchedule)
          return this._queue.queueJob({ time });
        else
          return null;
      })
      .catch(error => {
        this._scheduler._ironium.reportError(this.jobName, error);
        return Promise.reject(error);
      });
  }

  // Picks up the job from the queue and determines whether it
  // should be run or not. See `shouldRunJob()` below.
  _runJob(job) {
    if (shouldRunJob(this, job)) {
      debug('Processing %s, scheduled for %s', this.jobName, job.time);
      return Promise.resolve()
        .then(() => this.handler(job))
        .then(() => {
          debug('Completed %s, scheduled for %s', this.jobName, job.time);
        })
        .catch(error => {
          this._scheduler._ironium.reportError(`${this.jobName}@${job.time}`, error);
          return Promise.reject(error);
        });
    } else {
      debug('Ignoring this occurrence of %s', this.jobName);
      return Promise.resolve();
    }
  }
}

module.exports = class Scheduler {

  constructor(ironium) {
    // If true, every new job is started automatically.  Necessary in case you
    // call scheduleJob() after calling start().
    this.started      = false;

    this._ironium     = ironium;
    this._schedules   = new Map();
  }

  // Schedules a new job.
  scheduleJob(jobName, when, handler) {
    assert(handler instanceof Function,   'Third argument must be the handler function');
    assert(!this._schedules.has(jobName), `Attempt to schedule multiple jobs with same name (${jobName})`);

    const newSchedule = new Schedule(this, jobName, when, handler);
    this._schedules.set(jobName, newSchedule);

    if (this.started) {
      debug('Starting schedule %s', jobName);
      newSchedule.start();
    }
  }

  // Start all scheduled jobs.
  start() {
    debug('Starting all schedules');
    this.started = true;
    for (const schedule of this._schedules.values())
      schedule.start();
  }

  // Stop all scheduled jobs.
  stop() {
    debug('Stopping all schedules');
    this.started = false;
    for (const schedule of this._schedules.values())
      schedule.stop();
  }

  // Run all schedules jobs in parallel.
  runOnce() {
    const schedules = Array.from(this._schedules.values());
    const promises  = schedules.map(schedule => schedule.runOnce());
    return Promise.all(promises);
  }

  // Returns an array of all schedules.
  get schedules() {
    return Array.from(this._schedules.values());
  }

  // Resets all schedules, used during testing
  resetSchedule() {
    for (const schedule of this._schedules.values())
      schedule.resetSchedule();
  }

  // Use mutex to determine if this process can schedule this job
  _canScheduleJob(jobName, time) {
    return this._ironium.configPromise
      .then(config => config.canScheduleJob || canScheduleJobAlways)
      .then(canScheduleJob => canScheduleJob(jobName, time));
  }

  queue(queueName) {
    return this._ironium.queue(queueName);
  }
};

// canScheduleJob when there is no coordinator, and any worker may schedule the
// job. Applicable for development and testing environments, where you run a
// single worker.
function canScheduleJobAlways() {
  return true;
}

// A scheduled job may be returned to the queue when it fails or times out.
// For recurring jobs, we don't want to run duplicate jobs once the error
// condition is resolved.
// Returns true if the job should be run.
function shouldRunJob(schedule, job) {
  if (schedule) {
    if (schedule.every) {
      const jobTime   = +new Date(job.time);
      const expiredBy = jobTime + schedule.every;
      const isExpired = Date.now() > expiredBy;
      return !isExpired;
    } else {
      // One-offs never expire (i.e. they must be retried until successful).
      return true;
    }
  } else
    return false;
}
<|endoftext|>


<commit_before>module.exports = [
	{
		description: 'removes whitespace in switch statement',

		input: `
			switch ( foo ) {
				case  bar :
					a();
					break;

				case  baz :
					b();
					break;

				default :
					c();
			}`,

		output: `switch(foo){case bar:a();break;case baz:b();break;default:c()}`
	}
];
<commit_msg>failing test for #19<commit_after>module.exports = [
	{
		description: 'removes whitespace in switch statement',
		input: `
			switch ( foo ) {
				case  bar :
					a();
					break;

				case  baz :
					b();
					break;

				default :
					c();
			}`,
		output: `switch(foo){case bar:a();break;case baz:b();break;default:c()}`
	},

	{
		solo: true,
		description: 'TK',
		input: `
			function foo () {
				switch (a) {
					case 0: while (++i < l) { b(); } return;
				}
			};`,
		output: `function foo(){switch(a){case 0:while(++i<l)b();return}}`
	}
];
<|endoftext|>