<commit_before>module.exports = {
    '1': {
        text: 'What kind of construction permit do you need to submit?',
        answers: {
            'Commercial': { next: 2 },
            'Residential': { next: 3 }
        }
    },

    '3': {
        text: 'Is it one of the following projects?',
        caption: '

Replacing windows and/or exterior doors of the same size and same location

Adding/Removing siding or brick

Repairing a roof to the extent of replacing decking boards/fascia

Repairing foundation without increasing impervious cover

Roof mounted solar panels

Remove/Repair drywall in excess of 32 sq. ft.

Adding/Replacing insulation

Tub/Shower Conversions

',
        answers: {
            'Yes': { next: 4 },
            'No': { next: 2 }
        }
    },

    '4': {
        text: "Will this project involve any structural members or increase square footage of structure?",
        answers: {
            'Yes': { next: 2 },
            'No': { next: 5 }
        },
    },
    '5': {
        text: 'You`re almost done!  You have qualified for an express permit.',
        caption:'

There are a few more steps to check for any necessary prerequisites.

Check your property for NRHD Status and HD or H zoning designations

Click the link below. Then enter your address into the search bar. Change “Map Themes” to zoning map, and in “Layers” under “Zoning and Zoning Overlays” check the box for Airport Overlay and National Register of Historic Districts http://www.austintexas.gov/GIS/DevelopmentWebMap/Viewer.aspx

Is your property in a NRHD or in a H or HD?

',
        answers: {
            'Yes': { next:6, checklist:'Please schedule an appointment with the Historic District'},
            'No': {next:6 }
        }
  },

  '6': {
    text: 'Check your property for Airport Overlay Zoning',
    caption: '

Is your property in a Airport Overlay Zone?

Click the link below. Then enter your address into the search bar. Change “Map Themes” to zoning map, and in “Layers” under “Zoning and Zoning Overlays” check the box for Airport Overlay and National Register of Historic Districts http://www.austintexas.gov/GIS/DevelopmentWebMap/Viewer.aspx

',
    answers: {
      'Yes': { next:7, checklist:'Please schedule an appointment with the Aviation Department'},
      'No': { next:7}
    }
  },

    '7': {
        text: 'Check your proximity to a flood plain',
		caption: '

Is your property within 150 feet of a floodplain?

Visit either of the links below.

www.ATXFloodPro.com or the City’s Floodplain Development Information website http://austintexas.gov/page/floodplain-development-information

',
        answers: {
            'Yes': { next: 8, checklist: 'Please bring a FEMA elevation certificate form with your application' },
            'No': { next: 8 }
        }
    },

    '8': {
        text: "Check your property for expired permits",
		caption: '

Does your property have any expired permits?

Check at this link: https://www.austintexas.gov/devreview/a_queryfolder_permits.jsp

',
        answers: {
            'Yes': { next: 9 },
            'No': { next: 12 }
        }
    },

    '9': {
        text: 'Does the expired permit work qualify for the express permit?',
        answers: {
            'Yes': { next: 12, checklist: 'Add the expired permit work to the current permit' },
            'No': { next: 12, checklist: 'Bring an Acknowledgement of Expired Permit form with your application' }
        }
    },

    '12': {
        permitId:1
    }

};
<commit_msg>tooltip for structure<commit_after>module.exports = {
    '1': {
        text: 'What kind of construction permit do you need to submit?',
        answers: {
            'Commercial': { next: 2 },
            'Residential': { next: 3 }
        }
    },

    '3': {
        text: 'Is it one of the following projects?',
        caption: '

Replacing windows and/or exterior doors of the same size and same location

Adding/Removing siding or brick

Repairing a roof to the extent of replacing decking boards/fascia

Repairing foundation without increasing impervious cover

Roof mounted solar panels

Remove/Repair drywall in excess of 32 sq. ft.

Adding/Replacing insulation

Tub/Shower Conversions

',
        answers: {
            'Yes': { next: 4 },
            'No': { next: 2 }
        }
    },

    '4': {
        text: "Will this project involve any structural members<span style='color:#a1561f;' title='Support that is a consitiuent part of any structure or building, for example:\n\n\Beam - long thick piece of wood or metal or concrete, etc., used in construction\nBracing, brace - a structural member used to stiffen a framework\nPlate - structural member consisting of a horizontal beam that provides bearing and anchorage\nRiser - structural member consisting of the vertical part of a stair or step\nSill - structural member consisting of a continuous horizontal timber forming the lowest member of a framework or supporting structure\nStructure, construction - a thing constructed; a complex entity constructed of many parts; \nSupport - any device that bears the weight of another thing; \nTread - structural member consisting of the horizontal part of a stair or step\nUpright, vertical - a vertical structural member as a post or stake; \n' > [?] or increase square footage of structure?",
        answers: {
            'Yes': { next: 2 },
            'No': { next: 5 }
        },
    },
    '5': {
        text: 'You`re almost done!  You have qualified for an express permit.',
        caption:'

There are a few more steps to check for any necessary prerequisites.

Check your property for NRHD Status and HD or H zoning designations

Click the link below. Then enter your address into the search bar. Change “Map Themes” to zoning map, and in “Layers” under “Zoning and Zoning Overlays” check the box for Airport Overlay and National Register of Historic Districts http://www.austintexas.gov/GIS/DevelopmentWebMap/Viewer.aspx

Is your property in a NRHD or in a H or HD?

',
        answers: {
            'Yes': { next:6, checklist:'Please schedule an appointment with the Historic District'},
            'No': {next:6 }
        }
  },

  '6': {
    text: 'Check your property for Airport Overlay Zoning',
    caption: '

Is your property in a Airport Overlay Zone?

Click the link below. Then enter your address into the search bar. Change “Map Themes” to zoning map, and in “Layers” under “Zoning and Zoning Overlays” check the box for Airport Overlay and National Register of Historic Districts http://www.austintexas.gov/GIS/DevelopmentWebMap/Viewer.aspx

',
    answers: {
      'Yes': { next:7, checklist:'Please schedule an appointment with the Aviation Department'},
      'No': { next:7}
    }
  },

    '7': {
        text: 'Check your proximity to a flood plain',
		caption: '

Is your property within 150 feet of a floodplain?

Visit either of the links below.

www.ATXFloodPro.com or the City’s Floodplain Development Information website http://austintexas.gov/page/floodplain-development-information

',
        answers: {
            'Yes': { next: 8, checklist: 'Please bring a FEMA elevation certificate form with your application' },
            'No': { next: 8 }
        }
    },

    '8': {
        text: "Check your property for expired permits",
		caption: '

Does your property have any expired permits?

Check at this link: https://www.austintexas.gov/devreview/a_queryfolder_permits.jsp

',
        answers: {
            'Yes': { next: 9 },
            'No': { next: 12 }
        }
    },

    '9': {
        text: 'Does the expired permit work qualify for the express permit?',
        answers: {
            'Yes': { next: 12, checklist: 'Add the expired permit work to the current permit' },
            'No': { next: 12, checklist: 'Bring an Acknowledgement of Expired Permit form with your application' }
        }
    },

    '12': {
        permitId:1
    }

};
<|endoftext|>


<commit_before>/**
 * Unittests: javascript loader
 */

// or "localhost"
var s = "http://cloudpanic.info:3000";

module('Load');

asyncTest('1 file', 3, function () {

    head.ready("test1", function() {
       ok(true, "head.ready");
    });

    head.js(s + "/test1?value=1", function() {
        start();
        equals(test1, 1);
    });

    head.ready("test1", function() {
       ok(true, "head.ready");
    });

});

asyncTest('2 files', 2, function () {

    head.js(s + "/test1?value=2", s + "/test2?value=2", function() {
        start();
        equals(test1, 2);
        equals(test2, 2);
    });

});

asyncTest('2 files in order', 1, function () {

    head.js(s + "/dep1?value=1&time=100", s + "/test3?value=1&require=dep1", function() {
        start();
        equals(test3, 1);
    });

});

asyncTest('5 files in order', 3, function () {

    head.ready("dep4", function() {
        ok(true, "dep4 ready");
    });

    head.js(
        s + "/dep2?value=1&time=400",
        s + "/dep3?value=1&time=300&require=dep2",
        s + "/dep4?value=1&time=200&require=dep3",
        s + "/dep5?value=1&time=100&require=dep4",
        s + "/test3?value=2&require=dep5",

        function() {
            start();
            equals(test3, 2);
        }
    );

    head.ready("test3", function() {
        ok(dep2 && dep3 && dep4 && dep5 && test3, "all ready");
    });

});

asyncTest("document ready", function() {
    head.ready(document, function() {
        start();
        ok(!!document.getElementById("qunit-header"));
    });
});

<commit_msg>Test case for issue 128<commit_after>/**
 * Unittests: javascript loader
 */

// or "localhost"
var s = "http://cloudpanic.info:3000";

module('Load');

asyncTest('1 file', 3, function () {

    head.ready("test1", function() {
       ok(true, "head.ready");
    });

    head.js(s + "/test1?value=1", function() {
        start();
        equals(test1, 1);
    });

    head.ready("test1", function() {
       ok(true, "head.ready");
    });

});

asyncTest('2 files', 2, function () {

    head.js(s + "/test1?value=2", s + "/test2?value=2", function() {
        start();
        equals(test1, 2);
        equals(test2, 2);
    });

});

asyncTest('2 files in order', 1, function () {

    head.js(s + "/dep1?value=1&time=100", s + "/test3?value=1&require=dep1", function() {
        start();
        equals(test3, 1);
    });

});

asyncTest('5 files in order', 3, function () {

    head.ready("dep4", function() {
        ok(true, "dep4 ready");
    });

    head.js(
        s + "/dep2?value=1&time=400",
        s + "/dep3?value=1&time=300&require=dep2",
        s + "/dep4?value=1&time=200&require=dep3",
        s + "/dep5?value=1&time=100&require=dep4",
        s + "/test3?value=2&require=dep5",

        function() {
            start();
            equals(test3, 2);
        }
    );

    head.ready("test3", function() {
        ok(dep2 && dep3 && dep4 && dep5 && test3, "all ready");
    });

});

asyncTest('nested load', function () {

    head.js(s + "/dep6?value=1", function() {

        head.js(s + "/test4?value=1&require=dep6", function() {
            start();
            equals(test4, 1);
        });

    });

});

asyncTest("document ready", function() {
    head.ready(document, function() {
        start();
        ok(!!document.getElementById("qunit-header"));
    });
});

<|endoftext|>


<commit_before>exports.defineAutoTests = function () {

    describe('Write/Read Tests', function () {

    });

};<commit_msg>Added a test for object<commit_after>exports.defineAutoTests = function () {

    describe('Write/Read Tests', function () {
        it('Objects', function () {
            var dummyData = { data1: "", data2: 2, data3: 3.0 };
            NativeStorage.set("ref_obj",
                dummyData,
                function (result) {
                    NativeStorage.getObject("ref_obj",
                        function (result) {
                            expect(result).toEqual(dummyData);
                        },
                        function (e) {
                            fail("Read Object Failed");
                        });
                },
                function (e) {
                    fail("Write Object Failed");
                });

        });
    });

};<|endoftext|>


<commit_before>'use strict';

/**
 * Create a markdown-formatted reference link from the given values.
 *
 * ```js
 * utils.reference(alt, url);
 * //=> [template]: https://github/jonschlinkert/template "Make stuff!"
 * ```
 *
 * @param  {String} `id`
 * @param  {String} `url`
 * @param  {String} `alt`
 * @return {String}
 * @api public
 */

