<commit_before>GotoX.predefDomainName = "";

// Diagnose domain name to a valid and available domain
function selectionToUrl(selection) {
	var domain = GotoX.predefDomainName;
    GotoX.predefDomainName = "";
	if (domain.isBlank()) {
		console.log("no pre-defined domain");
		domain = selectionToDomainName(selection);
	}
	var url = "http://" + domain;
	console.log("final url: " + url);
	return url;
}

// Diagnose domain name to a valid title of menu item.
// Note: HIGH Performance needed
function selectionToDomainName(selection) {
	selection = pretreat(selection);
	var valid = isValidDomain(selection);
	console.log("'" + selection + "'" + " is " + (valid ? "valid" : "invalid"));
	return GotoX.predefDomainName = valid ? selection : selection + ".com";
}

function pretreat(selection) {
	return selection.trim().replaceAll(" ", "");
}

// TODO need to enhance performance
function isValidDomain(domain) {
	if (domain.includes(".")) {
		var domains = getDomains();
		for ( var i = 0; i < domains.length; i++) {
			if (domain.endsWith(domains[i])) {
				console.log("found: compared " + (i + 1) + " times");
				return true;
			}
		}
		console.log("not found: compared " + i + " times");
	} else if (domain === "localhost") {
		return true;
	}
	return false;
}<commit_msg>to lower case<commit_after>GotoX.predefDomainName = "";

// Diagnose domain name to a valid and available domain
function selectionToUrl(selection) {
	var domain = GotoX.predefDomainName;
    GotoX.predefDomainName = "";
	if (domain.isBlank()) {
		console.log("no pre-defined domain");
		domain = selectionToDomainName(selection);
	}
	var url = "http://" + domain;
	console.log("final url: " + url);
	return url;
}

// Diagnose domain name to a valid title of menu item.
// Note: HIGH Performance needed
function selectionToDomainName(selection) {
	selection = pretreat(selection);
	var valid = isValidDomain(selection);
	console.log("'" + selection + "'" + " is " + (valid ? "valid" : "invalid"));
	return GotoX.predefDomainName = valid ? selection : selection + ".com";
}

function pretreat(selection) {
	return selection.trim().replaceAll(" ", "").toLowerCase();
}

// TODO need to enhance performance
function isValidDomain(domain) {
	if (domain.includes(".")) {
		var domains = getDomains();
		for ( var i = 0; i < domains.length; i++) {
			if (domain.endsWith(domains[i])) {
				console.log("found: compared " + (i + 1) + " times");
				return true;
			}
		}
		console.log("not found: compared " + i + " times");
	} else if (domain === "localhost") {
		return true;
	}
	return false;
}<|endoftext|>


<commit_before>angular.module("collection", []).controller("CollectionController", ["$http", function CollectionController($http) {
    this.root = "";
    this.userId = "";
    this.userName = "";
    this.projectId = "";
    this.currentProject = null;
    this.currentPlot = null;
    this.userSamples = {};
    this.plotsAssigned = 0;
    this.plotsFlagged = 0;
    this.imageryList = [];
    this.showSideBar = false;
    this.mapclass = "fullmap";
    this.quitclass = "quit-full";
    this.statClass = "projNoStats";
    this.arrowstate = "arrow-down";
    this.mapClickEvent;
    var mycollection = this;

    this.getProjectById = function (projectId, userId) {
        $http.get(this.root + "/get-project-by-id/" + projectId)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "") {
                    alert("No project found with ID " + projectId + ".");
                    window.location = this.root + "/home";
                } else {
                    this.currentProject = response.data;
                    this.initialize(this.root, userId, this.userName, projectId);
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving the project info. See console for details.");
            });
    };

    this.getImageryList = function (institutionId) {
        $http.get(this.root + "/get-all-imagery?institutionId=" + institutionId)
            .then(angular.bind(this, function successCallback(response) {
                this.imageryList = response.data;
                this.initialize(this.root, this.userId, this.userName, this.projectId);
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving the imagery list. See console for details.");
            });
    };
    this.toggleStats = function(){
        if(this.statClass == "projNoStats"){
            this.statClass = "projStats";
            this.arrowstate = "arrow-up";
         }
         else{
            this.statClass = "projNoStats";
            this.arrowstate = "arrow-down";
         }
    };
    this.setBaseMapSource = function () {
        map_utils.set_current_imagery(this.currentProject.baseMapSource);
        var attribution = this.imageryList.find(
            function (imagery) {
                return imagery.title == this.currentProject.baseMapSource;
            },
            this
        ).attribution;
        if (this.currentProject.baseMapSource == "DigitalGlobeWMSImagery") {
            this.currentProject.attribution = attribution + " | " + this.currentProject.imageryYear + " (" + this.currentProject.stackingProfile + ")";
        } else {
            this.currentProject.attribution = attribution;
        }
    };

    this.initialize = function (documentRoot, userId, userName, projectId) {
        // Make the current documentRoot, userId, userName, and projectId globally available
        this.root = documentRoot;
        this.userId = userId;
        this.userName = userName;
        this.projectId = projectId;

        if (this.currentProject == null) {
            // Load the project details
            this.getProjectById(projectId, userId);
        } else if (angular.equals(this.imageryList, [])) {
            // Load the imageryList
            this.getImageryList(this.currentProject.institution);
        } else {
            // Initialize the base map and show the selected project's boundary
            map_utils.digital_globe_base_map({div_name: "image-analysis-pane",
                                              center_coords: [0.0, 0.0],
                                              zoom_level: 1},
                                             this.imageryList);
            map_utils.set_dg_wms_layer_params(this.currentProject.imageryYear, this.currentProject.stackingProfile);
            this.setBaseMapSource();
            map_utils.draw_polygon(this.currentProject.boundary);
            this.loadProjectPlots();
        }
    };

    this.getPlotData = function (projectId) {
        $http.get(this.root + "/get-unanalyzed-plot/" + projectId)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "done") {
                    this.currentPlot = null;
                    alert("All plots have been analyzed for this project.");
                } else {
                    this.currentPlot = response.data;
                    this.loadRandomPlot();
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving plot data. See console for details.");
            });
    };

    this.getPlotDatabyid = function (projectId, plotid) {
        $http.get(this.root + "/get-unanalyzed-plot-byid/" + projectId + "/" + plotid)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "done") {
                    this.currentPlot = null;
                    alert("All plots have been analyzed for this project.");
                } else {
                    this.currentPlot = response.data[0];
                    this.loadRandomPlot();
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving plot data. See console for details.");
            });
    };
    this.loadPlotById = function (id) {
        if (this.currentPlot == null) {
            this.getPlotDatabyid(this.projectId, id);
        } else {
            utils.enable_element("flag-plot-button");
            map_utils.draw_points(this.currentPlot.samples);
            window.open(this.root + "/geo-dash?editable=false&"
                        + encodeURIComponent("title=" + this.currentProject.name
                                             + "&pid=" + this.projectId
                                             + "&aoi=[" + map_utils.get_view_extent()
                                             + "]&daterange=&bcenter=" + this.currentPlot.center
                                             + "&bradius=" + this.currentProject.plotSize / 2),
                        "_geo-dash");
        }
    };
    this.loadRandomPlot = function () {
        if (this.currentPlot == null) {
            this.getPlotData(this.projectId);
        } else {
            utils.enable_element("flag-plot-button");
            map_utils.draw_points(this.currentPlot.samples);
            window.open(this.root + "/geo-dash?editable=false&"
                        + encodeURIComponent("title=" + this.currentProject.name
                                             + "&pid=" + this.projectId
                                             + "&aoi=[" + map_utils.get_view_extent()
                                             + "]&daterange=&bcenter=" + this.currentPlot.center
                                             + "&bradius=" + this.currentProject.plotSize / 2),
                        "_geo-dash");
        }
    };

    this.nextPlot = function () {
        mycollection.showSideBar = true;
        mycollection.mapclass = "sidemap";
        mycollection.quitclass = "quit-side";
        map_utils.remove_plots_layer();
        map_utils.map_ref.updateSize();
        window.setTimeout('map_utils.map_ref.updateSize()', 550);
        this.currentPlot = null;
        this.userSamples = {};
        utils.disable_element("flag-plot-button");
        utils.disable_element("save-values-button");
        this.loadRandomPlot();
    };

    this.setCurrentValue = function (sampleValueGroup, sampleValue) {
        var selectedFeatures = map_utils.get_selected_samples();
        if (selectedFeatures && selectedFeatures.getLength() > 0) {
            selectedFeatures.forEach(
                function (sample) {
                    var pointAssignments = this.userSamples[sample.get("sample_id")];
                    if (pointAssignments) {
                        pointAssignments[sampleValueGroup.name] = sampleValue.name;
                    } else {
                        pointAssignments = {};
                        pointAssignments[sampleValueGroup.name] = sampleValue.name;
                    }
                    this.userSamples[sample.get("sample_id")] = pointAssignments;
                    map_utils.highlight_sample(sample, sampleValue.color);
                },
                this // necessary to pass outer scope into function
            );
            selectedFeatures.clear();
            utils.blink_border(sampleValue.name + "_" + sampleValue.id);
            if (Object.keys(this.userSamples).length == this.currentPlot.samples.length
                && Object.values(this.userSamples).every(function (values) {
                    return Object.keys(values).length == this.currentProject.sampleValues.length;
                }, this)) {
                utils.enable_element("save-values-button");
                utils.disable_element("new-plot-button");
            }
        } else {
            alert("No sample points selected. Please click some first.");
        }
    };

    this.saveValues = function () {
        $http.post(this.root + "/add-user-samples",
                   {projectId: this.projectId,
                    plotId: this.currentPlot.id,
                    userId: this.userName,
                    userSamples: this.userSamples})
            .then(angular.bind(this, function successCallback(response) {
                alert("Your assignments have been saved to the database.");
                map_utils.disable_selection();
                this.plotsAssigned++;
                this.nextPlot();
            }), function errorCallback(response) {
                console.log(response);
                alert("Error saving your assignments to the database. See console for details.");
            });
    };

    this.flagPlot = function () {
        $http.post(this.root + "/flag-plot",
                   {projectId: this.projectId,
                    plotId:    this.currentPlot.id})
            .then(angular.bind(this, function successCallback(response) {
                alert("Plot " + this.currentPlot.id + " has been flagged.");
                this.plotsFlagged++;
                this.nextPlot();
            }), function errorCallback(response) {
                console.log(response);
                alert("Error flagging plot as bad. See console for details.");
            });
    };

    this.loadProjectPlots = function(){
        $http.get(this.root + "/get-project-plots/" + this.projectId + "/1000")
                .then(angular.bind(this, function successCallback(response) {
                    map_utils.draw_project_points(response.data, "red_fill");
                    var plotclick = function(feature){

                        if(feature.get("id") != null){
                            mycollection.showSideBar = true;
                            mycollection.mapclass = "sidemap";
                            mycollection.quitclass = "quit-side";
                            map_utils.remove_plots_layer();
                            mycollection.loadPlotById(feature.get("id"));
                        }
                        map_utils.map_ref.updateSize();
                        window.setTimeout('map_utils.map_ref.updateSize()', 550);
                    };
                    mycollection.mapClickEvent = map_utils.map_ref.on("click", function(evt){
                    var feature = map_utils.map_ref.forEachFeatureAtPixel(evt.pixel, function(feature) { return feature; });
                    //Check if it is a cluster or a single
                    if (map_utils.isCluster(feature)) {
                        var features = feature.get('features');
                        clusterpoints = [];
                        for(var i = 0; i < features.length; i++) {
                          clusterpoints.push(features[i].getGeometry().getCoordinates());
                        }
                        var linestring = new ol.geom.LineString(clusterpoints);
                        map_utils.map_ref.getView().fit(linestring.getExtent(), map_utils.map_ref.getSize());
                    } else {
                        mycollection.showSideBar = true;
                        mycollection.mapclass = "sidemap";
                        mycollection.quitclass = "quit-side";
                        map_utils.remove_plots_layer();
                        map_utils.map_ref.unByKey(mycollection.mapClickEvent);
                        mycollection.loadPlotById(feature.get("features")[0].get('id'));
                        map_utils.map_ref.updateSize();
                        window.setTimeout('map_utils.map_ref.updateSize()', 550);
                    }
                    });
                }), function errorCallback(response) {
                    console.log(response);
                    alert("Error flagging plot as bad. See console for details.");
                });
    }
}]);
<commit_msg>fixed no feature bug<commit_after>angular.module("collection", []).controller("CollectionController", ["$http", function CollectionController($http) {
    this.root = "";
    this.userId = "";
    this.userName = "";
    this.projectId = "";
    this.currentProject = null;
    this.currentPlot = null;
    this.userSamples = {};
    this.plotsAssigned = 0;
    this.plotsFlagged = 0;
    this.imageryList = [];
    this.showSideBar = false;
    this.mapclass = "fullmap";
    this.quitclass = "quit-full";
    this.statClass = "projNoStats";
    this.arrowstate = "arrow-down";
    this.mapClickEvent;
    var mycollection = this;

    this.getProjectById = function (projectId, userId) {
        $http.get(this.root + "/get-project-by-id/" + projectId)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "") {
                    alert("No project found with ID " + projectId + ".");
                    window.location = this.root + "/home";
                } else {
                    this.currentProject = response.data;
                    this.initialize(this.root, userId, this.userName, projectId);
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving the project info. See console for details.");
            });
    };

    this.getImageryList = function (institutionId) {
        $http.get(this.root + "/get-all-imagery?institutionId=" + institutionId)
            .then(angular.bind(this, function successCallback(response) {
                this.imageryList = response.data;
                this.initialize(this.root, this.userId, this.userName, this.projectId);
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving the imagery list. See console for details.");
            });
    };
    this.toggleStats = function(){
        if(this.statClass == "projNoStats"){
            this.statClass = "projStats";
            this.arrowstate = "arrow-up";
         }
         else{
            this.statClass = "projNoStats";
            this.arrowstate = "arrow-down";
         }
    };
    this.setBaseMapSource = function () {
        map_utils.set_current_imagery(this.currentProject.baseMapSource);
        var attribution = this.imageryList.find(
            function (imagery) {
                return imagery.title == this.currentProject.baseMapSource;
            },
            this
        ).attribution;
        if (this.currentProject.baseMapSource == "DigitalGlobeWMSImagery") {
            this.currentProject.attribution = attribution + " | " + this.currentProject.imageryYear + " (" + this.currentProject.stackingProfile + ")";
        } else {
            this.currentProject.attribution = attribution;
        }
    };

    this.initialize = function (documentRoot, userId, userName, projectId) {
        // Make the current documentRoot, userId, userName, and projectId globally available
        this.root = documentRoot;
        this.userId = userId;
        this.userName = userName;
        this.projectId = projectId;

        if (this.currentProject == null) {
            // Load the project details
            this.getProjectById(projectId, userId);
        } else if (angular.equals(this.imageryList, [])) {
            // Load the imageryList
            this.getImageryList(this.currentProject.institution);
        } else {
            // Initialize the base map and show the selected project's boundary
            map_utils.digital_globe_base_map({div_name: "image-analysis-pane",
                                              center_coords: [0.0, 0.0],
                                              zoom_level: 1},
                                             this.imageryList);
            map_utils.set_dg_wms_layer_params(this.currentProject.imageryYear, this.currentProject.stackingProfile);
            this.setBaseMapSource();
            map_utils.draw_polygon(this.currentProject.boundary);
            this.loadProjectPlots();
        }
    };

    this.getPlotData = function (projectId) {
        $http.get(this.root + "/get-unanalyzed-plot/" + projectId)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "done") {
                    this.currentPlot = null;
                    alert("All plots have been analyzed for this project.");
                } else {
                    this.currentPlot = response.data;
                    this.loadRandomPlot();
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving plot data. See console for details.");
            });
    };

    this.getPlotDatabyid = function (projectId, plotid) {
        $http.get(this.root + "/get-unanalyzed-plot-byid/" + projectId + "/" + plotid)
            .then(angular.bind(this, function successCallback(response) {
                if (response.data == "done") {
                    this.currentPlot = null;
                    alert("All plots have been analyzed for this project.");
                } else {
                    this.currentPlot = response.data[0];
                    this.loadRandomPlot();
                }
            }), function errorCallback(response) {
                console.log(response);
                alert("Error retrieving plot data. See console for details.");
            });
    };
    this.loadPlotById = function (id) {
        if (this.currentPlot == null) {
            this.getPlotDatabyid(this.projectId, id);
        } else {
            utils.enable_element("flag-plot-button");
            map_utils.draw_points(this.currentPlot.samples);
            window.open(this.root + "/geo-dash?editable=false&"
                        + encodeURIComponent("title=" + this.currentProject.name
                                             + "&pid=" + this.projectId
                                             + "&aoi=[" + map_utils.get_view_extent()
                                             + "]&daterange=&bcenter=" + this.currentPlot.center
                                             + "&bradius=" + this.currentProject.plotSize / 2),
                        "_geo-dash");
        }
    };
    this.loadRandomPlot = function () {
        if (this.currentPlot == null) {
            this.getPlotData(this.projectId);
        } else {
            utils.enable_element("flag-plot-button");
            map_utils.draw_points(this.currentPlot.samples);
            window.open(this.root + "/geo-dash?editable=false&"
                        + encodeURIComponent("title=" + this.currentProject.name
                                             + "&pid=" + this.projectId
                                             + "&aoi=[" + map_utils.get_view_extent()
                                             + "]&daterange=&bcenter=" + this.currentPlot.center
                                             + "&bradius=" + this.currentProject.plotSize / 2),
                        "_geo-dash");
        }
    };

    this.nextPlot = function () {
        mycollection.showSideBar = true;
        mycollection.mapclass = "sidemap";
        mycollection.quitclass = "quit-side";
        map_utils.remove_plots_layer();
        map_utils.map_ref.updateSize();
        window.setTimeout('map_utils.map_ref.updateSize()', 550);
        this.currentPlot = null;
        this.userSamples = {};
        utils.disable_element("flag-plot-button");
        utils.disable_element("save-values-button");
        this.loadRandomPlot();
    };

    this.setCurrentValue = function (sampleValueGroup, sampleValue) {
        var selectedFeatures = map_utils.get_selected_samples();
        if (selectedFeatures && selectedFeatures.getLength() > 0) {
            selectedFeatures.forEach(
                function (sample) {
                    var pointAssignments = this.userSamples[sample.get("sample_id")];
                    if (pointAssignments) {
                        pointAssignments[sampleValueGroup.name] = sampleValue.name;
                    } else {
                        pointAssignments = {};
                        pointAssignments[sampleValueGroup.name] = sampleValue.name;
                    }
                    this.userSamples[sample.get("sample_id")] = pointAssignments;
                    map_utils.highlight_sample(sample, sampleValue.color);
                },
                this // necessary to pass outer scope into function
            );
            selectedFeatures.clear();
            utils.blink_border(sampleValue.name + "_" + sampleValue.id);
            if (Object.keys(this.userSamples).length == this.currentPlot.samples.length
                && Object.values(this.userSamples).every(function (values) {
                    return Object.keys(values).length == this.currentProject.sampleValues.length;
                }, this)) {
                utils.enable_element("save-values-button");
                utils.disable_element("new-plot-button");
            }
        } else {
            alert("No sample points selected. Please click some first.");
        }
    };

    this.saveValues = function () {
        $http.post(this.root + "/add-user-samples",
                   {projectId: this.projectId,
                    plotId: this.currentPlot.id,
                    userId: this.userName,
                    userSamples: this.userSamples})
            .then(angular.bind(this, function successCallback(response) {
                alert("Your assignments have been saved to the database.");
                map_utils.disable_selection();
                this.plotsAssigned++;
                this.nextPlot();
            }), function errorCallback(response) {
                console.log(response);
                alert("Error saving your assignments to the database. See console for details.");
            });
    };

    this.flagPlot = function () {
        $http.post(this.root + "/flag-plot",
                   {projectId: this.projectId,
                    plotId:    this.currentPlot.id})
            .then(angular.bind(this, function successCallback(response) {
                alert("Plot " + this.currentPlot.id + " has been flagged.");
                this.plotsFlagged++;
                this.nextPlot();
            }), function errorCallback(response) {
                console.log(response);
                alert("Error flagging plot as bad. See console for details.");
            });
    };

    this.loadProjectPlots = function(){
        $http.get(this.root + "/get-project-plots/" + this.projectId + "/1000")
                .then(angular.bind(this, function successCallback(response) {
                    map_utils.draw_project_points(response.data, "red_fill");
                    var plotclick = function(feature){

                        if(feature.get("id") != null){
                            mycollection.showSideBar = true;
                            mycollection.mapclass = "sidemap";
                            mycollection.quitclass = "quit-side";
                            map_utils.remove_plots_layer();
                            mycollection.loadPlotById(feature.get("id"));
                        }
                        map_utils.map_ref.updateSize();
                        window.setTimeout('map_utils.map_ref.updateSize()', 550);
                    };
                    mycollection.mapClickEvent = map_utils.map_ref.on("click", function(evt){
                    var feature = map_utils.map_ref.forEachFeatureAtPixel(evt.pixel, function(feature) { return feature; });
                    //Check if it is a cluster or a single
                    if (map_utils.isCluster(feature)) {
                        var features = feature.get('features');
                        clusterpoints = [];
                        for(var i = 0; i < features.length; i++) {
                          clusterpoints.push(features[i].getGeometry().getCoordinates());
                        }
                        var linestring = new ol.geom.LineString(clusterpoints);
                        map_utils.map_ref.getView().fit(linestring.getExtent(), map_utils.map_ref.getSize());
                    } else {
                        if(feature.get("features") != null)
                        {
                            mycollection.showSideBar = true;
                            mycollection.mapclass = "sidemap";
                            mycollection.quitclass = "quit-side";
                            map_utils.remove_plots_layer();
                            map_utils.map_ref.unByKey(mycollection.mapClickEvent);
                            mycollection.loadPlotById(feature.get("features")[0].get('id'));
                            map_utils.map_ref.updateSize();
                            window.setTimeout('map_utils.map_ref.updateSize()', 550);
                        }
                    }
                    });
                }), function errorCallback(response) {
                    console.log(response);
                    alert("Error flagging plot as bad. See console for details.");
                });
    }
}]);
<|endoftext|>