module.exports = function reference(id, url, alt) {
  return '[' + id + ']: ' + url + ' "' + alt + '"';
};
<commit_msg>fixes reference link<commit_after>'use strict';

/**
 * Create a markdown-formatted reference link from the given values.
 *
 * ```js
 * utils.reference(alt, url);
 * //=> [template]: https://github/jonschlinkert/template "Make stuff!"
 * ```
 *
 * @param  {String} `id`
 * @param  {String} `url`
 * @param  {String} `alt`
 * @return {String}
 * @api public
 */

module.exports = function reference(id, url, alt) {
  return '[' + id + ']: ' + url + (alt ? (' "' + alt + '"') : '');
};
<|endoftext|>


<commit_before>({
	mainConfigFile: 'require.config.js',
	name: "WoltLab/WCF/Bootstrap",
	out: "WCF.Combined.min.js",
	useStrict: true,
	paths: {
		"requireLib": "require",

		"jquery": "empty:"
	},
	include: [
		"requireLib"
	]
})
<commit_msg>Automatically scan all modules in require.build.js<commit_after>({
	mainConfigFile: 'require.config.js',
	name: "WoltLab/_Meta",
	out: "WCF.Core.min.js",
	useStrict: true,
	preserveLicenseComments: false,
	optimize: 'uglify2',
	uglify2: {},
	paths: {
		"requireLib": "require",

		"jquery": "empty:",
	},
	include: [
		"requireLib",
	],
	excludeShallow: [
		'WoltLab/_Meta',
	],
	rawText: {
		'WoltLab/_Meta': 'define([], function () {});',
	},
	onBuildRead: function (moduleName, path, contents) {
		if (!process.versions.node) {
			throw new Error('You need to run node.js');
		}

		if (moduleName === 'WoltLab/_Meta') {
			if (global.allModules == undefined) {
				var fs = module.require('fs')
				  , path = module.require('path');
				global.allModules = [];
				var queue = [ 'WoltLab' ];
				var folder;
				while (folder = queue.shift()) {
					var files = fs.readdirSync(folder);
					for (var i = 0; i < files.length; i++) {
						var filename = path.join(folder, files[i]);
						if (path.extname(filename) == '.js') {
							global.allModules.push(filename);
						}
						else if (fs.statSync(filename).isDirectory()) {
							queue.push(filename);
						}
					}
				}
			}

			return 'define([' + global.allModules.map(function (item) { return "'" + item.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"; }).join(', ') + '], function () { });';
		}

		return contents;
	},
})
<|endoftext|>


<commit_before>window.onload = function() {
  var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    width = canvas.width = window.innerWidth,
    height = canvas.height = window.innerHeight;

  var centerY = height / 2,
      centerX = width / 2,
      radius = 200,
      speed = 0.01,
      angle = 0,
      x, y;

  render();

  function render() {
    context.clearRect(0, 0, width, height);
    x = centerX + Math.cos(angle) * radius;
    y = centerY + Math.sin(angle) * radius;
    context.beginPath();
    context.arc(x, y, 20, 0, Math.PI * 2, false);
    context.arc(centerX, centerY, 40, 0, Math.PI * 2, false);
    context.fill();

    angle += speed;
    requestAnimationFrame(render);
  }
};
<commit_msg>Make it an ellipse!<commit_after>window.onload = function() {
  var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    width = canvas.width = window.innerWidth,
    height = canvas.height = window.innerHeight;

  var centerY = height / 2,
      centerX = width / 2,
      xRadius = 200,
      yRadius = 400,
      speed = 0.01,
      angle = 0,
      x, y;

  render();

  function render() {
    context.clearRect(0, 0, width, height);
    x = centerX + Math.cos(angle) * xRadius;
    y = centerY + Math.sin(angle) * yRadius;
    context.beginPath();
    context.arc(x, y, 20, 0, Math.PI * 2, false);
    context.arc(centerX, centerY, 40, 0, Math.PI * 2, false);
    context.fill();

    angle += speed;
    requestAnimationFrame(render);
  }
};
<|endoftext|>


<commit_before>var getSetRelevance = require('./pure/setrelevance'),
    coalesceZooms = require('./pure/coalescezooms'),
    queue = require('queue-async'),
    feature = require('./util/feature'),
    cleanFeature = require('./util/ops').feature;

module.exports = relevSort;

// Given that we've geocoded potential results in multiple sources, given
// arrays of `feats` and `grids` of the same length, combine matches that
// are over the same point, factoring in the zoom levels on which they
// occur.
// Calls `callback` with `(err, contexts, relevd)` in which
//
// @param `contexts` is an array of bboxes which are assigned scores
// @param `relevd` which is an object mapping place ids to places
// @param {Object} geocoder the geocoder instance
// @param {Array} feats an array of feature objects
// @param {Array} grids an array of grid objects
// @param {Array} zooms an array of zoom numbers
// @param {Function} callback
function relevSort(indexes, types, data, geocoder, feats, grids, zooms, callback) {

    // Combine the scores for each match across multiple grids and zoom levels,
    // producing a mapping from `zxy` to matches
    var coalesced = coalesceZooms(grids, feats, types, zooms, indexes);

    var sets = {},
        i, j, c, l,
        feat,
        rowMemo = {},
        rows,
        relev,
        fullid;

    for (c in coalesced) {
        rows = coalesced[c];
        // Sort by db, relev such that total relev can be
        // calculated without results for the same db being summed.
        rows.sort(sortRelevReason);
        relev = getSetRelevance(data.query, rows);

        // A threshold here reduces results early.
        // if (relev < 0.75) continue;

        for (i = 0, l = rows.length; i < l; i++) {
            fullid = rows[i].db + '.' + rows[i].id;
            sets[fullid] = sets[fullid] || rows[i];
            rowMemo[rows[i].tmpid] = rowMemo[rows[i].tmpid] || {
                db: rows[i].db,
                id: rows[i].id,
                tmpid: rows[i].tmpid,
                relev: relev
            };
        }
    }

    var results = [],
        contexts = [],
        formatted = [],
        memo = null;

    for (j in rowMemo) {
        results.push(rowMemo[j]);
    }

    results.sort(sortByRelev);

    for (var k = 0; k < results.length; k++) {
        feat = results[k];
        if (memo === null || memo - feat.relev < 0.5) {
            memo = feat.relev;
            formatted.push(feat);
        }
    }

    results = formatted;

    data.stats.relevTime = +new Date() - data.stats.relevTime;
    data.stats.relevCount = results.length;

    if (!results.length) return callback(null, results);

    // Disallow more than 50 of the best results at this point.
    if (results.length > 50) results = results.slice(0, 50);

    var start = +new Date();
    var q = queue();

    results.forEach(function(term) {
        q.defer(loadResult, term);
    });

    q.awaitAll(function(err) {
        if (err) return callback(err);
        data.stats.contextTime = +new Date() - start;
        data.stats.contextCount = contexts.length;
        return callback(null, contexts, sets);
    });

    // For each result, load the feature from its Carmen index so that we can
    // then load all of the relevant contexts for that feature
    function loadResult(term, callback) {
        var cq = queue();
        var source = geocoder.indexes[term.db];
        feature.getFeature(source, term.id, featureLoaded);
        function featureLoaded(err, features) {
            if (err) return callback(err);
            // @TODO determine if this needs to be more selective with
            // which contexts are loaded.
            // if (Object.keys(features).length > 1) console.log(features);
            for (var id in features) cq.defer(function(feat, callback) {
                geocoder._contextByFeature(cleanFeature(term.id, term.db, feat), callback);
            }, features[id]);
            cq.awaitAll(contextLoaded);
        }
        function contextLoaded(err, loaded) {
            if (err) return callback(err);
            contexts.push.apply(contexts, loaded);
            callback();
        }
    }

    function sortRelevReason(a, b) {
        var ai = types.indexOf(a.db);
        var bi = types.indexOf(b.db);
        if (ai < bi) return -1;
        else if (ai > bi) return 1;
        else if (a.relev > b.relev) return -1;
        else if (a.relev < b.relev) return 1;
        else if (a.reason > b.reason) return -1;
        else if (a.reason < b.reason) return 1;
        else if (a.id < b.id) return -1;
        else if (a.id > b.id) return 1;
        return 0;
    }
}

function sortByRelev(a, b) {
    return a.relev > b.relev ? -1 :
        a.relev < b.relev ? 1 :
        a.tmpid < b.tmpid ? -1 :
        a.tmpid > b.tmpid ? 1 : 0;
}
<commit_msg>Verify all packed features by confirming that they are part of the coalesced result set.<commit_after>var getSetRelevance = require('./pure/setrelevance'),
    coalesceZooms = require('./pure/coalescezooms'),
    sm = new(require('sphericalmercator'))(),
    ops = require('./util/ops'),
    mp2_14 = Math.pow(2, 14),
    mp2_28 = Math.pow(2, 28),
    queue = require('queue-async'),
    feature = require('./util/feature'),
    cleanFeature = require('./util/ops').feature;

module.exports = relevSort;

// Given that we've geocoded potential results in multiple sources, given
// arrays of `feats` and `grids` of the same length, combine matches that
// are over the same point, factoring in the zoom levels on which they
// occur.
// Calls `callback` with `(err, contexts, relevd)` in which
//
// @param `contexts` is an array of bboxes which are assigned scores
// @param `relevd` which is an object mapping place ids to places
// @param {Object} geocoder the geocoder instance
// @param {Array} feats an array of feature objects
// @param {Array} grids an array of grid objects
// @param {Array} zooms an array of zoom numbers
// @param {Function} callback
function relevSort(indexes, types, data, geocoder, feats, grids, zooms, callback) {

    // Combine the scores for each match across multiple grids and zoom levels,
    // producing a mapping from `zxy` to matches
    var coalesced = coalesceZooms(grids, feats, types, zooms, indexes);

    var sets = {},
        i, j, c, l,
        feat,
        rowMemo = {},
        rows,
        relev,
        fullid;

    for (c in coalesced) {
        rows = coalesced[c];
        // Sort by db, relev such that total relev can be
        // calculated without results for the same db being summed.
        rows.sort(sortRelevReason);
        relev = getSetRelevance(data.query, rows);

        // A threshold here reduces results early.
        // if (relev < 0.75) continue;

        for (i = 0, l = rows.length; i < l; i++) {
            fullid = rows[i].db + '.' + rows[i].id;
            sets[fullid] = sets[fullid] || rows[i];
            rowMemo[rows[i].tmpid] = rowMemo[rows[i].tmpid] || {
                db: rows[i].db,
                id: rows[i].id,
                tmpid: rows[i].tmpid,
                relev: relev
            };
        }
    }

    var results = [],
        contexts = [],
        formatted = [],
        memo = null;

    for (j in rowMemo) {
        results.push(rowMemo[j]);
    }

    results.sort(sortByRelev);

    for (var k = 0; k < results.length; k++) {
        feat = results[k];
        if (memo === null || memo - feat.relev < 0.5) {
            memo = feat.relev;
            formatted.push(feat);
        }
    }

    results = formatted;

    data.stats.relevTime = +new Date() - data.stats.relevTime;
    data.stats.relevCount = results.length;

    if (!results.length) return callback(null, results);

    // Disallow more than 50 of the best results at this point.
    if (results.length > 50) results = results.slice(0, 50);

    var start = +new Date();
    var q = queue();

    results.forEach(function(term) {
        q.defer(loadResult, term);
    });

    q.awaitAll(function(err) {
        if (err) return callback(err);
        data.stats.contextTime = +new Date() - start;
        data.stats.contextCount = contexts.length;
        return callback(null, contexts, sets);
    });

    // For each result, load the feature from its Carmen index so that we can
    // then load all of the relevant contexts for that feature
    function loadResult(term, callback) {
        var cq = queue();
        var source = geocoder.indexes[term.db];
        feature.getFeature(source, term.id, featureLoaded);
        function featureLoaded(err, features) {
            if (err) return callback(err);
            for (var id in features) {
                // To exclude false positives from feature hash collisions
                // check the feature lon/lat coord and ensure it is in the
                // coalesced result set.
                // @TODO for fully accurate results, iterate through
                // coalesced[coord] for a matching feature id.
                var bbox = sm.xyz([features[id].lon, features[id].lat, features[id].lon, features[id].lat], source._geocoder.zoom);
                var coord = (source._geocoder.zoom * mp2_28) + (bbox.minX * mp2_14) + (bbox.minY);
                if (coalesced[coord]) cq.defer(function(feat, callback) {
                    geocoder._contextByFeature(cleanFeature(term.id, term.db, feat), callback);
                }, features[id]);
            }
            cq.awaitAll(contextLoaded);
        }
        function contextLoaded(err, loaded) {
            if (err) return callback(err);
            contexts.push.apply(contexts, loaded);
            callback();
        }
    }

    function sortRelevReason(a, b) {
        var ai = types.indexOf(a.db);
        var bi = types.indexOf(b.db);
        if (ai < bi) return -1;
        else if (ai > bi) return 1;
        else if (a.relev > b.relev) return -1;
        else if (a.relev < b.relev) return 1;
        else if (a.reason > b.reason) return -1;
        else if (a.reason < b.reason) return 1;
        else if (a.id < b.id) return -1;
        else if (a.id > b.id) return 1;
        return 0;
    }
}

function sortByRelev(a, b) {
    return a.relev > b.relev ? -1 :
        a.relev < b.relev ? 1 :
        a.tmpid < b.tmpid ? -1 :
        a.tmpid > b.tmpid ? 1 : 0;
}
<|endoftext|>


<commit_before>var protocol = require('hypercore-protocol')
var bitfield = require('./bitfield')
var rle = require('bitfield-rle')
var unordered = require('unordered-set')

module.exports = replicate

function replicate (core, feed, opts) {
  if (!opts) opts = {}

  var stream = opts.stream

  if (!stream) {
    stream = protocol({id: core.id, private: opts.private !== false})
    stream.setTimeout(opts.timeout || 5000, stream.destroy)
  }

  if (feed) {
    feed.open(function (err) {
      if (err) return stream.destroy(err)
      ready(stream, feed)
    })
  }

  return stream
}

function Peer (feed) {
  this._tick = 0
  this._index = -1

  this.maxRequests = 16 // arbitrary for now. should be more flex in the future
  this.feed = feed
  this.channel = null

  this.remotePausing = false
  this.remoteBitfield = bitfield(16)
  this.remoteRequests = []

  this.pausing = false
  this.responses = []
  this.requests = []
}

// called by the feed when it has a new block

Peer.prototype.have = function (block) {
  if (this.channel.opened) this.channel.have({start: block})
}

// expose the update methods for easier debugging

Peer.prototype.update = function () {
  while (true) {
    if (this.requests.length >= this.maxRequests) return

    var message = pickBlock(this)
    if (!message) return

    this.requests.push(message.block)
    this.feed.peersRequesting.set(message.block, true)
    this.channel.request(message)
  }
}

Peer.prototype.updateAll = function () {
  for (var i = 0; i < this.feed.peers.length; i++) {
    this.feed.peers[i].update()
  }
}

function ready (stream, feed) {
  var peer = new Peer(feed)
  var channel = peer.channel = stream.open(feed.key, {state: peer})

  unordered.add(feed.peers, peer)

  channel.on('have', onhave)
  channel.on('want', onwant)
  channel.on('request', onrequest)
  channel.on('data', ondata)
  channel.on('cancel', oncancel)
  channel.on('pause', onpause)
  channel.on('resume', onresume)

  // meta events
  channel.on('tick', ontick)
  channel.on('end', onend)

  if (channel.opened) onopen.call(channel)
  else channel.on('open', onopen)
}

function onopen () {
  var peer = this.state

  peer.channel.have({
    start: 0,
    bitfield: rle.encode(peer.feed.bitfield.buffer)
  })
}

function onwant (message) {
  var peer = this.state
  var byteOffset = Math.floor(message.start / 8)

  peer.channel.have({
    start: byteOffset * 8,
    bitfield: rle.encode(this.feed.bitfield.buffer.slice(byteOffset))
  })
}

function onhave (message) {
  var peer = this.state

  // TODO: we should protect better / make configurable how big the BF grows

  var start = message.start
  var end = message.end || (start + 1)

  if (message.bitfield) {
    var bits = bitfield(rle.decode(message.bitfield))
    for (var i = 0; i < bits.length; i++) {
      peer.remoteBitfield.set(start + i, bits.get(i))
    }
  } else {
    while (start < end) {
      peer.remoteBitfield.set(start++, true)
    }
  }

  peer.update() // TODO: if previous update failed we should only look in the update state
}

function onrequest (message) {
  var peer = this.state

  if (peer.remoteRequests.push(message) === 1) read(peer)
}

function ondata (message) {
  var peer = this.state

  if (remove(peer.requests, message.block) === 0) this._tick = 0
  if (peer.responses.push(message) === 1) write(peer)

  peer.update()
}

function oncancel (message) {
  var peer = this.state

  for (var i = 0; i < peer.remoteRequests.length; i++) {
    var req = peer.remoteRequests[i]

    if (req.block === message.block && req.bytes === message.bytes) {
      if (i) peer.remoteRequests.splice(i, 1)
      else peer.remoteRequests.shift()
      return
    }
  }
}

function onpause (message) {
  var peer = this.state

  peer.remotePausing = true
  while (peer.requests.length) {
    peer.feed.peersRequesting.set(peer.requests.shift(), false)
  }
  peer._tick = 0
  peer.updateAll()
  peer.emit('pause')
}

function onresume (message) {
  var peer = this.state

  peer.remotePausing = false
  peer.update()
}

function ontick () {
  var peer = this.state

  if (!peer.requests.length || ++peer._tick < 4) return

  // timeout!

  peer.feed.peersRequesting.set(peer.requests.shift(), false)
  peer.updateAll()
}

function onend () {
  var peer = this.state

  while (peer.requests.length) {
    peer.feed.peersRequesting.set(peer.requests.shift(), false)
  }

  unordered.remove(peer.feed.peers, peer)
  peer.updateAll()
  peer.emit('close')
}

function pickBlock (self) {
  for (var i = 0; i < self.feed.prioritized.length; i++) {
    var message = self.feed.prioritized[i]

    if (message.block === -1) {
      message.block = pickUnprioritizedBlock(self)
      if (message.block === -1) continue
    }

    message.nodes = self.feed.digest(message.block)
    if (validBlock(self, message.block)) return message
  }

  var block = pickUnprioritizedBlock(self)
  if (block === -1) return null

  return {
    block: block,
    nodes: self.feed.digest(block)
  }
}

function pickUnprioritizedBlock (self) {
  var i = 0
  var offset = Math.floor(Math.random() * self.remoteBitfield.length)

  for (i = offset; i < self.remoteBitfield.length; i++) {
    if (validBlock(self, i)) return i
  }
  for (i = 0; i < offset; i++) {
    if (validBlock(self, i)) return i
  }

  return -1
}

function validBlock (self, i) {
  return self.remoteBitfield.get(i) && !self.feed.bitfield.get(i) && !self.feed.peersRequesting.get(i)
}

function remove (list, val) {
  var i = list.indexOf(val)
  if (i === 0) list.shift()
  else if (i > -1) list.splice(i, 1)
  return i
}

function write (self) {
  var next = self.responses[0]

  self.feed.put(next.block, next.value, next, function (err) {
    if (err) return self.channel.destroy(err)

    self.feed.emit('download', next.block, next.value)
    self.responses.shift()
    if (self.responses.length) write(self)
  })
}

function readBytes (self) {
  var next = self.remoteRequests[0]

  self.feed._seek(next.bytes, function (err, index) {
    if (err || index & 1) { // Could not fulfil byte request
      next.bytes = 0
      read(self)
      return
    }

    var block = index / 2

    if (self.remoteBitfield.get(block)) {
      self.remoteRequests.shift()
      if (self.remoteRequests.length) read(self)
      return
    }

    // TODO: use nodes
    self.remoteRequests.unshift({
      block: block,
      bytes: 0,
      hash: self.hash
    })

    read(self)
  })
}

function read (self) { // TODO: use a remote tree as well
  var next = self.remoteRequests[0]

  while (self.remoteBitfield.get(next.block) && !next.bytes) {
    self.remoteRequests.shift()
    if (!self.remoteRequests.length) return
    next = self.remoteRequests[0]
  }

  var opts = {digest: next.nodes}
  if (next.bytes) return readBytes(self)

  self.feed.proof(next.block, opts, function (err, proof) {
    if (err) return self.channel.destroy(err)

    self.feed.get(next.block, function (err, data) {
      if (err) return self.channel.destroy(err)

      self.feed.emit('upload', next.block, data)
      self.remoteBitfield.set(next.block, true)
      self.channel.data({
        block: next.block,
        value: data,
        nodes: proof.nodes,
        signature: proof.signature
      })

      self.remoteRequests.shift()
      if (self.remoteRequests.length) read(self)
    })
  })
}
<commit_msg>add hash comment<commit_after>var protocol = require('hypercore-protocol')
var bitfield = require('./bitfield')
var rle = require('bitfield-rle')
var unordered = require('unordered-set')

module.exports = replicate

function replicate (core, feed, opts) {
  if (!opts) opts = {}

  var stream = opts.stream

  if (!stream) {
    stream = protocol({id: core.id, private: opts.private !== false})
    stream.setTimeout(opts.timeout || 5000, stream.destroy)
  }

  if (feed) {
    feed.open(function (err) {
      if (err) return stream.destroy(err)
      ready(stream, feed)
    })
  }

  return stream
}

function Peer (feed) {
  this._tick = 0
  this._index = -1

  this.maxRequests = 16 // arbitrary for now. should be more flex in the future
  this.feed = feed
  this.channel = null

  this.remotePausing = false
  this.remoteBitfield = bitfield(16)
  this.remoteRequests = []

  this.pausing = false
  this.responses = []
  this.requests = []
}

// called by the feed when it has a new block

Peer.prototype.have = function (block) {
  if (this.channel.opened) this.channel.have({start: block})
}

// expose the update methods for easier debugging

Peer.prototype.update = function () {
  while (true) {
    if (this.requests.length >= this.maxRequests) return

    var message = pickBlock(this)
    if (!message) return

    this.requests.push(message.block)
    this.feed.peersRequesting.set(message.block, true)
    this.channel.request(message)
  }
}

Peer.prototype.updateAll = function () {
  for (var i = 0; i < this.feed.peers.length; i++) {
    this.feed.peers[i].update()
  }
}

function ready (stream, feed) {
  var peer = new Peer(feed)
  var channel = peer.channel = stream.open(feed.key, {state: peer})

  unordered.add(feed.peers, peer)

  channel.on('have', onhave)
  channel.on('want', onwant)
  channel.on('request', onrequest)
  channel.on('data', ondata)
  channel.on('cancel', oncancel)
  channel.on('pause', onpause)
  channel.on('resume', onresume)

  // meta events
  channel.on('tick', ontick)
  channel.on('end', onend)

  if (channel.opened) onopen.call(channel)
  else channel.on('open', onopen)
}

function onopen () {
  var peer = this.state

  peer.channel.have({
    start: 0,
    bitfield: rle.encode(peer.feed.bitfield.buffer)
  })
}

function onwant (message) {
  var peer = this.state
  var byteOffset = Math.floor(message.start / 8)

  peer.channel.have({
    start: byteOffset * 8,
    bitfield: rle.encode(this.feed.bitfield.buffer.slice(byteOffset))
  })
}

function onhave (message) {
  var peer = this.state

  // TODO: we should protect better / make configurable how big the BF grows

  var start = message.start
  var end = message.end || (start + 1)

  if (message.bitfield) {
    var bits = bitfield(rle.decode(message.bitfield))
    for (var i = 0; i < bits.length; i++) {
      peer.remoteBitfield.set(start + i, bits.get(i))
    }
  } else {
    while (start < end) {
      peer.remoteBitfield.set(start++, true)
    }
  }

  peer.update() // TODO: if previous update failed we should only look in the update state
}

function onrequest (message) {
  var peer = this.state

  if (peer.remoteRequests.push(message) === 1) read(peer)
}

function ondata (message) {
  var peer = this.state

  if (remove(peer.requests, message.block) === 0) this._tick = 0
  if (peer.responses.push(message) === 1) write(peer)

  peer.update()
}

function oncancel (message) {
  var peer = this.state

  for (var i = 0; i < peer.remoteRequests.length; i++) {
    var req = peer.remoteRequests[i]

    if (req.block === message.block && req.bytes === message.bytes) {
      if (i) peer.remoteRequests.splice(i, 1)
      else peer.remoteRequests.shift()
      return
    }
  }
}

function onpause (message) {
  var peer = this.state

  peer.remotePausing = true
  while (peer.requests.length) {
    peer.feed.peersRequesting.set(peer.requests.shift(), false)
  }
  peer._tick = 0
  peer.updateAll()
  peer.emit('pause')
}

function onresume (message) {
  var peer = this.state

  peer.remotePausing = false
  peer.update()
}

function ontick () {
  var peer = this.state

  if (!peer.requests.length || ++peer._tick < 4) return

  // timeout!

  peer.feed.peersRequesting.set(peer.requests.shift(), false)
  peer.updateAll()
}

function onend () {
  var peer = this.state

  while (peer.requests.length) {
    peer.feed.peersRequesting.set(peer.requests.shift(), false)
  }

  unordered.remove(peer.feed.peers, peer)
  peer.updateAll()
  peer.emit('close')
}

function pickBlock (self) {
  for (var i = 0; i < self.feed.prioritized.length; i++) {
    var message = self.feed.prioritized[i]

    if (message.block === -1) {
      message.block = pickUnprioritizedBlock(self)
      if (message.block === -1) continue
    }

    message.nodes = self.feed.digest(message.block)
    if (validBlock(self, message.block)) return message
  }

  var block = pickUnprioritizedBlock(self)
  if (block === -1) return null

  return {
    block: block,
    nodes: self.feed.digest(block)
  }
}

function pickUnprioritizedBlock (self) {
  var i = 0
  var offset = Math.floor(Math.random() * self.remoteBitfield.length)

  for (i = offset; i < self.remoteBitfield.length; i++) {
    if (validBlock(self, i)) return i
  }
  for (i = 0; i < offset; i++) {
    if (validBlock(self, i)) return i
  }

  return -1
}

function validBlock (self, i) {
  return self.remoteBitfield.get(i) && !self.feed.bitfield.get(i) && !self.feed.peersRequesting.get(i)
}

function remove (list, val) {
  var i = list.indexOf(val)
  if (i === 0) list.shift()
  else if (i > -1) list.splice(i, 1)
  return i
}

function write (self) {
  var next = self.responses[0]

  self.feed.put(next.block, next.value, next, function (err) {
    if (err) return self.channel.destroy(err)

    self.feed.emit('download', next.block, next.value)
    self.responses.shift()
    if (self.responses.length) write(self)
  })
}

function readBytes (self) {
  var next = self.remoteRequests[0]

  self.feed._seek(next.bytes, function (err, index) {
    if (err || index & 1) { // Could not fulfil byte request
      next.bytes = 0
      read(self)
      return
    }

    var block = index / 2

    if (self.remoteBitfield.get(block)) {
      self.remoteRequests.shift()
      if (self.remoteRequests.length) read(self)
      return
    }

    // TODO: use nodes
    self.remoteRequests.unshift({
      block: block,
      bytes: 0,
      hash: self.hash
    })

    read(self)
  })
}

function read (self) { // TODO: use a remote tree as well
  var next = self.remoteRequests[0]

  while (self.remoteBitfield.get(next.block) && !next.bytes) {
    self.remoteRequests.shift()
    if (!self.remoteRequests.length) return
    next = self.remoteRequests[0]
  }

  // TODO: support hash responses

  var opts = {digest: next.nodes}
  if (next.bytes) return readBytes(self)

  self.feed.proof(next.block, opts, function (err, proof) {
    if (err) return self.channel.destroy(err)

    self.feed.get(next.block, function (err, data) {
      if (err) return self.channel.destroy(err)

      self.feed.emit('upload', next.block, data)
      self.remoteBitfield.set(next.block, true)
      self.channel.data({
        block: next.block,
        value: data,
        nodes: proof.nodes,
        signature: proof.signature
      })

      self.remoteRequests.shift()
      if (self.remoteRequests.length) read(self)
    })
  })
}
<|endoftext|>


<commit_before>var protocol = require('hypercore-protocol')
var bitfield = require('sparse-bitfield')
var set = require('unordered-set')
var rle = require('bitfield-rle')

module.exports = replicate

function replicate (feed, opts) {
  var stream = opts.stream

  if (!stream) {
    if (!opts.expectedFeeds) opts.expectedFeeds = 1
    if (!opts.id) opts.id = feed.id
    stream = protocol(opts)
  }

  feed.ready(function (err) {
    if (err) return stream.destroy(err)
    if (stream.destroyed) return

    var peer = new Peer(feed, opts)
    peer.feed = feed
    peer.stream = stream.feed(feed.key, {peer: peer})
    peer.ready()
  })

  return stream
}

function Peer (feed, opts) {
  this.feed = feed
  this.stream = null // set by replicate just after creation
  this.remoteBitfield = null
  this.remoteLength = 0
  this.live = !!opts.live

  this.downloading = !feed.writable
  this.uploading = true

  this.maxRequests = opts.maxRequests || feed.maxRequests || 16
  this.inflightRequests = []

  this._index = -1
  this._lastBytes = 0
  this._closed = false
}

Peer.prototype.onwant = function () {
  // TODO: reply to the actual want context
  var rle = this.feed.bitfield.compress()
  this.stream.have({start: 0, bitfield: rle})
}

Peer.prototype.ondata = function (data) {
  var self = this

  this.feed._putBuffer(data.index, data.value, data, this, function (err) {
    if (err) return self.destroy(err)
    self._clear(data.index, !data.value)
  })
}

Peer.prototype._clear = function (index, hash) {
  // TODO: optimize me (no splice and do not run through all ...)
  for (var i = 0; i < this.inflightRequests.length; i++) {
    if (this.inflightRequests[i].index === index) {
      this.inflightRequests.splice(i, 1)
      i--
    }
  }

  if (hash) {
    // TODO: once we compress the _reserved bitfield we should clear it always here ...
    // since we don't right now nothing is gained unless it is a hash response
    this.feed._reserved.set(index, false)
  }

  // TODO: only update all if we have overlapping selections
  this.feed._updatePeers()
}

Peer.prototype.onrequest = function (request) {
  if (request.bytes) return this._onbytes(request)

  var self = this
  var opts = {digest: request.nodes, hash: request.hash}

  this.feed.proof(request.index, opts, onproof)

  function onproof (err, proof) {
    if (err) return self.destroy(err)
    if (request.hash) onvalue(null, null)
    else self.feed._getBuffer(request.index, onvalue)

    function onvalue (err, value) {
      if (err) return self.destroy(err)

      if (value) {
        if (!self.remoteBitfield.set(request.index, true)) return
        self.feed.emit('upload', request.index, value, self)
      } else {
        if (self.remoteBitfield.get(request.index)) return
      }

      self.stream.data({
        index: request.index,
        value: value,
        nodes: proof.nodes,
        signature: proof.signature
      })
    }
  }
}

Peer.prototype._onbytes = function (request) {
  var self = this

  this.feed.seek(request.bytes, {wait: false}, function (err, index) {
    if (err) {
      request.bytes = 0
      self.onrequest(request)
      return
    }

    // quick'n'dirty filter for parallel bytes requests
    // it does not matter that this doesn't catch ALL parallel requests - just a bandwidth optimization
    if (self._lastBytes === request.bytes) return
    self._lastBytes = request.bytes

    request.bytes = 0
    request.index = index
    request.nodes = 0

    self.onrequest(request)
  })
}

Peer.prototype.ontick = function () {
  if (!this.inflightRequests.length) return

  var first = this.inflightRequests[0]
  if (--first.tick) return

  if (first.hash ? this.feed.tree.get(2 * first.index) : this.feed.bitfield.get(first.index)) {
    // prob a bytes response
    this.inflightRequests.shift()
    this.feed._reserved.set(first.index, false)
    return
  }

  this.destroy(new Error('Request timeout'))
}

Peer.prototype.onhave = function (have) {
  if (have.bitfield) { // TODO: handle start !== 0
    this.remoteBitfield = bitfield(rle.decode(have.bitfield))
    if (this.remoteBitfield.length > this.remoteLength) {
      this.remoteLength = this.remoteBitfield.length
      while (this.remoteLength && !this.remoteBitfield.get(this.remoteLength - 1)) this.remoteLength--
    }
  } else {
    if (!this.remoteBitfield) this.remoteBitfield = bitfield()
    // TODO: if len > something simply copy a 0b1111... buffer to the bitfield

    var start = have.start
    var len = have.length || 1

    while (len--) this.remoteBitfield.set(start++, true)
    if (start > this.remoteLength) this.remoteLength = start
  }

  this._updateEnd()
  this.update()
}

Peer.prototype._updateEnd = function () {
  if (this.live || this.feed.sparse || !this.feed._selections.length) return

  var sel = this.feed._selections[0]
  var remoteLength = -1

  for (var i = 0; i < this.feed.peers.length; i++) {
    if (this.feed.peers[i].remoteLength > remoteLength) {
      remoteLength = this.feed.peers[i].remoteLength
    }
  }

  sel.end = remoteLength
}

Peer.prototype.oninfo = function (info) {
  if (info.downloading || this.live) return
  this.update()
  if (this.feed._selections.length && this.downloading) return
  this.end()
}

Peer.prototype.onunhave =
Peer.prototype.onunwant =
Peer.prototype.oncancel = function () {
  // TODO: impl all of me
}

Peer.prototype.onclose = function () {
  this.destroy()
}

Peer.prototype.have = function (have) { // called by feed
  this.stream.have(have)
}

Peer.prototype.haveBytes = function (bytes) { // called by feed
  for (var i = 0; i < this.inflightRequests.length; i++) {
    if (this.inflightRequests[i].bytes === bytes) {
      this.feed._reserved.set(this.inflightRequests[i].index, false)
      this.inflightRequests.splice(i, 1)
      i--
    }
  }

  this.update()
}

Peer.prototype.update = function () {
  if (!this.downloading || !this.remoteBitfield) return

  var selections = this.feed._selections
  var waiting = this.feed._waiting
  var inflight = this.inflightRequests.length
  var offset = 0
  var i = 0

  // TODO: less duplicate code here
  // TODO: re-add priority levels

  while (inflight < this.maxRequests) {
    offset = Math.floor(Math.random() * waiting.length)

    for (i = 0; i < waiting.length; i++) {
      var w = waiting[offset++]
      if (offset === waiting.length) offset = 0

      this._downloadWaiting(w)
      if (this.inflightRequests.length >= this.maxRequests) return
    }
    if (inflight === this.inflightRequests.length) break
    inflight = this.inflightRequests.length
  }

  while (inflight < this.maxRequests) {
    offset = Math.floor(Math.random() * selections.length)

    for (i = 0; i < selections.length; i++) {
      var s = selections[offset++]
      if (offset === selections.length) offset = 0

      if (!s.iterator) s.iterator = this.feed.bitfield.iterator(s.start, s.end)
      this._downloadRange(s)
      if (this.inflightRequests.length >= this.maxRequests) return
    }

    if (inflight === this.inflightRequests.length) return
    inflight = this.inflightRequests.length
  }
}

Peer.prototype.ready = function () {
  set.add(this.feed.peers, this)
  this.stream.want({start: 0}) // TODO: don't just subscribe to *EVERYTHING* hehe
}

Peer.prototype.end = function () {
  if (!this._closed) {
    this._closed = true
    this.stream.info({downloading: false})
  } else {
    if (!this.live) this.stream.close()
  }
}

Peer.prototype.destroy = function (err) {
  if (this._index === -1) return
  set.remove(this.feed.peers, this)
  for (var i = 0; i < this.inflightRequests.length; i++) {
    this.feed._reserved.set(this.inflightRequests[i].index, false)
  }
  this._updateEnd()
  this._index = -1
  this.stream.destroy(err)
  this.feed._updatePeers()
}

Peer.prototype._downloadWaiting = function (wait) {
  if (!wait.bytes) {
    if (!this.remoteBitfield.get(wait.index) || !this.feed._reserved.set(wait.index, true)) return
    this._request(wait.index, 0, false)
    return
  }

  this._downloadRange(wait)
}

Peer.prototype._downloadRange = function (range) {
  if (!range.iterator) range.iterator = this.feed.bitfield.iterator(range.start, range.end)

  var reserved = this.feed._reserved
  var ite = range.iterator
  var wantedEnd = Math.min(range.end === -1 ? this.remoteLength : range.end, this.remoteLength)

  if (ite.end !== wantedEnd) ite.range(range.start, wantedEnd)

  var i = range.linear ? ite.next() : ite.random()
  var reset = false
  var start = i

  if (i === -1) {
    if (!range.bytes && ite.seek(0).next() === -1 && (range.end > -1 && this.remoteLength >= range.end)) {
      set.remove(this.feed._selections, range)
      range.callback(null)
      if (!this.live && !this.sparse && !this.feed._selections.length) this.end()
    }
    return
  }

  while (!this.remoteBitfield.get(i) || (range.hash && this.feed.tree.get(2 * i)) || !reserved.set(i, true)) {
    i = ite.next()
    reset = true

    if (i > -1) {
      // check this index
      continue
    }

    if (!range.linear && start !== 0) {
      // retry from the beginning since we are iterating randomly and started !== 0
      i = ite.seek(0).next()
      start = 0
      continue
    }

    // we have checked all indexes.
    // if we are looking for hashes we should check if we have all now (first check only checks blocks)
    if (range.hash) {
      // quick'n'dirty check if have all hashes - can be optimized be checking only tree roots
      // but we don't really request long ranges of hashes so yolo
      for (var j = range.start; j < wantedEnd; j++) {
        if (!this.feed.tree.get(2 * j)) return
      }
      if (!range.bytes) {
        set.remove(this.feed._selections, range)
        range.callback(null)
      }
    }

    // exit the update loop - nothing to do
    return
  }

  if (reset) ite.seek(0)

  this._request(i, range.bytes || 0, range.hash)
}

Peer.prototype._request = function (index, bytes, hash) {
  var request = {
    tick: 6,
    bytes: bytes,
    index: index,
    hash: hash,
    nodes: this.feed.digest(index)
  }

  this.inflightRequests.push(request)
  this.stream.request(request)
}
<commit_msg>Readd peer-add and peer-remove events (#83)<commit_after>var protocol = require('hypercore-protocol')
var bitfield = require('sparse-bitfield')
var set = require('unordered-set')
var rle = require('bitfield-rle')

module.exports = replicate

function replicate (feed, opts) {
  var stream = opts.stream

  if (!stream) {
    if (!opts.expectedFeeds) opts.expectedFeeds = 1
    if (!opts.id) opts.id = feed.id
    stream = protocol(opts)
  }

  feed.ready(function (err) {
    if (err) return stream.destroy(err)
    if (stream.destroyed) return

    var peer = new Peer(feed, opts)
    peer.feed = feed
    peer.stream = stream.feed(feed.key, {peer: peer})
    peer.ready()
  })

  return stream
}

function Peer (feed, opts) {
  this.feed = feed
  this.stream = null // set by replicate just after creation
  this.remoteBitfield = null
  this.remoteLength = 0
  this.live = !!opts.live

  this.downloading = !feed.writable
  this.uploading = true

  this.maxRequests = opts.maxRequests || feed.maxRequests || 16
  this.inflightRequests = []

  this._index = -1
  this._lastBytes = 0
  this._closed = false
}

Peer.prototype.onwant = function () {
  // TODO: reply to the actual want context
  var rle = this.feed.bitfield.compress()
  this.stream.have({start: 0, bitfield: rle})
}

Peer.prototype.ondata = function (data) {
  var self = this

  this.feed._putBuffer(data.index, data.value, data, this, function (err) {
    if (err) return self.destroy(err)
    self._clear(data.index, !data.value)
  })
}

Peer.prototype._clear = function (index, hash) {
  // TODO: optimize me (no splice and do not run through all ...)
  for (var i = 0; i < this.inflightRequests.length; i++) {
    if (this.inflightRequests[i].index === index) {
      this.inflightRequests.splice(i, 1)
      i--
    }
  }

  if (hash) {
    // TODO: once we compress the _reserved bitfield we should clear it always here ...
    // since we don't right now nothing is gained unless it is a hash response
    this.feed._reserved.set(index, false)
  }

  // TODO: only update all if we have overlapping selections
  this.feed._updatePeers()
}

Peer.prototype.onrequest = function (request) {
  if (request.bytes) return this._onbytes(request)

  var self = this
  var opts = {digest: request.nodes, hash: request.hash}

  this.feed.proof(request.index, opts, onproof)

  function onproof (err, proof) {
    if (err) return self.destroy(err)
    if (request.hash) onvalue(null, null)
    else self.feed._getBuffer(request.index, onvalue)

    function onvalue (err, value) {
      if (err) return self.destroy(err)

      if (value) {
        if (!self.remoteBitfield.set(request.index, true)) return
        self.feed.emit('upload', request.index, value, self)
      } else {
        if (self.remoteBitfield.get(request.index)) return
      }

      self.stream.data({
        index: request.index,
        value: value,
        nodes: proof.nodes,
        signature: proof.signature
      })
    }
  }
}

Peer.prototype._onbytes = function (request) {
  var self = this

  this.feed.seek(request.bytes, {wait: false}, function (err, index) {
    if (err) {
      request.bytes = 0
      self.onrequest(request)
      return
    }

    // quick'n'dirty filter for parallel bytes requests
    // it does not matter that this doesn't catch ALL parallel requests - just a bandwidth optimization
    if (self._lastBytes === request.bytes) return
    self._lastBytes = request.bytes

    request.bytes = 0
    request.index = index
    request.nodes = 0

    self.onrequest(request)
  })
}

Peer.prototype.ontick = function () {
  if (!this.inflightRequests.length) return

  var first = this.inflightRequests[0]
  if (--first.tick) return

  if (first.hash ? this.feed.tree.get(2 * first.index) : this.feed.bitfield.get(first.index)) {
    // prob a bytes response
    this.inflightRequests.shift()
    this.feed._reserved.set(first.index, false)
    return
  }

  this.destroy(new Error('Request timeout'))
}

Peer.prototype.onhave = function (have) {
  if (have.bitfield) { // TODO: handle start !== 0
    this.remoteBitfield = bitfield(rle.decode(have.bitfield))
    if (this.remoteBitfield.length > this.remoteLength) {
      this.remoteLength = this.remoteBitfield.length
      while (this.remoteLength && !this.remoteBitfield.get(this.remoteLength - 1)) this.remoteLength--
    }
  } else {
    if (!this.remoteBitfield) this.remoteBitfield = bitfield()
    // TODO: if len > something simply copy a 0b1111... buffer to the bitfield

    var start = have.start
    var len = have.length || 1

    while (len--) this.remoteBitfield.set(start++, true)
    if (start > this.remoteLength) this.remoteLength = start
  }

  this._updateEnd()
  this.update()
}

Peer.prototype._updateEnd = function () {
  if (this.live || this.feed.sparse || !this.feed._selections.length) return

  var sel = this.feed._selections[0]
  var remoteLength = -1

  for (var i = 0; i < this.feed.peers.length; i++) {
    if (this.feed.peers[i].remoteLength > remoteLength) {
      remoteLength = this.feed.peers[i].remoteLength
    }
  }

  sel.end = remoteLength
}

Peer.prototype.oninfo = function (info) {
  if (info.downloading || this.live) return
  this.update()
  if (this.feed._selections.length && this.downloading) return
  this.end()
}

Peer.prototype.onunhave =
Peer.prototype.onunwant =
Peer.prototype.oncancel = function () {
  // TODO: impl all of me
}

Peer.prototype.onclose = function () {
  this.destroy()
}

Peer.prototype.have = function (have) { // called by feed
  this.stream.have(have)
}

Peer.prototype.haveBytes = function (bytes) { // called by feed
  for (var i = 0; i < this.inflightRequests.length; i++) {
    if (this.inflightRequests[i].bytes === bytes) {
      this.feed._reserved.set(this.inflightRequests[i].index, false)
      this.inflightRequests.splice(i, 1)
      i--
    }
  }

  this.update()
}

Peer.prototype.update = function () {
  if (!this.downloading || !this.remoteBitfield) return

  var selections = this.feed._selections
  var waiting = this.feed._waiting
  var inflight = this.inflightRequests.length
  var offset = 0
  var i = 0

  // TODO: less duplicate code here
  // TODO: re-add priority levels

  while (inflight < this.maxRequests) {
    offset = Math.floor(Math.random() * waiting.length)

    for (i = 0; i < waiting.length; i++) {
      var w = waiting[offset++]
      if (offset === waiting.length) offset = 0

      this._downloadWaiting(w)
      if (this.inflightRequests.length >= this.maxRequests) return
    }
    if (inflight === this.inflightRequests.length) break
    inflight = this.inflightRequests.length
  }

  while (inflight < this.maxRequests) {
    offset = Math.floor(Math.random() * selections.length)

    for (i = 0; i < selections.length; i++) {
      var s = selections[offset++]
      if (offset === selections.length) offset = 0

      if (!s.iterator) s.iterator = this.feed.bitfield.iterator(s.start, s.end)
      this._downloadRange(s)
      if (this.inflightRequests.length >= this.maxRequests) return
    }

    if (inflight === this.inflightRequests.length) return
    inflight = this.inflightRequests.length
  }
}

Peer.prototype.ready = function () {
  set.add(this.feed.peers, this)
  this.stream.want({start: 0}) // TODO: don't just subscribe to *EVERYTHING* hehe
  this.feed.emit('peer-add')
}

Peer.prototype.end = function () {
  if (!this._closed) {
    this._closed = true
    this.stream.info({downloading: false})
  } else {
    if (!this.live) this.stream.close()
  }
}

Peer.prototype.destroy = function (err) {
  if (this._index === -1) return
  set.remove(this.feed.peers, this)
  for (var i = 0; i < this.inflightRequests.length; i++) {
    this.feed._reserved.set(this.inflightRequests[i].index, false)
  }
  this._updateEnd()
  this._index = -1
  this.stream.destroy(err)
  this.feed._updatePeers()
  this.feed.emit('peer-remove')
}

Peer.prototype._downloadWaiting = function (wait) {
  if (!wait.bytes) {
    if (!this.remoteBitfield.get(wait.index) || !this.feed._reserved.set(wait.index, true)) return
    this._request(wait.index, 0, false)
    return
  }

  this._downloadRange(wait)
}

Peer.prototype._downloadRange = function (range) {
  if (!range.iterator) range.iterator = this.feed.bitfield.iterator(range.start, range.end)

  var reserved = this.feed._reserved
  var ite = range.iterator
  var wantedEnd = Math.min(range.end === -1 ? this.remoteLength : range.end, this.remoteLength)

  if (ite.end !== wantedEnd) ite.range(range.start, wantedEnd)

  var i = range.linear ? ite.next() : ite.random()
  var reset = false
  var start = i

  if (i === -1) {
    if (!range.bytes && ite.seek(0).next() === -1 && (range.end > -1 && this.remoteLength >= range.end)) {
      set.remove(this.feed._selections, range)
      range.callback(null)
      if (!this.live && !this.sparse && !this.feed._selections.length) this.end()
    }
    return
  }

  while (!this.remoteBitfield.get(i) || (range.hash && this.feed.tree.get(2 * i)) || !reserved.set(i, true)) {
    i = ite.next()
    reset = true

    if (i > -1) {
      // check this index
      continue
    }

    if (!range.linear && start !== 0) {
      // retry from the beginning since we are iterating randomly and started !== 0
      i = ite.seek(0).next()
      start = 0
      continue
    }

    // we have checked all indexes.
    // if we are looking for hashes we should check if we have all now (first check only checks blocks)
    if (range.hash) {
      // quick'n'dirty check if have all hashes - can be optimized be checking only tree roots
      // but we don't really request long ranges of hashes so yolo
      for (var j = range.start; j < wantedEnd; j++) {
        if (!this.feed.tree.get(2 * j)) return
      }
      if (!range.bytes) {
        set.remove(this.feed._selections, range)
        range.callback(null)
      }
    }

    // exit the update loop - nothing to do
    return
  }

  if (reset) ite.seek(0)

  this._request(i, range.bytes || 0, range.hash)
}

Peer.prototype._request = function (index, bytes, hash) {
  var request = {
    tick: 6,
    bytes: bytes,
    index: index,
    hash: hash,
    nodes: this.feed.digest(index)
  }

  this.inflightRequests.push(request)
  this.stream.request(request)
}
<|endoftext|>


<commit_before>const StatementUI          = require('./statement.ui');
const ViewPopup            = require('../../view_popup/view_popup');
const Client               = require('../../../../base/client');
const showLocalTimeOnHover = require('../../../../base/clock').showLocalTimeOnHover;
const BinarySocket         = require('../../../../base/socket');
const DateTo               = require('../../../../common/attach_dom/date_to');
const isEuCountry          = require('../../../../common/country_base').isEuCountry;
const addTooltip           = require('../../../../common/get_app_details').addTooltip;
const buildOauthApps       = require('../../../../common/get_app_details').buildOauthApps;
const localize             = require('../../../../../_common/localize').localize;

const StatementInit = (() => {
    // Batch refer to number of data get from ws service per request
    // chunk refer to number of data populate to ui for each append
    // receive means receive from ws service
    // consume means consume by UI and displayed to page

    let batch_size,
        chunk_size,
        no_more_data,
        pending,
        current_batch,
        transactions_received,
        transactions_consumed;

    const tableExist = () => document.getElementById('statement-table');

    const finishedConsumed = () => (transactions_consumed === transactions_received);

    const getStatement = (opts) => {
        const req = { statement: 1, description: 1 };

        if (opts) $.extend(true, req, opts);

        const obj_date_to_from = DateTo.getDateToFrom();
        if (obj_date_to_from) $.extend(true, req, obj_date_to_from);

        BinarySocket.send(req).then((response) => {
            statementHandler(response);
            $('.barspinner').setVisibility(0);
        });
    };

    const getNextBatchStatement = () => {
        getStatement({ offset: transactions_received, limit: batch_size });
        pending = true;
    };

    const getNextChunkStatement = () => {
        const chunk = current_batch.splice(0, chunk_size);
        transactions_consumed += chunk.length;
        $('#rows_count').text(transactions_consumed);
        return chunk;
    };

    const getAccountStatistics = () => {
        // only show Account Statistics to MLT/MX clients
        if (!/^(malta|iom)$/.test(Client.get('landing_company_shortcode'))) return;

        BinarySocket.send({ account_statistics: 1 }).then(response => {
            StatementUI.updateAccountStatistics(response.account_statistics);
        });
    };

    const statementHandler = (response) => {
        if (response.error) {
            StatementUI.errorMessage(response.error.message);
            return;
        }

        pending = false;

        const statement = response.statement;
        current_batch   = statement.transactions;
        transactions_received += current_batch.length;

        if (current_batch.length < batch_size) {
            no_more_data = true;
        }

        if (!tableExist()) {
            StatementUI.createEmptyStatementTable().appendTo('#statement-container');
            $('.act, .credit').addClass('nowrap');
            StatementUI.updateStatementTable(getNextChunkStatement());

            // Show a message when the table is empty
            if (transactions_received === 0 && current_batch.length === 0) {
                $('#statement-table').find('tbody')
                    .append($('<tr/>', { class: 'flex-tr' })
                        .append($('<td/>', { colspan: 7 })
                            .append($('<p/>', { class: 'notice-msg center-text', text: localize('Your account has no trading activity.') }))));
            } else {
                $('#util_row').setVisibility(1);
                // uncomment to enable export to CSV
                // $('#download_csv')
                //     .setVisibility(1)
                //     .find('a')
                //     .off('click')
                //     .on('click', () => { StatementUI.exportCSV(); });
            }
        }
        showLocalTimeOnHover('td.date');
    };

    const loadStatementChunkWhenScroll = () => {
        $(document).scroll(() => {
            const hidableHeight = (percentage) => {
                const total_hideable = $(document).height() - $(window).height();
                return Math.floor((total_hideable * percentage) / 100);
            };

            const p_from_top = $(document).scrollTop();

            if (!tableExist() || p_from_top < hidableHeight(70)) return;

            if (finishedConsumed() && !no_more_data && !pending) {
                getNextBatchStatement();
                return;
            }

            if (!finishedConsumed()) StatementUI.updateStatementTable(getNextChunkStatement());
        });
    };

    const onUnload = () => {
        pending      = false;
        no_more_data = false;

        current_batch = [];

        transactions_received = 0;
        transactions_consumed = 0;

        StatementUI.errorMessage(null);
        StatementUI.clearTableContent();
    };

    const initPage = () => {
        batch_size            = 200;
        chunk_size            = batch_size / 2;
        no_more_data          = false;
        pending               = false;            // serve as a lock to prevent ws request is sequential
        current_batch         = [];
        transactions_received = 0;
        transactions_consumed = 0;

        BinarySocket.send({ oauth_apps: 1 }).then((response) => {
            addTooltip(StatementUI.setOauthApps(buildOauthApps(response)));
        });
        getNextBatchStatement();
        loadStatementChunkWhenScroll();
        getAccountStatistics();

        BinarySocket.wait('website_status', 'authorize', 'landing_company').then(() => {
            if (isEuCountry() && !Client.get('is_virtual')) {
                initDownloadStatement();
            }
        });
    };

    const onLoad = () => {
        initPage();
        DateTo.attachDateToPicker(() => {
            StatementUI.clearTableContent();
            $('.barspinner').setVisibility(1);
            initPage();
        });
        ViewPopup.viewButtonOnClick('#statement-container');
    };

    const initDownloadStatement = () => {
        const $statement_container    = $('#statement-container');
        const $ds_container           = $('#download-statement-container');
        const $download_statement_btn = $('#download_statement_btn');
        const $request_statement_btn  = $('#request_statement_btn');
        const $success_msg            = $ds_container.find('.success-msg');
        const $error_msg              = $ds_container.find('.error-msg');

        const download_from_id        = '#download_from';
        const download_to_id          = '#download_to';

        $download_statement_btn.setVisibility(1);
        $download_statement_btn.off('click').on('click', (e) => {
            e.preventDefault();

            $statement_container.setVisibility(0);
            $ds_container.setVisibility(1);

            DateTo.attachDateRangePicker(download_from_id, download_to_id, () => {
                $success_msg.setVisibility(0);
                $error_msg.setVisibility(0);

                setTimeout(() => {
                    // need to wrap with setTimeout 0 to execute this chunk of code right
                    // after datepicker value are updated with newly selected date,
                    // otherwise we will get the previously selected date
                    // More info: https://javascript.info/settimeout-setinterval#settimeout-0
                    const date_from  = DateTo.getDatePickerValue(download_from_id);
                    const date_to    = DateTo.getDatePickerValue(download_to_id, true);
                    const can_submit = date_from && date_to;

                    if (can_submit) {
                        $request_statement_btn.removeClass('button-disabled')
                            .on('click', (evt) => {
                                evt.preventDefault();
                                BinarySocket.send({
                                    request_report: 1,
                                    report_type   : 'statement',
                                    date_from,
                                    date_to,
                                }).then((response) => {
                                    if (response.error) {
                                        $error_msg.text(response.error.message).setVisibility(1);
                                    } else {
                                        $success_msg.setVisibility(1);
                                    }
                                    $request_statement_btn.addClass('button-disabled').off('click');
                                });
                            });
                    } else {
                        $request_statement_btn.addClass('button-disabled').off('click');
                    }
                }, 0);
            });
        });

        $('#go_back_btn').off('click').on('click', (e) => {
            e.preventDefault();
            $ds_container.setVisibility(0);
            $statement_container.setVisibility(1);
            $success_msg.setVisibility(0);
            $error_msg.setVisibility(0);
            $request_statement_btn.addClass('button-disabled').off('click');
            $(download_from_id).val('').removeAttr('data-value');
            $(download_to_id).val('').removeAttr('data-value');
        });
    };

    return {
        onLoad,
        onUnload,
    };
})();

module.exports = StatementInit;
<commit_msg>prevent attaching click event listener multiples time<commit_after>const StatementUI          = require('./statement.ui');
const ViewPopup            = require('../../view_popup/view_popup');
const Client               = require('../../../../base/client');
const showLocalTimeOnHover = require('../../../../base/clock').showLocalTimeOnHover;
const BinarySocket         = require('../../../../base/socket');
const DateTo               = require('../../../../common/attach_dom/date_to');
const isEuCountry          = require('../../../../common/country_base').isEuCountry;
const addTooltip           = require('../../../../common/get_app_details').addTooltip;
const buildOauthApps       = require('../../../../common/get_app_details').buildOauthApps;
const localize             = require('../../../../../_common/localize').localize;

const StatementInit = (() => {
    // Batch refer to number of data get from ws service per request
    // chunk refer to number of data populate to ui for each append
    // receive means receive from ws service
    // consume means consume by UI and displayed to page

    let batch_size,
        chunk_size,
        no_more_data,
        pending,
        current_batch,
        transactions_received,
        transactions_consumed;

    const tableExist = () => document.getElementById('statement-table');

    const finishedConsumed = () => (transactions_consumed === transactions_received);

    const getStatement = (opts) => {
        const req = { statement: 1, description: 1 };

        if (opts) $.extend(true, req, opts);

        const obj_date_to_from = DateTo.getDateToFrom();
        if (obj_date_to_from) $.extend(true, req, obj_date_to_from);

        BinarySocket.send(req).then((response) => {
            statementHandler(response);
            $('.barspinner').setVisibility(0);
        });
    };

    const getNextBatchStatement = () => {
        getStatement({ offset: transactions_received, limit: batch_size });
        pending = true;
    };

    const getNextChunkStatement = () => {
        const chunk = current_batch.splice(0, chunk_size);
        transactions_consumed += chunk.length;
        $('#rows_count').text(transactions_consumed);
        return chunk;
    };

    const getAccountStatistics = () => {
        // only show Account Statistics to MLT/MX clients
        if (!/^(malta|iom)$/.test(Client.get('landing_company_shortcode'))) return;

        BinarySocket.send({ account_statistics: 1 }).then(response => {
            StatementUI.updateAccountStatistics(response.account_statistics);
        });
    };

    const statementHandler = (response) => {
        if (response.error) {
            StatementUI.errorMessage(response.error.message);
            return;
        }

        pending = false;

        const statement = response.statement;
        current_batch   = statement.transactions;
        transactions_received += current_batch.length;

        if (current_batch.length < batch_size) {
            no_more_data = true;
        }

        if (!tableExist()) {
            StatementUI.createEmptyStatementTable().appendTo('#statement-container');
            $('.act, .credit').addClass('nowrap');
            StatementUI.updateStatementTable(getNextChunkStatement());

            // Show a message when the table is empty
            if (transactions_received === 0 && current_batch.length === 0) {
                $('#statement-table').find('tbody')
                    .append($('<tr/>', { class: 'flex-tr' })
                        .append($('<td/>', { colspan: 7 })
                            .append($('<p/>', { class: 'notice-msg center-text', text: localize('Your account has no trading activity.') }))));
            } else {
                $('#util_row').setVisibility(1);
                // uncomment to enable export to CSV
                // $('#download_csv')
                //     .setVisibility(1)
                //     .find('a')
                //     .off('click')
                //     .on('click', () => { StatementUI.exportCSV(); });
            }
        }
        showLocalTimeOnHover('td.date');
    };

    const loadStatementChunkWhenScroll = () => {
        $(document).scroll(() => {
            const hidableHeight = (percentage) => {
                const total_hideable = $(document).height() - $(window).height();
                return Math.floor((total_hideable * percentage) / 100);
            };

            const p_from_top = $(document).scrollTop();

            if (!tableExist() || p_from_top < hidableHeight(70)) return;

            if (finishedConsumed() && !no_more_data && !pending) {
                getNextBatchStatement();
                return;
            }

            if (!finishedConsumed()) StatementUI.updateStatementTable(getNextChunkStatement());
        });
    };

    const onUnload = () => {
        pending      = false;
        no_more_data = false;

        current_batch = [];

        transactions_received = 0;
        transactions_consumed = 0;

        StatementUI.errorMessage(null);
        StatementUI.clearTableContent();
    };

    const initPage = () => {
        batch_size            = 200;
        chunk_size            = batch_size / 2;
        no_more_data          = false;
        pending               = false;            // serve as a lock to prevent ws request is sequential
        current_batch         = [];
        transactions_received = 0;
        transactions_consumed = 0;

        BinarySocket.send({ oauth_apps: 1 }).then((response) => {
            addTooltip(StatementUI.setOauthApps(buildOauthApps(response)));
        });
        getNextBatchStatement();
        loadStatementChunkWhenScroll();
        getAccountStatistics();

        BinarySocket.wait('website_status', 'authorize', 'landing_company').then(() => {
            if (isEuCountry() && !Client.get('is_virtual')) {
                initDownloadStatement();
            }
        });
    };

    const onLoad = () => {
        initPage();
        DateTo.attachDateToPicker(() => {
            StatementUI.clearTableContent();
            $('.barspinner').setVisibility(1);
            initPage();
        });
        ViewPopup.viewButtonOnClick('#statement-container');
    };

    const initDownloadStatement = () => {
        const $statement_container    = $('#statement-container');
        const $ds_container           = $('#download-statement-container');
        const $download_statement_btn = $('#download_statement_btn');
        const $request_statement_btn  = $('#request_statement_btn');
        const $success_msg            = $ds_container.find('.success-msg');
        const $error_msg              = $ds_container.find('.error-msg');

        const download_from_id        = '#download_from';
        const download_to_id          = '#download_to';

        $download_statement_btn.setVisibility(1);
        $download_statement_btn.off('click').on('click', (e) => {
            e.preventDefault();

            $statement_container.setVisibility(0);
            $ds_container.setVisibility(1);

            DateTo.attachDateRangePicker(download_from_id, download_to_id, () => {
                $success_msg.setVisibility(0);
                $error_msg.setVisibility(0);

                setTimeout(() => {
                    // need to wrap with setTimeout 0 to execute this chunk of code right
                    // after datepicker value are updated with newly selected date,
                    // otherwise we will get the previously selected date
                    // More info: https://javascript.info/settimeout-setinterval#settimeout-0
                    const date_from  = DateTo.getDatePickerValue(download_from_id);
                    const date_to    = DateTo.getDatePickerValue(download_to_id, true);
                    const can_submit = date_from && date_to;

                    if (can_submit) {
                        $request_statement_btn.removeClass('button-disabled')
                            .off('click')
                            .on('click', (evt) => {
                                evt.preventDefault();
                                BinarySocket.send({
                                    request_report: 1,
                                    report_type   : 'statement',
                                    date_from,
                                    date_to,
                                }).then((response) => {
                                    if (response.error) {
                                        $error_msg.text(response.error.message).setVisibility(1);
                                    } else {
                                        $success_msg.setVisibility(1);
                                    }
                                    $request_statement_btn.addClass('button-disabled').off('click');
                                });
                            });
                    } else {
                        $request_statement_btn.addClass('button-disabled').off('click');
                    }
                }, 0);
            });
        });

        $('#go_back_btn').off('click').on('click', (e) => {
            e.preventDefault();
            $ds_container.setVisibility(0);
            $statement_container.setVisibility(1);
            $success_msg.setVisibility(0);
            $error_msg.setVisibility(0);
            $request_statement_btn.addClass('button-disabled').off('click');
            $(download_from_id).val('').removeAttr('data-value');
            $(download_to_id).val('').removeAttr('data-value');
        });
    };

    return {
        onLoad,
        onUnload,
    };
})();

module.exports = StatementInit;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 * Use of this source code is governed by a BSD-style license
 * that can be found in the LICENSE file in the root of the source
 * tree. An additional intellectual property rights grant can be found
 * in the file PATENTS.  All contributing project authors may
 * be found in the AUTHORS file in the root of the source tree.
 */

// LoopbackTest establish a one way loopback call between 2 peer connections
// while continuously monitoring bandwidth stats. The idea is to use this as
// a base for other future tests and to keep track of more than just bandwidth
// stats.
//
// Usage:
//  var test = new LoopbackTest(stream, callDurationMs,
//                              forceTurn, maxVideoBitrateKbps);
//  test.run(onDone);
//  function onDone() {
//    test.getResults(); // return stats recorded during the loopback test.
//  }
//
function LoopbackTest(stream, callDurationMs, forceTurn, maxVideoBitrateKbps) {
  var pc1StatTracker;
  var pc2StatTracker;

  // In order to study effect of network (e.g. wifi) on peer connection one can
  // establish a loopback call and force it to go via a turn server. This way
  // the call won't switch to local addresses. That is achieved by filtering out
  // all non-relay ice candidades on both peers.
  function constrainTurnCandidates(pc) {
    var origAddIceCandidate = pc.addIceCandidate;
    pc.addIceCandidate = function (candidate, successCallback,
                                   failureCallback) {
      if (forceTurn && candidate.candidate.indexOf("typ relay ") == -1) {
        trace("Dropping non-turn candidate: " + candidate.candidate);
        successCallback();
        return;
      } else {
        origAddIceCandidate.call(this, candidate, successCallback,
                                 failureCallback);
      }
    }
  }

  // FEC makes it hard to study bwe estimation since there seems to be a spike
  // when it is enabled and disabled. Disable it for now. FEC issue tracked on:
  // https://code.google.com/p/webrtc/issues/detail?id=3050
  function constrainOfferToRemoveFec(pc) {
    var origCreateOffer = pc.createOffer;
    pc.createOffer = function (successCallback, failureCallback, options) {
      function filteredSuccessCallback(desc) {
        desc.sdp = desc.sdp.replace(/(m=video 1 [^\r]+)(116 117)(\r\n)/g,
                                    '$1\r\n');
        desc.sdp = desc.sdp.replace(/a=rtpmap:116 red\/90000\r\n/g, '');
        desc.sdp = desc.sdp.replace(/a=rtpmap:117 ulpfec\/90000\r\n/g, '');
        successCallback(desc);
      }
      origCreateOffer.call(this, filteredSuccessCallback, failureCallback,
                           options);
    }
  }

  // Constraint max video bitrate by modifying the SDP when creating an answer.
  function constrainBitrateAnswer(pc) {
    var origCreateAnswer = pc.createAnswer;
    pc.createAnswer = function (successCallback, failureCallback, options) {
      function filteredSuccessCallback(desc) {
        if (maxVideoBitrateKbps) {
          desc.sdp = desc.sdp.replace(
              /a=mid:video\r\n/g,
              'a=mid:video\r\nb=AS:' + maxVideoBitrateKbps + '\r\n');
        }
        successCallback(desc);
      }
      origCreateAnswer.call(this, filteredSuccessCallback, failureCallback,
                            options);
    }
  }

  // Run the actual LoopbackTest.
  this.run = function(doneCallback) {
    if (forceTurn) requestTurn(start, fail);
    else start();

    function start(turnServer) {
      var pcConfig = forceTurn ? { iceServers: [turnServer] } : null;
      console.log(pcConfig);
      var pc1 = new RTCPeerConnection(pcConfig);
      constrainTurnCandidates(pc1);
      constrainOfferToRemoveFec(pc1);
      pc1StatTracker = new StatTracker(pc1, 50);
      pc1StatTracker.recordStat("EstimatedSendBitrate",
                                "bweforvideo", "googAvailableSendBandwidth");
      pc1StatTracker.recordStat("TransmitBitrate",
                                "bweforvideo", "googTransmitBitrate");
      pc1StatTracker.recordStat("TargetEncodeBitrate",
                                "bweforvideo", "googTargetEncBitrate");
      pc1StatTracker.recordStat("ActualEncodedBitrate",
                                "bweforvideo", "googActualEncBitrate");

      var pc2 = new RTCPeerConnection(pcConfig);
      constrainTurnCandidates(pc2);
      constrainBitrateAnswer(pc2);
      pc2StatTracker = new StatTracker(pc2, 50);
      pc2StatTracker.recordStat("REMB",
                                "bweforvideo", "googAvailableReceiveBandwidth");

      pc1.addStream(stream);
      var call = new Call(pc1, pc2);

      call.start();
      setTimeout(function () {
          call.stop();
          pc1StatTracker.stop();
          pc2StatTracker.stop();
          success();
        }, callDurationMs);
    }

    function success() {
      trace("Success");
      doneCallback();
    }

    function fail() {
      trace("Fail");
      doneCallback();
    }
  }

  // Returns a google visualization datatable with the recorded samples during
  // the loopback test.
  this.getResults = function () {
    return mergeDataTable(pc1StatTracker.dataTable(),
                          pc2StatTracker.dataTable());
  }

  // Helper class to establish and manage a call between 2 peer connections.
  // Usage:
  //   var c = new Call(pc1, pc2);
  //   c.start();
  //   c.stop();
  //
  function Call(pc1, pc2) {
    pc1.onicecandidate = applyIceCandidate.bind(pc2);
    pc2.onicecandidate = applyIceCandidate.bind(pc1);

    function applyIceCandidate(e) {
      if (e.candidate) {
        this.addIceCandidate(new RTCIceCandidate(e.candidate),
                             onAddIceCandidateSuccess,
                             onAddIceCandidateError);
      }
    }

    function onAddIceCandidateSuccess() {}
    function onAddIceCandidateError(error) {
      trace("Failed to add Ice Candidate: " + error.toString());
    }

    this.start = function() {
      pc1.createOffer(gotDescription1, onCreateSessionDescriptionError);

      function onCreateSessionDescriptionError(error) {
        trace('Failed to create session description: ' + error.toString());
      }

      function gotDescription1(desc){
        trace("Offer: " + desc.sdp);
        pc1.setLocalDescription(desc);
        pc2.setRemoteDescription(desc);
        // Since the "remote" side has no media stream we need
        // to pass in the right constraints in order for it to
        // accept the incoming offer of audio and video.
        pc2.createAnswer(gotDescription2, onCreateSessionDescriptionError);
      }

      function gotDescription2(desc){
        trace("Answer: " + desc.sdp);
        pc2.setLocalDescription(desc);
        pc1.setRemoteDescription(desc);
      }
    }

    this.stop = function() {
      pc1.close();
      pc2.close();
    }
  }

  // Request a turn server. This uses the same servers as apprtc.
  function requestTurn(successCallback, failureCallback) {
    var currentDomain = document.domain;
    if (currentDomain.search('localhost') === -1 &&
        currentDomain.search('apprtc') === -1) {
      onerror("not authorized domain");
      return;
    }

    // Get a turn server from computeengineondemand.appspot.com.
    var turnUrl = 'https://computeengineondemand.appspot.com/' +
                  'turn?username=156547625762562&key=4080218913';
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = onTurnResult;
    xmlhttp.open('GET', turnUrl, true);
    xmlhttp.send();

    function onTurnResult() {
      if (this.readyState !== 4) {
        return;
      }

      if (this.status === 200) {
        var turnServer = JSON.parse(xmlhttp.responseText);
        // Create turnUris using the polyfill (adapter.js).
        turnServer.uris = turnServer.uris.filter(
            function (e) { return e.search('transport=udp') != -1; }
        );
        var iceServers = createIceServers(turnServer.uris,
                                          turnServer.username,
                                          turnServer.password);
        if (iceServers !== null) {
          successCallback(iceServers);
          return;
        }
      }
      failureCallback("Failed to get a turn server.");
    }
  }
}
<commit_msg>Allow loopback tests to do TURN when served from webrtc.googlecode.com. BUG=3037 R=fischman@webrtc.org<commit_after>/**
 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 * Use of this source code is governed by a BSD-style license
 * that can be found in the LICENSE file in the root of the source
 * tree. An additional intellectual property rights grant can be found
 * in the file PATENTS.  All contributing project authors may
 * be found in the AUTHORS file in the root of the source tree.
 */

// LoopbackTest establish a one way loopback call between 2 peer connections
// while continuously monitoring bandwidth stats. The idea is to use this as
// a base for other future tests and to keep track of more than just bandwidth
// stats.
//
// Usage:
//  var test = new LoopbackTest(stream, callDurationMs,
//                              forceTurn, maxVideoBitrateKbps);
//  test.run(onDone);
//  function onDone() {
//    test.getResults(); // return stats recorded during the loopback test.
//  }
//
function LoopbackTest(stream, callDurationMs, forceTurn, maxVideoBitrateKbps) {
  var pc1StatTracker;
  var pc2StatTracker;

  // In order to study effect of network (e.g. wifi) on peer connection one can
  // establish a loopback call and force it to go via a turn server. This way
  // the call won't switch to local addresses. That is achieved by filtering out
  // all non-relay ice candidades on both peers.
  function constrainTurnCandidates(pc) {
    var origAddIceCandidate = pc.addIceCandidate;
    pc.addIceCandidate = function (candidate, successCallback,
                                   failureCallback) {
      if (forceTurn && candidate.candidate.indexOf("typ relay ") == -1) {
        trace("Dropping non-turn candidate: " + candidate.candidate);
        successCallback();
        return;
      } else {
        origAddIceCandidate.call(this, candidate, successCallback,
                                 failureCallback);
      }
    }
  }

  // FEC makes it hard to study bwe estimation since there seems to be a spike
  // when it is enabled and disabled. Disable it for now. FEC issue tracked on:
  // https://code.google.com/p/webrtc/issues/detail?id=3050
  function constrainOfferToRemoveFec(pc) {
    var origCreateOffer = pc.createOffer;
    pc.createOffer = function (successCallback, failureCallback, options) {
      function filteredSuccessCallback(desc) {
        desc.sdp = desc.sdp.replace(/(m=video 1 [^\r]+)(116 117)(\r\n)/g,
                                    '$1\r\n');
        desc.sdp = desc.sdp.replace(/a=rtpmap:116 red\/90000\r\n/g, '');
        desc.sdp = desc.sdp.replace(/a=rtpmap:117 ulpfec\/90000\r\n/g, '');
        successCallback(desc);
      }
      origCreateOffer.call(this, filteredSuccessCallback, failureCallback,
                           options);
    }
  }

  // Constraint max video bitrate by modifying the SDP when creating an answer.
  function constrainBitrateAnswer(pc) {
    var origCreateAnswer = pc.createAnswer;
    pc.createAnswer = function (successCallback, failureCallback, options) {
      function filteredSuccessCallback(desc) {
        if (maxVideoBitrateKbps) {
          desc.sdp = desc.sdp.replace(
              /a=mid:video\r\n/g,
              'a=mid:video\r\nb=AS:' + maxVideoBitrateKbps + '\r\n');
        }
        successCallback(desc);
      }
      origCreateAnswer.call(this, filteredSuccessCallback, failureCallback,
                            options);
    }
  }

  // Run the actual LoopbackTest.
  this.run = function(doneCallback) {
    if (forceTurn) requestTurn(start, fail);
    else start();

    function start(turnServer) {
      var pcConfig = forceTurn ? { iceServers: [turnServer] } : null;
      console.log(pcConfig);
      var pc1 = new RTCPeerConnection(pcConfig);
      constrainTurnCandidates(pc1);
      constrainOfferToRemoveFec(pc1);
      pc1StatTracker = new StatTracker(pc1, 50);
      pc1StatTracker.recordStat("EstimatedSendBitrate",
                                "bweforvideo", "googAvailableSendBandwidth");
      pc1StatTracker.recordStat("TransmitBitrate",
                                "bweforvideo", "googTransmitBitrate");
      pc1StatTracker.recordStat("TargetEncodeBitrate",
                                "bweforvideo", "googTargetEncBitrate");
      pc1StatTracker.recordStat("ActualEncodedBitrate",
                                "bweforvideo", "googActualEncBitrate");

      var pc2 = new RTCPeerConnection(pcConfig);
      constrainTurnCandidates(pc2);
      constrainBitrateAnswer(pc2);
      pc2StatTracker = new StatTracker(pc2, 50);
      pc2StatTracker.recordStat("REMB",
                                "bweforvideo", "googAvailableReceiveBandwidth");

      pc1.addStream(stream);
      var call = new Call(pc1, pc2);

      call.start();
      setTimeout(function () {
          call.stop();
          pc1StatTracker.stop();
          pc2StatTracker.stop();
          success();
        }, callDurationMs);
    }

    function success() {
      trace("Success");
      doneCallback();
    }

    function fail(msg) {
      trace("Fail: " + msg);
      doneCallback();
    }
  }

  // Returns a google visualization datatable with the recorded samples during
  // the loopback test.
  this.getResults = function () {
    return mergeDataTable(pc1StatTracker.dataTable(),
                          pc2StatTracker.dataTable());
  }

  // Helper class to establish and manage a call between 2 peer connections.
  // Usage:
  //   var c = new Call(pc1, pc2);
  //   c.start();
  //   c.stop();
  //
  function Call(pc1, pc2) {
    pc1.onicecandidate = applyIceCandidate.bind(pc2);
    pc2.onicecandidate = applyIceCandidate.bind(pc1);

    function applyIceCandidate(e) {
      if (e.candidate) {
        this.addIceCandidate(new RTCIceCandidate(e.candidate),
                             onAddIceCandidateSuccess,
                             onAddIceCandidateError);
      }
    }

    function onAddIceCandidateSuccess() {}
    function onAddIceCandidateError(error) {
      trace("Failed to add Ice Candidate: " + error.toString());
    }

    this.start = function() {
      pc1.createOffer(gotDescription1, onCreateSessionDescriptionError);

      function onCreateSessionDescriptionError(error) {
        trace('Failed to create session description: ' + error.toString());
      }

      function gotDescription1(desc){
        trace("Offer: " + desc.sdp);
        pc1.setLocalDescription(desc);
        pc2.setRemoteDescription(desc);
        // Since the "remote" side has no media stream we need
        // to pass in the right constraints in order for it to
        // accept the incoming offer of audio and video.
        pc2.createAnswer(gotDescription2, onCreateSessionDescriptionError);
      }

      function gotDescription2(desc){
        trace("Answer: " + desc.sdp);
        pc2.setLocalDescription(desc);
        pc1.setRemoteDescription(desc);
      }
    }

    this.stop = function() {
      pc1.close();
      pc2.close();
    }
  }

  // Request a turn server. This uses the same servers as apprtc.
  function requestTurn(successCallback, failureCallback) {
    var currentDomain = document.domain;
    if (currentDomain.search('localhost') === -1 &&
        currentDomain.search('webrtc.googlecode.com') === -1) {
      failureCallback("Domain not authorized for turn server: " +
                      currentDomain);
      return;
    }

    // Get a turn server from computeengineondemand.appspot.com.
    var turnUrl = 'https://computeengineondemand.appspot.com/' +
                  'turn?username=156547625762562&key=4080218913';
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = onTurnResult;
    xmlhttp.open('GET', turnUrl, true);
    xmlhttp.send();

    function onTurnResult() {
      if (this.readyState !== 4) {
        return;
      }

      if (this.status === 200) {
        var turnServer = JSON.parse(xmlhttp.responseText);
        // Create turnUris using the polyfill (adapter.js).
        turnServer.uris = turnServer.uris.filter(
            function (e) { return e.search('transport=udp') != -1; }
        );
        var iceServers = createIceServers(turnServer.uris,
                                          turnServer.username,
                                          turnServer.password);
        if (iceServers !== null) {
          successCallback(iceServers);
          return;
        }
      }
      failureCallback("Failed to get a turn server.");
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Created by fritz on 2/15/14.
 */
var path = require('path');

/**
 * easy way for circular module loading
 */
function requireFn(name) {
	return function () {
		var moduleDir = path.dirname(module.parent.id);
		return require(path.resolve(moduleDir, name));
	};
}

module.exports = requireFn;
<commit_msg>fix requireFn<commit_after>/**
 * Created by fritz on 2/15/14.
 */
var Module = require('module'),
	path = require('path');

/**
 * easy way for circular module loading
 */
function requireFn(request) {
	return function () {
		var parent = module.parent,
			name = Module._resolveFilename(request, parent);
		return require(name);
	};
}

module.exports = requireFn;
<|endoftext|>