<commit_before>var graph = require('fbgraph');
var _ = require('underscore');
var json2csv = require('json2csv');
var async = require('async');
var fs = require('fs');

var config = {
  version: '2.5',
  post_limit: 25,
  comment_limit: 500, // If this is too big, you may get errors stating "Please reduce the amount of data you're asking for, then retry your request"
  refresh_interval_ms: 1000*60,
  backup_interval_ms: 1000*60*10,
  alive_age_ms: 1000*60*60*24,
  state_backup_file: './monitor.backup',
  post_export_file: './monitor_posts.csv',
  comment_export_file: './monitor_comments.csv',
  access_token: 'APP_ID|APP_SECRET',
  sources: [
    'some_fb_page',
    'some_fb_group',
    'not_a_user_though',
    'nytimes'
  ],
  debug: false
}

var state = {
  requests: [],
  live_posts: {},
  posts: {},
  comments: {}
};

var post_params = {
  fields: 'id,created_time,updated_time,message,comments.summary(true).filter(stream),likes.summary(true),shares',
  limit: config.post_limit
};

var comment_params = {
  fields: 'id,created_time,from,likes.summary(true),message',
  limit: config.comment_limit,
  filter: 'stream' // Get comment replies too
};

// Read nested value (i.e. obj.k1.k2.k3.k4) from object, returning undefined
// rather than an error if any key in the sequence does not exist.
function safe_get(obj, key_arr) {
  if(_.isUndefined(obj)) {
    return obj;
  }

  if(key_arr.length === 0) {
    return obj;
  }

  var next_key = key_arr.pop();
  return safe_get(obj[next_key], key_arr);
}

function format_post(source_id) {
  return function(post) {
    return {
      source_id: source_id,
      id: post.id,
      created_time: post.created_time,
      updated_time: post.updated_time,
      likes: safe_get(post, ['likes', 'summary', 'total_count']),
      comments: safe_get(post, ['comments', 'summary', 'total_count']),
      shares: post.shares ? post.shares.count
                          : 0,
      message: post.message
    };
  };
}

function format_comment(post_id) {
  return function(comment) {
    return {
      post_id: post_id,
      id: comment.id,
      created_time: comment.created_time,
      from_id: safe_get(comment, ['from', 'id']),
      likes: safe_get(comment, ['likes', 'summary', 'total_count']),
      message: comment.message
    };
  };
}

function handle_error(err) {
  if(!err) {
    return;
  }

  if(typeof err === 'object') {
    console.error(err);
    err = err.message;
  } else {
    err = err.toString();
    console.error(err);
  }

  if(!config.debug) {
    throw new Error(err);
  }
}

function record_request() {
  state.requests.push(new Date());
}

function log_requests() {
  state.requests = state.requests.filter(function(when) {
    var ms_per_hour = 1000*60*60;
    return (new Date() - when) < ms_per_hour;
  });
  console.log(state.requests.length + ' requests in the last hour.');
}

function updated_recently(post) {
  return (new Date() - new Date(post.updated_time)) < config.alive_age_ms;
}

function get(path, params, after, callback) {
  if(after) {
    params = _.clone(params);
    params.after = after;
  }

  graph.get(path, params, function(err, res) {
    if(err && err.is_transient) {
      console.log('Got a transient error; retrying ' + path);
      return get(path, params, after, callback);
    }

    if(err) {
      console.error('Got an error while processing ' + path);
      return handle_error(err);
    }

    if(!res) {
      return handle_error('No response object!');
    }

    record_request();
    callback(res);
    module.exports.debug.res = res;
  });
}

// Takes an array of objects of the form
//   [ {id: 'foo', ...}, {id: 'bar', ...}, ...]
// and produces
//   { 'foo': {id: 'foo', ...},
//     'bar': {id: 'bar', ...} }
function use_ids_as_keys(arr) {
  var ids = _.map(arr, function(obj) {
    return obj.id;
  });
  return _.object(_.zip(ids, arr));
}

function get_comments(post_id, after, callback) {
  get('/' + post_id + '/comments', comment_params, after, function(res) {
    console.log('Got ' + res.data.length + ' comments');
    var new_comments_arr = _.map(res.data, format_comment(post_id));
    state.comments = _.extend(state.comments,
                              use_ids_as_keys(new_comments_arr));

    if(res && res.paging && res.paging.cursors && res.paging.cursors.after) {
      var new_after = res.paging.cursors.after;
      if(after !== new_after) {
        get_comments(post_id, new_after, callback);
      }
    } else {
      console.log('Got all comments.');
      callback();
    }
  });
}

// Just the last 25
function get_posts(source_id) {
  get('/' + source_id + '/feed', post_params, null, function(res) {
    console.log('Got ' + res.data.length + ' posts from ' + source_id);

    var post_records = _.map(res.data, format_post(source_id));

    _.each(post_records, function(post) {
      var maybe_new_comments = !state.posts[post.id] ||
                               (state.posts[post.id].updated_time !==
                                post.updated_time)
      if(maybe_new_comments) {
        state.live_posts[post.id] = post;
      }
      state.posts[post.id] = post;
    });
  });
}

function get_all_comments(post, callback) {
  // By passing null here, the first request gets the oldest comments, and
  // recursive requests get all of them.
  get_comments(post.id, null, callback);
}

function prune_posts() {
  var alive_and_dying = _.partition(_.values(state.live_posts),
                                    updated_recently);
  state.live_posts = use_ids_as_keys(alive_and_dying[0]);
  async.each(alive_and_dying[1], get_all_comments);
  console.log('Pruned ' + alive_and_dying[1].length + ' posts. ' +
              alive_and_dying[0].length + ' posts remain alive.');
}

function check_all_sources() {
  _.each(config.sources, get_posts);
}

var save = fs.writeFile;

// Save the state of this tool so it can be restarted easily.
function export_state(filename) {
  if(!filename) {
    filename = config.state_backup_file;
  }
  save(filename, JSON.stringify(state), handle_error);
}

function load_state(filename, callback) {
  if(typeof filename === 'function') {
    callback = filename;
    filename = null;
  }

  if(!filename) {
    filename = config.state_backup_file;
  }

  fs.readFile(filename, function(err, data) {
    if(err && callback) {
      return callback(err);
    }

    if(err) {
      return handle_error(err);
    }

    state = JSON.parse(data)

    if(callback) {
      callback();
    }
  });
}

function save_live_post_comments(callback) {
  async.each(state.live_posts, get_all_comments, callback);
}

function save_csv(filename, obj) {
  var arr = _.values(obj);
  json2csv({ data: arr, fields: _.keys(arr[0]) }, function(err, csv) {
    if(err) {
      return handle_error(err);
    }
    save(filename, csv, handle_error);
  });
}

// Export all data as two csv files, including comments from live posts
function export_all() {
  save_live_post_comments(function(err) {
    if(err) {
      return handle_error(err);
    }
    save_csv(config.post_export_file, state.posts);
    save_csv(config.comment_export_file, state.comments);
    console.log('Export complete');
  });
}

function initialize() {
  graph.setVersion(config.version);
  graph.setAccessToken(config.access_token);
}

function start(new_config) {
  if(new_config) {
    config = _.extend(config, new_config);
  }
  initialize();
  setInterval(check_all_sources, config.refresh_interval_ms);
  setInterval(prune_posts, config.refresh_interval_ms);
  setInterval(log_requests, config.refresh_interval_ms);
  setInterval(export_state, config.backup_interval_ms);
}

// Note: like most debug tools, you have to call monitor.debug.initialize()
// before this.
function test_sources() {
  var posts = 0;
  async.each(config.sources, function(source_id, callback) {
    get('/' + source_id + '/feed', post_params, null, function(res) {
      if(res.data.length === 0) {
        callback(new Error('Source ' + source_id + ' returned 0 posts'));
      } else {
        posts += res.data.length;
        callback();
      }
    });
  }, function(err) {
    if(err) {
      handle_error(err);
    } else {
      console.log('Successfully got posts (' + posts + ') from all sources (' + config.sources.length + ').');
      if(config.sources.length * config.post_limit !== posts) {
        console.log('Warning: hoped to get ' + (config.sources.length * config.post_limit) + ' posts, but only got ' + posts + '.');
      }
    }
  });
}

// For testing rate limits
function getlots(n) {
  _.each(_.range(n), function() {
    get('/nytimes/feed', post_params, null, function() {});
  });
}

function get_state() {
  return state;
}

module.exports = {
  start: start,
  export_all: export_all,
  load_state: load_state,
  debug: {
    initialize: initialize,
    getlots: getlots,
    test_sources: test_sources,
    save_live_post_comments: save_live_post_comments,
    export_state: export_state,
    get_state: get_state
  }
};
<commit_msg>Better debug support Setting config options with initialize means you can set your API token more earily in the REPL<commit_after>var graph = require('fbgraph');
var _ = require('underscore');
var json2csv = require('json2csv');
var async = require('async');
var fs = require('fs');

var config = {
  version: '2.5',
  post_limit: 25,
  comment_limit: 500, // If this is too big, you may get errors stating "Please reduce the amount of data you're asking for, then retry your request"
  refresh_interval_ms: 1000*60,
  backup_interval_ms: 1000*60*10,
  alive_age_ms: 1000*60*60*24,
  state_backup_file: './monitor.backup',
  post_export_file: './monitor_posts.csv',
  comment_export_file: './monitor_comments.csv',
  access_token: 'APP_ID|APP_SECRET',
  sources: [
    'some_fb_page',
    'some_fb_group',
    'not_a_user_though',
    'nytimes'
  ],
  debug: false
}

var state = {
  requests: [],
  live_posts: {},
  posts: {},
  comments: {}
};

var post_params = {
  fields: 'id,created_time,updated_time,message,comments.summary(true).filter(stream),likes.summary(true),shares',
  limit: config.post_limit
};

var comment_params = {
  fields: 'id,created_time,from,likes.summary(true),message',
  limit: config.comment_limit,
  filter: 'stream' // Get comment replies too
};

// Read nested value (i.e. obj.k1.k2.k3.k4) from object, returning undefined
// rather than an error if any key in the sequence does not exist.
function safe_get(obj, key_arr) {
  if(_.isUndefined(obj)) {
    return obj;
  }

  if(key_arr.length === 0) {
    return obj;
  }

  var next_key = key_arr.pop();
  return safe_get(obj[next_key], key_arr);
}

function format_post(source_id) {
  return function(post) {
    return {
      source_id: source_id,
      id: post.id,
      created_time: post.created_time,
      updated_time: post.updated_time,
      likes: safe_get(post, ['likes', 'summary', 'total_count']),
      comments: safe_get(post, ['comments', 'summary', 'total_count']),
      shares: post.shares ? post.shares.count
                          : 0,
      message: post.message
    };
  };
}

function format_comment(post_id) {
  return function(comment) {
    return {
      post_id: post_id,
      id: comment.id,
      created_time: comment.created_time,
      from_id: safe_get(comment, ['from', 'id']),
      likes: safe_get(comment, ['likes', 'summary', 'total_count']),
      message: comment.message
    };
  };
}

function handle_error(err) {
  if(!err) {
    return;
  }

  if(typeof err === 'object') {
    console.error(err);
    err = err.message;
  } else {
    err = err.toString();
    console.error(err);
  }

  if(!config.debug) {
    throw new Error(err);
  }
}

function record_request() {
  state.requests.push(new Date());
}

function log_requests() {
  state.requests = state.requests.filter(function(when) {
    var ms_per_hour = 1000*60*60;
    return (new Date() - when) < ms_per_hour;
  });
  console.log(state.requests.length + ' requests in the last hour.');
}

function updated_recently(post) {
  return (new Date() - new Date(post.updated_time)) < config.alive_age_ms;
}

function get(path, params, after, callback) {
  if(after) {
    params = _.clone(params);
    params.after = after;
  }

  graph.get(path, params, function(err, res) {
    if(err && err.is_transient) {
      console.log('Got a transient error; retrying ' + path);
      return get(path, params, after, callback);
    }

    if(err) {
      console.error('Got an error while processing ' + path);
      return handle_error(err);
    }

    if(!res) {
      return handle_error('No response object!');
    }

    record_request();
    callback(res);
    module.exports.debug.res = res;
  });
}

// Takes an array of objects of the form
//   [ {id: 'foo', ...}, {id: 'bar', ...}, ...]
// and produces
//   { 'foo': {id: 'foo', ...},
//     'bar': {id: 'bar', ...} }
function use_ids_as_keys(arr) {
  var ids = _.map(arr, function(obj) {
    return obj.id;
  });
  return _.object(_.zip(ids, arr));
}

function get_comments(post_id, after, callback) {
  get('/' + post_id + '/comments', comment_params, after, function(res) {
    console.log('Got ' + res.data.length + ' comments');
    var new_comments_arr = _.map(res.data, format_comment(post_id));
    state.comments = _.extend(state.comments,
                              use_ids_as_keys(new_comments_arr));

    if(res && res.paging && res.paging.cursors && res.paging.cursors.after) {
      var new_after = res.paging.cursors.after;
      if(after !== new_after) {
        get_comments(post_id, new_after, callback);
      }
    } else {
      console.log('Got all comments.');
      callback();
    }
  });
}

// Just the last 25
function get_posts(source_id) {
  get('/' + source_id + '/feed', post_params, null, function(res) {
    console.log('Got ' + res.data.length + ' posts from ' + source_id);

    var post_records = _.map(res.data, format_post(source_id));

    _.each(post_records, function(post) {
      var maybe_new_comments = !state.posts[post.id] ||
                               (state.posts[post.id].updated_time !==
                                post.updated_time)
      if(maybe_new_comments) {
        state.live_posts[post.id] = post;
      }
      state.posts[post.id] = post;
    });
  });
}

function get_all_comments(post, callback) {
  // By passing null here, the first request gets the oldest comments, and
  // recursive requests get all of them.
  get_comments(post.id, null, callback);
}

function prune_posts() {
  var alive_and_dying = _.partition(_.values(state.live_posts),
                                    updated_recently);
  state.live_posts = use_ids_as_keys(alive_and_dying[0]);
  async.each(alive_and_dying[1], get_all_comments);
  console.log('Pruned ' + alive_and_dying[1].length + ' posts. ' +
              alive_and_dying[0].length + ' posts remain alive.');
}

function check_all_sources() {
  _.each(config.sources, get_posts);
}

var save = fs.writeFile;

// Save the state of this tool so it can be restarted easily.
function export_state(filename) {
  if(!filename) {
    filename = config.state_backup_file;
  }
  save(filename, JSON.stringify(state), handle_error);
}

function load_state(filename, callback) {
  if(typeof filename === 'function') {
    callback = filename;
    filename = null;
  }

  if(!filename) {
    filename = config.state_backup_file;
  }

  fs.readFile(filename, function(err, data) {
    if(err && callback) {
      return callback(err);
    }

    if(err) {
      return handle_error(err);
    }

    state = JSON.parse(data)

    if(callback) {
      callback();
    }
  });
}

function save_live_post_comments(callback) {
  async.each(state.live_posts, get_all_comments, callback);
}

function save_csv(filename, obj) {
  var arr = _.values(obj);
  json2csv({ data: arr, fields: _.keys(arr[0]) }, function(err, csv) {
    if(err) {
      return handle_error(err);
    }
    save(filename, csv, handle_error);
  });
}

// Export all data as two csv files, including comments from live posts
function export_all() {
  save_live_post_comments(function(err) {
    if(err) {
      return handle_error(err);
    }
    save_csv(config.post_export_file, state.posts);
    save_csv(config.comment_export_file, state.comments);
    console.log('Export complete');
  });
}

function initialize(new_config) {
  if(new_config) {
    config = _.extend(config, new_config);
  }
  graph.setVersion(config.version);
  graph.setAccessToken(config.access_token);
}

function start(new_config) {
  initialize(new_config);
  setInterval(check_all_sources, config.refresh_interval_ms);
  setInterval(prune_posts, config.refresh_interval_ms);
  setInterval(log_requests, config.refresh_interval_ms);
  setInterval(export_state, config.backup_interval_ms);
}

// Note: like most debug tools, you have to call monitor.debug.initialize()
// before this.
function test_sources() {
  var posts = 0;
  async.each(config.sources, function(source_id, callback) {
    get('/' + source_id + '/feed', post_params, null, function(res) {
      if(res.data.length === 0) {
        callback(new Error('Source ' + source_id + ' returned 0 posts'));
      } else {
        posts += res.data.length;
        callback();
      }
    });
  }, function(err) {
    if(err) {
      handle_error(err);
    } else {
      console.log('Successfully got posts (' + posts + ') from all sources (' + config.sources.length + ').');
      if(config.sources.length * config.post_limit !== posts) {
        console.log('Warning: hoped to get ' + (config.sources.length * config.post_limit) + ' posts, but only got ' + posts + '.');
      }
    }
  });
}

// For testing rate limits
function getlots(n) {
  _.each(_.range(n), function() {
    get('/nytimes/feed', post_params, null, log_requests);
  });
}

function get_state() {
  return state;
}

module.exports = {
  start: start,
  export_all: export_all,
  load_state: load_state,
  debug: {
    initialize: initialize,
    getlots: getlots,
    log_requests: log_requests,
    test_sources: test_sources,
    save_live_post_comments: save_live_post_comments,
    export_state: export_state,
    get_state: get_state
  }
};
<|endoftext|>


<commit_before>/**
* WebcastJS
* @version 1.0
* @author Carles Galan Cladera
**/

/**
* Cuepoint definition
*/
_V_.Cuepoint = _V_.Class.extend({
	init: function (player, type, start, end, opts){
		this.player = player;
		this.type = type;
		this.start = start;
		this.end = end || -1;
		this.opts = opts || {};
	},
	activate : function (){
		var self = this;
		self._setup();
		this.player.addEvent("timeupdate", function (){
			self._process();
		});
	},
	suspend : function (){
		var self = this;
		this.fired = false;
		this.player.removeEvent("timeupdate", function (){
			self._process();
		});
	},
	_process: function (){
		//Check if current time is between start and end
        if(this.player.currentTime() >= this.start && (this.end < 0 || this.player.currentTime() < this.end)){
            if(this.fired) //Do nothing if start has already been called
                return;
            this.fired = true; //Set fired flag to true
            this._start(); //Call start function
        }else{
            if(!this.fired) //Do nothing if end has already been called
                return;
            this.fired = false; //Set fired flat to false
            this._end(); //Call end function
        }
	},
	_setup: function (){
		var e = new _V_.Event("cuepointSetup");
		e.cuepoint = this;
		this.player.triggerEvent(e);
	},
    _start: function(){
        var e = new _V_.Event("cuepointStart");
        e.cuepoint = this;
        this.player.triggerEvent(e);
    },
    _end: function(){
        var e = new _V_.Event("cuepointEnd");
        e.cuepoint = this;
        this.player.triggerEvent(e);
    }
});
/**
* Webcast Component definition
*/

_V_.Webcast = _V_.Component.extend({
    init: function (player, options){
    	var opts = player.options.webcast || {};
        _V_.merge(opts, _V_.Webcast.options);
        player.options.webcast = opts;
        this._super(player, options);
        //Set with and height
        this.el.style.width = opts.width;
        this.el.style.height = opts.height;
        //Init webcast
        this.cuepoints = [];
        this.player.webcast = this;
        var e = new _V_.Event("webcastReady");
        e.webcast = this;
        this.player.triggerEvent(e);
    },
    buildCSSClass: function(){
        return "webcast-js";
    },
    createElement: function(type, attrs){
        attrs = _V_.merge({
            className: this.buildCSSClass(),
            innerHTML:''
        }, attrs);
        return this._super(type, attrs);
    },
    addCuepoint : function(type, start, end, opts){
    	var cp = new _V_.Cuepoint(this.player, type, start, end, opts);
    	cp.activate();
    	this.cuepoints.push(cp);
    	return cp;
    }
});
_V_.Webcast.options = {
	width : "1024px",
    height: "670px"
}

/**
* SyncComponent definition
*/

_V_.SyncComponent = _V_.Component.extend({
    init: function (player, options){
    	//Set options
    	var opts = {};
    	_V_.merge(opts, _V_.SyncComponent.options); //Copy defaults
    	_V_.merge(opts, options); //Override/extend with options from constructor
    	//Call super constructor
        var p = this._super(player, opts);
        var self = this;
        //Start listening to start cuepoint event
        player.addEvent("cuepointSetup", function (event){
        	var c = event.cuepoint;
        	var regExp = new RegExp(options.cuepointFilter, 'ig');
        	if(regExp.test(c.type))
        		self.setup(c);
        });
        player.addEvent("cuepointStart", function(event){
            var c = event.cuepoint; //Copy cuepoint from event object
            //Filter unrelated cuepoints
            var regExp = new RegExp(options.cuepointfilter,'ig');
            if(regExp.test(c.type))
            	self.start(c); //Call start function
        });
        //Start listenting to end cuepoint event
        player.addEvent("cuepointEnd", function(event){
            var c = event.cuepoint; //Copy cuepoint from event object
            //Filter unrelated cuepoints
            var regExp = new RegExp(options.cuepointfilter,'ig');
            if(regExp.test(c.type))
            	self.end(c); //Call end function
        });
    },
    setup : function (c) {
    	//Setup function
    },
    start : function(c){
        //Start function
    },
    end : function (c){
        //End function
    }
});
_V_.SyncComponent.options = {
	cuepointfilter : ".*"
};

/**
* Slideshow sync compnent definition
*/
_V_.Slideshow = _V_.SyncComponent.extend({
    init: function (player, options){
    	//Set options
    	console.log(player.options);
    	var opts = player.options.webcast.slideshow || {};
    	_V_.merge(opts, _V_.Slideshow.options); //Copy defaults
    	_V_.merge(opts, options); //Override/extend with options from constructor
    	//Call super constructor
        this._super(player, opts);
        this.el.style.width = opts.width;
        this.el.style.height = opts.height;
        var self = this;
        this.player.addEvent("fullscreenchange", function (){
        	if(this.isFullScreen){
        		self.hide();
        	}else {
        		self.show();
        	}
        });
        this.player.addEvent("enterFullWindow", function (){
        	console.log("enterFullWindow");
        });
        this.player.addEvent("exitFullWindow",function (){
        	console.log("exitFullWindow");
        });
    },
    buildCSSClass: function(){
        return this._super() +  "wjs-slideshow";
    },
    createElement: function(type, attrs){
        attrs = _V_.merge({
            className: this.buildCSSClass()
        }, attrs);

        return this._super(type, attrs);
    },
    setup: function (c) {
    	this.createSlide(c.opts.id, c.opts.src);
    },
    start : function (c){
    	this._super(c);
    	this.showSlide(c.opts.id);

    },
    end: function(c){
    	this._super(c);
      	this.hideSlide(c.opts.id);
    },
    getSlide: function (id) {
    	return this.el.querySelector("img#"+id);
    },
    createSlide: function (id, src) {
    	var self = this;
    	var s = _V_.createElement("img", {
    		id: id,
    		src: src,
    		className: "wjs-slide"
    	});
    	s.style.width = "100%";
    	s.style["max-height"] = "100%";
    	s.style.opacity = 0;
    	s.style.visibility = "hidden";
    	s.style.position = "absolute";
    	s.style.top = 0;
    	s.style.left = 0;
    	this.el.appendChild(s);
    },
    hideSlide: function (id) {
    	var s = this.getSlide(id);
    	_V_.removeClass(s, "vjs-fade-in");
    	_V_.addClass(s, "vjs-fade-out");
    },
    showSlide: function (id){
    	var s = this.getSlide(id);
    	_V_.removeClass(s, "vjs-fade-out");
    	_V_.addClass(s, "vjs-fade-in");
    }
});
_V_.Slideshow.options = {
	cuepointfilter : "slideshow",
	width: "497px",
	height: "373px"
};
//Enable Webcast component
_V_.options.components.webcast = {
	components: {}
};
//Enable slideshow component
_V_.options.components.webcast.components.slideshow = {};<commit_msg>Testing onClick Component method<commit_after>/**
* WebcastJS
* @version 1.0
* @author Carles Galan Cladera
**/

/**
* Cuepoint definition
*/
_V_.Cuepoint = _V_.Class.extend({
	init: function (player, type, start, end, opts){
		this.player = player;
		this.type = type;
		this.start = start;
		this.end = end || -1;
		this.opts = opts || {};
	},
	activate : function (){
		var self = this;
		self._setup();
		this.player.addEvent("timeupdate", function (){
			self._process();
		});
	},
	suspend : function (){
		var self = this;
		this.fired = false;
		this.player.removeEvent("timeupdate", function (){
			self._process();
		});
	},
	_process: function (){
		//Check if current time is between start and end
        if(this.player.currentTime() >= this.start && (this.end < 0 || this.player.currentTime() < this.end)){
            if(this.fired) //Do nothing if start has already been called
                return;
            this.fired = true; //Set fired flag to true
            this._start(); //Call start function
        }else{
            if(!this.fired) //Do nothing if end has already been called
                return;
            this.fired = false; //Set fired flat to false
            this._end(); //Call end function
        }
	},
	_setup: function (){
		var e = new _V_.Event("cuepointSetup");
		e.cuepoint = this;
		this.player.triggerEvent(e);
	},
    _start: function(){
        var e = new _V_.Event("cuepointStart");
        e.cuepoint = this;
        this.player.triggerEvent(e);
    },
    _end: function(){
        var e = new _V_.Event("cuepointEnd");
        e.cuepoint = this;
        this.player.triggerEvent(e);
    }
});
/**
* Webcast Component definition
*/

_V_.Webcast = _V_.Component.extend({
    init: function (player, options){
    	var opts = player.options.webcast || {};
        _V_.merge(opts, _V_.Webcast.options);
        player.options.webcast = opts;
        this._super(player, options);
        //Set with and height
        this.el.style.width = opts.width;
        this.el.style.height = opts.height;
        //Init webcast
        this.cuepoints = [];
        this.player.webcast = this;
        var e = new _V_.Event("webcastReady");
        e.webcast = this;
        this.player.triggerEvent(e);
    },
    buildCSSClass: function(){
        return "webcast-js";
    },
    createElement: function(type, attrs){
        attrs = _V_.merge({
            className: this.buildCSSClass(),
            innerHTML:''
        }, attrs);
        return this._super(type, attrs);
    },
    addCuepoint : function(type, start, end, opts){
    	var cp = new _V_.Cuepoint(this.player, type, start, end, opts);
    	cp.activate();
    	this.cuepoints.push(cp);
    	return cp;
    }
});
_V_.Webcast.options = {
	width : "1024px",
    height: "670px"
}

/**
* SyncComponent definition
*/

_V_.SyncComponent = _V_.Component.extend({
    init: function (player, options){
    	//Set options
    	var opts = {};
    	_V_.merge(opts, _V_.SyncComponent.options); //Copy defaults
    	_V_.merge(opts, options); //Override/extend with options from constructor
    	//Call super constructor
        var p = this._super(player, opts);
        var self = this;
        //Start listening to start cuepoint event
        player.addEvent("cuepointSetup", function (event){
        	var c = event.cuepoint;
        	var regExp = new RegExp(options.cuepointFilter, 'ig');
        	if(regExp.test(c.type))
        		self.setup(c);
        });
        player.addEvent("cuepointStart", function(event){
            var c = event.cuepoint; //Copy cuepoint from event object
            //Filter unrelated cuepoints
            var regExp = new RegExp(options.cuepointfilter,'ig');
            if(regExp.test(c.type))
            	self.start(c); //Call start function
        });
        //Start listenting to end cuepoint event
        player.addEvent("cuepointEnd", function(event){
            var c = event.cuepoint; //Copy cuepoint from event object
            //Filter unrelated cuepoints
            var regExp = new RegExp(options.cuepointfilter,'ig');
            if(regExp.test(c.type))
            	self.end(c); //Call end function
        });
    },
    setup : function (c) {
    	//Setup function
    },
    start : function(c){
        //Start function
    },
    end : function (c){
        //End function
    }
});
_V_.SyncComponent.options = {
	cuepointfilter : ".*"
};

/**
* Slideshow sync compnent definition
*/
_V_.Slideshow = _V_.SyncComponent.extend({
    init: function (player, options){
    	//Set options
    	console.log(player.options);
    	var opts = player.options.webcast.slideshow || {};
    	_V_.merge(opts, _V_.Slideshow.options); //Copy defaults
    	_V_.merge(opts, options); //Override/extend with options from constructor
    	//Call super constructor
        this._super(player, opts);
        this.el.style.width = opts.width;
        this.el.style.height = opts.height;
        var self = this;
        this.player.addEvent("fullscreenchange", function (){
        	if(this.isFullScreen){
        		self.hide();
        	}else {
        		self.show();
        	}
        });
        this.player.addEvent("enterFullWindow", function (){
        	console.log("enterFullWindow");
        });
        this.player.addEvent("exitFullWindow",function (){
        	console.log("exitFullWindow");
        });
    },
    buildCSSClass: function(){
        return this._super() +  "wjs-slideshow";
    },
    createElement: function(type, attrs){
        attrs = _V_.merge({
            className: this.buildCSSClass()
        }, attrs);

        return this._super(type, attrs);
    },
    onClick: function () {
    	console.log("Component clicked");
    },
    setup: function (c) {
    	this.createSlide(c.opts.id, c.opts.src);
    },
    start : function (c){
    	this._super(c);
    	this.showSlide(c.opts.id);

    },
    end: function(c){
    	this._super(c);
      	this.hideSlide(c.opts.id);
    },
    getSlide: function (id) {
    	return this.el.querySelector("img#"+id);
    },
    createSlide: function (id, src) {
    	var self = this;
    	var s = _V_.createElement("img", {
    		id: id,
    		src: src,
    		className: "wjs-slide"
    	});
    	s.style.width = "100%";
    	s.style["max-height"] = "100%";
    	s.style.opacity = 0;
    	s.style.visibility = "hidden";
    	s.style.position = "absolute";
    	s.style.top = 0;
    	s.style.left = 0;
    	this.el.appendChild(s);
    },
    hideSlide: function (id) {
    	var s = this.getSlide(id);
    	_V_.removeClass(s, "vjs-fade-in");
    	_V_.addClass(s, "vjs-fade-out");
    },
    showSlide: function (id){
    	var s = this.getSlide(id);
    	_V_.removeClass(s, "vjs-fade-out");
    	_V_.addClass(s, "vjs-fade-in");
    }
});
_V_.Slideshow.options = {
	cuepointfilter : "slideshow",
	width: "497px",
	height: "373px"
};
//Enable Webcast component
_V_.options.components.webcast = {
	components: {}
};
//Enable slideshow component
_V_.options.components.webcast.components.slideshow = {};<|endoftext|>


<commit_before>// account.js

var org = {

	// added on script load
	/*
	config: {
						api: { hzws: 'http://api.spokanelibrary.org/v2/request/'
									,oclc: 'http://api.spokanelibrary.org/oclc/'
								 }
					 }
	*/
	// added on authentication
	//user: {}

	// called on script load
	init: function() {
		_self = this;

		this.config = ORG.config;

		this.setUser();

		// init ui
		this.initRequestItem();

	} // init()

, setUser: function(user) {
		if ( !ORG.user ) {
			ORG.setUser(user);
		}
		this.user = ORG.user;
}

, initRequestItem: function() {

		//console.log(this.user);

		this.initRequestItemPanels();
		this.initOCLC();

  } // initRequestItem()

, initOCLC: function() {

		// search
		$('body').on('click', '#spl-form-oclc-search', function(e) {
      e.preventDefault();
      _self.oclcSearchKeyword();
    });
    $('body').on('keypress', '#spl-form-search', function(e) {
      // 13 == enter
      if (e.keyCode == 13) {
        e.preventDefault();
        _self.oclcSearchKeyword();
      }
    });

    // pagination
    $('body').on('click', '.paginate-index', function(e) {
      e.preventDefault();
      var start = $(this).data('start-index');
      if ( !$(this).parent('li').hasClass('active') && !$(this).parent('li').hasClass('disabled') ) {
        _self.oclcSearchKeyword(start);
      }
    });

    // select search result
    $('body').on('click', '.oclc-select', function(e) {
      e.preventDefault();

      var bib = {ocn: $(this).data('ocn')
                ,canonical: $(this).data('canonical')
                ,title: $(this).data('title')
                ,author: $(this).data('author')
                ,publisher: $(this).data('publisher')
                ,pubdate: $(this).data('pubdate')
                };
      _self.oclcSelectBib(bib);
    });

} // initOCLC()

, oclcSelectBib: function(bib) {
		//console.log(bib);
		$('#spl-form-title').val(bib.title);
    $('#spl-form-author').val(bib.author);
    $('#spl-form-publisher').val(bib.publisher);
    $('#spl-form-pubdate').val(bib.pubdate);
    $('#spl-form-oclc').val(bib.ocn);
    $('#spl-form-isbn').val(bib.canonical);
    if ( bib.canonical ) {
      this.checkISBNHolding( bib.canonical );
    }
	} // oclcSelectBib()

, checkISBNHolding: function(isbn) {
    // check the SPL catalog for holdings using an isbn search and parsing the resulting xml. ugh.
    console.log(isbn);

    $.ajax({
        url: this.config.api.isbn
        ,data: { isbn: isbn }
      })
      .done(function(obj) {
        if ( 1 == obj.holding ) {
          //console.log('we have this');
          $holding = $('#spl-form-request-holding');
          var tmpl = Handlebars.compile( $('#spl-form-request-holding-tmpl').html() );
          $holding.html(tmpl( {isbn:isbn} ));
        }
      })
      .fail(function() {
      })
      .always(function() {
      });

  } //checkISBNHolding()

, oclcSearchKeyword: function(start) {

		var tmpl;
		var query = $('#spl-form-search').val();
		var $modal = $('#spl-form-oclc-result');

		if ( !start ) {
      start = 1;
    }

    if ( query ) {
    	tmpl = Handlebars.compile( $('#oclc-search-tmpl').html() );
      $modal.html(tmpl({ term: query })).modal('show');

    	$.ajax({
        url: this.config.api.oclc
        ,data: { sru: query // q: $('#spl-form-title').val()
                //,mt: $('#spl-form-material').val()
                ,mt: $('input:radio[name="spl-form[material]"]').val()
                ,index: $('#spl-form-search-index').val()
                ,sort: $('#spl-form-search-sort').val()
                ,start: start
                ,count: 20
                }
      })
      .done(function(results) {
        //console.log(results);
        tmpl = Handlebars.compile( $('#oclc-results-tmpl').html() );
	      $modal.html(tmpl(results));
	    })
      .fail(function() {
      })
      .always(function() {
      });

    }

}

, initRequestItemPanels: function() {
		/*
		$('body').on('change', 'input:radio[name = "spl-form[material]"]', function() {
			var materialTypeDescription = $(this).parent().text();
			console.log(materialTypeDescription);
		});
		*/

		// hide material type selectors and generic request form on select
		$('body').on('show.bs.collapse', '#spl-form-request-panels', function() {
			$('#spl-form-request-panel-choose').collapse('hide');
		});

		// show generic request form when requested
		$('body').on('show.bs.collapse', '.spl-form-request-generic', function() {

			if ( $(this).hasClass('spl-form-request-download') ) {
				$('#spl-form-request-submit-item').hide();
				$('#spl-form-request-submit-download').show();
			} else {
				$('#spl-form-request-submit-item').show();
				$('#spl-form-request-submit-download').hide();
			}

			$('#spl-form-panel-request').collapse('show');

		});

		// hide generic request form when not requested
		$('body').on('shown.bs.collapse', '.spl-form-request-specific', function() {
			$('#spl-form-panel-request').collapse('hide');
		});

} // initRequestItemPanels

};
<commit_msg>request<commit_after>// account.js

var org = {

	// added on script load
	/*
	config: {
						api: { hzws: 'http://api.spokanelibrary.org/v2/request/'
									,oclc: 'http://api.spokanelibrary.org/oclc/'
								 }
					 }
	*/
	// added on authentication
	//user: {}

	// called on script load
	init: function() {
		_self = this;

		this.config = ORG.config;

		this.setUser();

		// init ui
		this.initRequestItem();

	} // init()

, setUser: function(user) {
		if ( !ORG.user ) {
			ORG.setUser(user);
		}
		this.user = ORG.user;
}

, initRequestItem: function() {

		//console.log(this.user);

		this.initRequestItemPanels();
		this.initOCLC();

  } // initRequestItem()

, initOCLC: function() {

		// search
		$('body').on('click', '#spl-form-oclc-search', function(e) {
      e.preventDefault();
      _self.oclcSearchKeyword();
    });
    $('body').on('keypress', '#spl-form-search', function(e) {
      // 13 == enter
      if (e.keyCode == 13) {
        e.preventDefault();
        _self.oclcSearchKeyword();
      }
    });

    // pagination
    $('body').on('click', '.paginate-index', function(e) {
      e.preventDefault();
      var start = $(this).data('start-index');
      if ( !$(this).parent('li').hasClass('active') && !$(this).parent('li').hasClass('disabled') ) {
        _self.oclcSearchKeyword(start);
      }
    });

    // select search result
    $('body').on('click', '.oclc-select', function(e) {
      e.preventDefault();

      var bib = {ocn: $(this).data('ocn')
                ,canonical: $(this).data('canonical')
                ,title: $(this).data('title')
                ,author: $(this).data('author')
                ,publisher: $(this).data('publisher')
                ,pubdate: $(this).data('pubdate')
                };
      _self.oclcSelectBib(bib);
    });

} // initOCLC()

, oclcSelectBib: function(bib) {
		//console.log(bib);
		$('#spl-form-title').val(bib.title);
    $('#spl-form-author').val(bib.author);
    $('#spl-form-publisher').val(bib.publisher);
    $('#spl-form-pubdate').val(bib.pubdate);
    $('#spl-form-oclc').val(bib.ocn);
    $('#spl-form-isbn').val(bib.canonical);
    if ( bib.canonical ) {
      this.checkISBNHolding( bib.canonical );
    }
	} // oclcSelectBib()

, checkISBNHolding: function(isbn) {
    // check the SPL catalog for holdings using an isbn search and parsing the resulting xml. ugh.
    //console.log(isbn);

    $.ajax({
        url: this.config.api.isbn
        ,data: { isbn: isbn }
      })
      .done(function(obj) {
        if ( 1 == obj.holding ) {
          console.log(obj);
          $holding = $('#spl-form-request-holding');
          var tmpl = Handlebars.compile( $('#spl-form-request-holding-tmpl').html() );
          $holding.html(tmpl( {isbn:isbn} ));
        }
      })
      .fail(function() {
      })
      .always(function() {
      });

  } //checkISBNHolding()

, oclcSearchKeyword: function(start) {

		var tmpl;
		var query = $('#spl-form-search').val();
		var $modal = $('#spl-form-oclc-result');

		if ( !start ) {
      start = 1;
    }

    if ( query ) {
    	tmpl = Handlebars.compile( $('#oclc-search-tmpl').html() );
      $modal.html(tmpl({ term: query })).modal('show');

    	$.ajax({
        url: this.config.api.oclc
        ,data: { sru: query // q: $('#spl-form-title').val()
                //,mt: $('#spl-form-material').val()
                ,mt: $('input:radio[name="spl-form[material]"]').val()
                ,index: $('#spl-form-search-index').val()
                ,sort: $('#spl-form-search-sort').val()
                ,start: start
                ,count: 20
                }
      })
      .done(function(results) {
        //console.log(results);
        tmpl = Handlebars.compile( $('#oclc-results-tmpl').html() );
	      $modal.html(tmpl(results));
	    })
      .fail(function() {
      })
      .always(function() {
      });

    }

}

, initRequestItemPanels: function() {
		/*
		$('body').on('change', 'input:radio[name = "spl-form[material]"]', function() {
			var materialTypeDescription = $(this).parent().text();
			console.log(materialTypeDescription);
		});
		*/

		// hide material type selectors and generic request form on select
		$('body').on('show.bs.collapse', '#spl-form-request-panels', function() {
			$('#spl-form-request-panel-choose').collapse('hide');
		});

		// show generic request form when requested
		$('body').on('show.bs.collapse', '.spl-form-request-generic', function() {

			if ( $(this).hasClass('spl-form-request-download') ) {
				$('#spl-form-request-submit-item').hide();
				$('#spl-form-request-submit-download').show();
			} else {
				$('#spl-form-request-submit-item').show();
				$('#spl-form-request-submit-download').hide();
			}

			$('#spl-form-panel-request').collapse('show');

		});

		// hide generic request form when not requested
		$('body').on('shown.bs.collapse', '.spl-form-request-specific', function() {
			$('#spl-form-panel-request').collapse('hide');
		});

} // initRequestItemPanels

};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule MessageQueue
 */

/*eslint no-bitwise: 0*/

'use strict';

const Systrace = require('Systrace');
const ErrorUtils = require('ErrorUtils');
const JSTimersExecution = require('JSTimersExecution');

const invariant = require('fbjs/lib/invariant');
const keyMirror = require('fbjs/lib/keyMirror');
const stringifySafe = require('stringifySafe');

const MODULE_IDS = 0;
const METHOD_IDS = 1;
const PARAMS = 2;
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;

const TO_NATIVE = 1;
const TO_JS = 0;

const TRACE_TAG_REACT_APPS = 1 << 17;

const MethodTypes = keyMirror({
  remote: null,
  remoteAsync: null,
  syncHook: null,
});

const guard = (fn) => {
  try {
    fn();
  } catch (error) {
    ErrorUtils.reportFatalError(error);
  }
};

type Config = {
  remoteModuleConfig: Object,
};

class MessageQueue {
  constructor(configProvider: () => Config, serializeNativeParams: boolean) {
    this._callableModules = {};
    this._queue = [[], [], [], 0];
    this._callbacks = [];
    this._callbackID = 0;
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();
    this._serializeNativeParams = serializeNativeParams;

    if (__DEV__) {
      this._debugInfo = {};
      this._remoteModuleTable = {};
      this._remoteMethodTable = {};
    }

    [
      'invokeCallbackAndReturnFlushedQueue',
      'callFunctionReturnFlushedQueue',
      'callFunction',
      'flushedQueue',
    ].forEach((fn) => (this[fn] = this[fn].bind(this)));

    lazyProperty(this, 'RemoteModules', () => {
      const {remoteModuleConfig} = configProvider();
      const modulesConfig = remoteModuleConfig;
      const modules = this._genModules(modulesConfig);

      if (__DEV__) {
        this._genLookupTables(
          modulesConfig, this._remoteModuleTable, this._remoteMethodTable
        );
      }

      return modules;
    });
  }

  /**
   * Public APIs
   */

  static spy(spyOrToggle){
    if (spyOrToggle === true){
      MessageQueue.prototype.__spy = (info)=>{
        console.log(`${info.type == TO_JS ? 'N->JS' : 'JS->N'} : ` +
                    `${info.module ? (info.module + '.') : ''}${info.method}` +
                    `(${JSON.stringify(info.args)})`);
      };
    } else if (spyOrToggle === false) {
      MessageQueue.prototype.__spy = null;
    } else {
      MessageQueue.prototype.__spy = spyOrToggle;
    }
  }

  callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

  callFunction(module, method, args) {
    let result;
    guard(() => {
      result = this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return result;
  }

  invokeCallbackAndReturnFlushedQueue(cbID, args) {
    guard(() => {
      this.__invokeCallback(cbID, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

  flushedQueue() {
    this.__callImmediates();

    const queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

  processModuleConfig(config, moduleID) {
    const info = this._genModule(config, moduleID);
    this.RemoteModules[info.name] = info.module;
    if (__DEV__) {
      this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
    }
    return info.module;
  }

  getEventLoopRunningTime() {
    return new Date().getTime() - this._eventLoopStartTime;
  }

  /**
   * "Private" methods
   */

  __callImmediates() {
    Systrace.beginEvent('JSTimersExecution.callImmediates()');
    guard(() => JSTimersExecution.callImmediates());
    Systrace.endEvent();
  }

  __nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
      if (__DEV__) {
        // eventually delete old debug info
        (this._callbackID > (1 << 5)) &&
          (this._debugInfo[this._callbackID >> 5] = null);
        this._debugInfo[this._callbackID >> 1] = [module, method];
      }
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }
    var preparedParams = this._serializeNativeParams ? JSON.stringify(params) : params;

    if (__DEV__) {
      global.nativeTraceBeginAsyncFlow &&
        global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
    }
    this._callID++;

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(preparedParams);

    const now = new Date().getTime();
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
    if (__DEV__ && this.__spy && isFinite(module)) {
        this.__spy(
          { type: TO_NATIVE,
            module: this._remoteModuleTable[module],
            method: this._remoteMethodTable[module][method],
            args: params }
        );
    }
  }

  __callFunction(module: string, method: string, args: any) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    Systrace.beginEvent(`${module}.${method}()`);
    if (__DEV__ && this.__spy) {
      this.__spy({ type: TO_JS, module, method, args});
    }
    const moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,
      'Module %s is not a registered callable module (calling %s)',
      module, method
    );
    invariant(
      !!moduleMethods[method],
      'Method %s does not exist on module %s',
      method, module
    );
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }

  __invokeCallback(cbID, args) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    const callback = this._callbacks[cbID];

    if (__DEV__) {
      const debug = this._debugInfo[cbID >> 1];
      const module = debug && this._remoteModuleTable[debug[0]];
      const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
      if (!callback) {
        let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
        if (method) {
          errorMessage = `The callback ${method}() exists in module ${module}, `
          + 'but only one callback may be registered to a function in a native module.';
        }
        invariant(
          callback,
          errorMessage
        );
      }
      const profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
      if (callback && this.__spy && __DEV__) {
        this.__spy({ type: TO_JS, module:null, method:profileName, args });
      }
      Systrace.beginEvent(
        `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
    } else {
      if (!callback) {
        return;
      }
    }

    this._callbacks[cbID & ~1] = null;
    this._callbacks[cbID |  1] = null;
    callback.apply(null, args);

    if (__DEV__) {
      Systrace.endEvent();
    }
  }

  /**
   * Private helper methods
   */

  _genLookupTables(modulesConfig, moduleTable, methodTable) {
    modulesConfig.forEach((config, moduleID) => {
      this._genLookup(config, moduleID, moduleTable, methodTable);
    });
  }

  _genLookup(config, moduleID, moduleTable, methodTable) {
    if (!config) {
      return;
    }

    let moduleName, methods;
    if (moduleHasConstants(config)) {
      [moduleName, , methods] = config;
    } else {
      [moduleName, methods] = config;
    }

    moduleTable[moduleID] = moduleName;
    methodTable[moduleID] = Object.assign({}, methods);
  }

  _genModules(remoteModules) {
    const modules = {};

    remoteModules.forEach((config, moduleID) => {
      const info = this._genModule(config, moduleID);
      if (info) {
        modules[info.name] = info.module;
      }
    });

    return modules;
  }

  _genModule(config, moduleID): ?Object {
    if (!config) {
      return null;
    }

    let moduleName, constants, methods, asyncMethods, syncHooks;
    if (moduleHasConstants(config)) {
      [moduleName, constants, methods, asyncMethods, syncHooks] = config;
    } else {
      [moduleName, methods, asyncMethods, syncHooks] = config;
    }

    const module = {};
    methods && methods.forEach((methodName, methodID) => {
      const isAsync = asyncMethods && arrayContains(asyncMethods, methodID);
      const isSyncHook = syncHooks && arrayContains(syncHooks, methodID);
      invariant(!isAsync || !isSyncHook, 'Cannot have a method that is both async and a sync hook');
      const methodType = isAsync ? MethodTypes.remoteAsync :
          isSyncHook ? MethodTypes.syncHook :
          MethodTypes.remote;
      module[methodName] = this._genMethod(moduleID, methodID, methodType);
    });
    Object.assign(module, constants);

    if (!constants && !methods && !asyncMethods) {
      module.moduleID = moduleID;
    }

    return { name: moduleName, module };
  }

  _genMethod(module, method, type) {
    let fn = null;
    const self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(...args) {
        return new Promise((resolve, reject) => {
          self.__nativeCall(
            module,
            method,
            args,
            (data) => {
              resolve(data);
            },
            (errorData) => {
              var error = createErrorFromErrorData(errorData);
              reject(error);
            });
        });
      };
    } else if (type === MethodTypes.syncHook) {
      return function(...args) {
        return global.nativeCallSyncHook(module, method, args);
      };
    } else {
      fn = function(...args) {
        const lastArg = args.length > 0 ? args[args.length - 1] : null;
        const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
        const hasSuccCB = typeof lastArg === 'function';
        const hasErrorCB = typeof secondLastArg === 'function';
        hasErrorCB && invariant(
          hasSuccCB,
          'Cannot have a non-function arg after a function arg.'
        );
        const numCBs = hasSuccCB + hasErrorCB;
        const onSucc = hasSuccCB ? lastArg : null;
        const onFail = hasErrorCB ? secondLastArg : null;
        args = args.slice(0, args.length - numCBs);
        return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

  registerCallableModule(name, methods) {
    this._callableModules[name] = methods;
  }

}

function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
  return !Array.isArray(moduleArray[1]);
}

function arrayContains<T>(array: Array<T>, value: T): boolean {
  return array.indexOf(value) !== -1;
}

function createErrorFromErrorData(errorData: {message: string}): Error {
  const {
    message,
    ...extraErrorInfo,
  } = errorData;
  const error = new Error(message);
  error.framesToPop = 1;
  return Object.assign(error, extraErrorInfo);
}

function lazyProperty(target: Object, name: string, f: () => any) {
  Object.defineProperty(target, name, {
    configurable: true,
    enumerable: true,
    get() {
      const value = f();
      Object.defineProperty(target, name, {
        configurable: true,
        enumerable: true,
        writeable: true,
        value: value,
      });
      return value;
    }
  });
}

module.exports = MessageQueue;
<commit_msg>Fix memory leak in MessageQueue<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule MessageQueue
 */

/*eslint no-bitwise: 0*/

'use strict';

const Systrace = require('Systrace');
const ErrorUtils = require('ErrorUtils');
const JSTimersExecution = require('JSTimersExecution');

const invariant = require('fbjs/lib/invariant');
const keyMirror = require('fbjs/lib/keyMirror');
const stringifySafe = require('stringifySafe');

const MODULE_IDS = 0;
const METHOD_IDS = 1;
const PARAMS = 2;
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;

const TO_NATIVE = 1;
const TO_JS = 0;

const TRACE_TAG_REACT_APPS = 1 << 17;

const DEBUG_INFO_LIMIT = 32;

const MethodTypes = keyMirror({
  remote: null,
  remoteAsync: null,
  syncHook: null,
});

const guard = (fn) => {
  try {
    fn();
  } catch (error) {
    ErrorUtils.reportFatalError(error);
  }
};

type Config = {
  remoteModuleConfig: Object,
};

class MessageQueue {
  constructor(configProvider: () => Config, serializeNativeParams: boolean) {
    this._callableModules = {};
    this._queue = [[], [], [], 0];
    this._callbacks = [];
    this._callbackID = 0;
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();
    this._serializeNativeParams = serializeNativeParams;

    if (__DEV__) {
      this._debugInfo = {};
      this._remoteModuleTable = {};
      this._remoteMethodTable = {};
    }

    [
      'invokeCallbackAndReturnFlushedQueue',
      'callFunctionReturnFlushedQueue',
      'callFunction',
      'flushedQueue',
    ].forEach((fn) => (this[fn] = this[fn].bind(this)));

    lazyProperty(this, 'RemoteModules', () => {
      const {remoteModuleConfig} = configProvider();
      const modulesConfig = remoteModuleConfig;
      const modules = this._genModules(modulesConfig);

      if (__DEV__) {
        this._genLookupTables(
          modulesConfig, this._remoteModuleTable, this._remoteMethodTable
        );
      }

      return modules;
    });
  }

  /**
   * Public APIs
   */

  static spy(spyOrToggle){
    if (spyOrToggle === true){
      MessageQueue.prototype.__spy = (info)=>{
        console.log(`${info.type == TO_JS ? 'N->JS' : 'JS->N'} : ` +
                    `${info.module ? (info.module + '.') : ''}${info.method}` +
                    `(${JSON.stringify(info.args)})`);
      };
    } else if (spyOrToggle === false) {
      MessageQueue.prototype.__spy = null;
    } else {
      MessageQueue.prototype.__spy = spyOrToggle;
    }
  }

  callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

  callFunction(module, method, args) {
    let result;
    guard(() => {
      result = this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return result;
  }

  invokeCallbackAndReturnFlushedQueue(cbID, args) {
    guard(() => {
      this.__invokeCallback(cbID, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

  flushedQueue() {
    this.__callImmediates();

    const queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

  processModuleConfig(config, moduleID) {
    const info = this._genModule(config, moduleID);
    this.RemoteModules[info.name] = info.module;
    if (__DEV__) {
      this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
    }
    return info.module;
  }

  getEventLoopRunningTime() {
    return new Date().getTime() - this._eventLoopStartTime;
  }

  /**
   * "Private" methods
   */

  __callImmediates() {
    Systrace.beginEvent('JSTimersExecution.callImmediates()');
    guard(() => JSTimersExecution.callImmediates());
    Systrace.endEvent();
  }

  __nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
      if (__DEV__) {
        let callId = this._callbackID >> 1;
        this._debugInfo[callId] = [module, method];
        if (callId > DEBUG_INFO_LIMIT) {
          delete this._debugInfo[callId - DEBUG_INFO_LIMIT];
        }
      }
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }
    var preparedParams = this._serializeNativeParams ? JSON.stringify(params) : params;

    if (__DEV__) {
      global.nativeTraceBeginAsyncFlow &&
        global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
    }
    this._callID++;

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(preparedParams);

    const now = new Date().getTime();
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
    if (__DEV__ && this.__spy && isFinite(module)) {
        this.__spy(
          { type: TO_NATIVE,
            module: this._remoteModuleTable[module],
            method: this._remoteMethodTable[module][method],
            args: params }
        );
    }
  }

  __callFunction(module: string, method: string, args: any) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    Systrace.beginEvent(`${module}.${method}()`);
    if (__DEV__ && this.__spy) {
      this.__spy({ type: TO_JS, module, method, args});
    }
    const moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,
      'Module %s is not a registered callable module (calling %s)',
      module, method
    );
    invariant(
      !!moduleMethods[method],
      'Method %s does not exist on module %s',
      method, module
    );
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }

  __invokeCallback(cbID, args) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    const callback = this._callbacks[cbID];

    if (__DEV__) {
      const debug = this._debugInfo[cbID >> 1];
      const module = debug && this._remoteModuleTable[debug[0]];
      const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
      if (!callback) {
        let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
        if (method) {
          errorMessage = `The callback ${method}() exists in module ${module}, `
          + 'but only one callback may be registered to a function in a native module.';
        }
        invariant(
          callback,
          errorMessage
        );
      }
      const profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
      if (callback && this.__spy && __DEV__) {
        this.__spy({ type: TO_JS, module:null, method:profileName, args });
      }
      Systrace.beginEvent(
        `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
    } else {
      if (!callback) {
        return;
      }
    }

    this._callbacks[cbID & ~1] = null;
    this._callbacks[cbID |  1] = null;
    callback.apply(null, args);

    if (__DEV__) {
      Systrace.endEvent();
    }
  }

  /**
   * Private helper methods
   */

  _genLookupTables(modulesConfig, moduleTable, methodTable) {
    modulesConfig.forEach((config, moduleID) => {
      this._genLookup(config, moduleID, moduleTable, methodTable);
    });
  }

  _genLookup(config, moduleID, moduleTable, methodTable) {
    if (!config) {
      return;
    }

    let moduleName, methods;
    if (moduleHasConstants(config)) {
      [moduleName, , methods] = config;
    } else {
      [moduleName, methods] = config;
    }

    moduleTable[moduleID] = moduleName;
    methodTable[moduleID] = Object.assign({}, methods);
  }

  _genModules(remoteModules) {
    const modules = {};

    remoteModules.forEach((config, moduleID) => {
      const info = this._genModule(config, moduleID);
      if (info) {
        modules[info.name] = info.module;
      }
    });

    return modules;
  }

  _genModule(config, moduleID): ?Object {
    if (!config) {
      return null;
    }

    let moduleName, constants, methods, asyncMethods, syncHooks;
    if (moduleHasConstants(config)) {
      [moduleName, constants, methods, asyncMethods, syncHooks] = config;
    } else {
      [moduleName, methods, asyncMethods, syncHooks] = config;
    }

    const module = {};
    methods && methods.forEach((methodName, methodID) => {
      const isAsync = asyncMethods && arrayContains(asyncMethods, methodID);
      const isSyncHook = syncHooks && arrayContains(syncHooks, methodID);
      invariant(!isAsync || !isSyncHook, 'Cannot have a method that is both async and a sync hook');
      const methodType = isAsync ? MethodTypes.remoteAsync :
          isSyncHook ? MethodTypes.syncHook :
          MethodTypes.remote;
      module[methodName] = this._genMethod(moduleID, methodID, methodType);
    });
    Object.assign(module, constants);

    if (!constants && !methods && !asyncMethods) {
      module.moduleID = moduleID;
    }

    return { name: moduleName, module };
  }

  _genMethod(module, method, type) {
    let fn = null;
    const self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(...args) {
        return new Promise((resolve, reject) => {
          self.__nativeCall(
            module,
            method,
            args,
            (data) => {
              resolve(data);
            },
            (errorData) => {
              var error = createErrorFromErrorData(errorData);
              reject(error);
            });
        });
      };
    } else if (type === MethodTypes.syncHook) {
      return function(...args) {
        return global.nativeCallSyncHook(module, method, args);
      };
    } else {
      fn = function(...args) {
        const lastArg = args.length > 0 ? args[args.length - 1] : null;
        const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
        const hasSuccCB = typeof lastArg === 'function';
        const hasErrorCB = typeof secondLastArg === 'function';
        hasErrorCB && invariant(
          hasSuccCB,
          'Cannot have a non-function arg after a function arg.'
        );
        const numCBs = hasSuccCB + hasErrorCB;
        const onSucc = hasSuccCB ? lastArg : null;
        const onFail = hasErrorCB ? secondLastArg : null;
        args = args.slice(0, args.length - numCBs);
        return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

  registerCallableModule(name, methods) {
    this._callableModules[name] = methods;
  }

}

function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
  return !Array.isArray(moduleArray[1]);
}

function arrayContains<T>(array: Array<T>, value: T): boolean {
  return array.indexOf(value) !== -1;
}

function createErrorFromErrorData(errorData: {message: string}): Error {
  const {
    message,
    ...extraErrorInfo,
  } = errorData;
  const error = new Error(message);
  error.framesToPop = 1;
  return Object.assign(error, extraErrorInfo);
}

function lazyProperty(target: Object, name: string, f: () => any) {
  Object.defineProperty(target, name, {
    configurable: true,
    enumerable: true,
    get() {
      const value = f();
      Object.defineProperty(target, name, {
        configurable: true,
        enumerable: true,
        writeable: true,
        value: value,
      });
      return value;
    }
  });
}

module.exports = MessageQueue;
<|endoftext|>