<commit_before>'use strict';

var schedule = require('node-schedule');

var dbPool = require('./dbpool');
var Session = require('./session');
var SessionList = require('./sessionlist');

function SessionManager() {
    this._sessions = new SessionList();
}

SessionManager.prototype.hasOpenSession = function(sessionId, userId) {
    return this._sessions.has(sessionId, userId);
};

SessionManager.prototype.openSession = function(userId, callback) {
    if (this._sessions.hasForUser(userId)) {
        callback(403, { error: "a session is already open for user id " + userId });
        return;
    }

    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            callback(503, { error: 'error fetching client from pool: ' + err });
            return;
        }

        client.query('SELECT id FROM sessions WHERE user_id=$1::integer AND end_date IS NULL', [ userId ], function(err, result) {
            if (err) {
                done();
                callback(500, { error: 'error running query: ' + err });
                return;
            }

            if (result.rowCount != 0) {
                done();
                console.error("An open session was present in the database for user " + userId);
                callback(403, { error: "a session is already open for user id " + userId });
                return;
            }

            client.query('INSERT INTO sessions VALUES (DEFAULT, $1::integer, DEFAULT, NULL) RETURNING *', [ userId ], function(err, result) {
                done();

                if (err) {
                    callback(500, { error: 'error running query: ' + err });
                    return;
                }

                var row = result.rows[0];
                var session = new Session(row.id, row.user_id, row.start_date);
                that._sessions.add(session);

                console.log("opening session " + session.id + " for user " + session.userId);

                callback(200, { session_id: session.id, start_date: session.startDate });
            });
        });
    });
};

SessionManager.prototype.closeSession = function(sessionId, userId, callback) {
    if (!this._sessions.has(sessionId)) {
        callback(400, { error: "session " + sessionId + " is not an open session for user " + userId });
        return;
    }

    var session = this._sessions.get(sessionId);

    if (session.userId != userId) {
        callback(400, { error: "session " + sessionId + " is not an open session for user " + userId });
        return;
    }

    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            callback(503, { message: 'error fetching client from pool: ' + err });
            return;
        }

        client.query('UPDATE sessions SET end_date = now() WHERE id=$1::integer AND user_id=$2::integer AND end_date IS NULL', [ sessionId, userId ], function(err, result) {
            done();

            if (err) {
                callback(500, { message: 'error running query: ' + err });
                return;
            }

            that._sessions.remove(sessionId);

            console.log("closing session " + sessionId + " for user " + userId);

            callback(200, { message: 'session closed' });
        });
    });
};

SessionManager.prototype.closeOpenSessions = function() {
    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            console.error('Error fetching client from pool: ' + err);
            return;
        }

        client.query('UPDATE sessions SET end_date = now() WHERE end_date IS NULL', [ ], function(err, result) {
            done();

            if (err) {
                console.error('Error running query: ' + err);
                return;
            }

            that._sessions.clear();

            if (result.rowCount > 0) {
                console.log(result.rowCount + ' open sessions closed')
            }
        });
    });
};

SessionManager.prototype.scheduleSessionClosing = function() {
    var that = this;

    var rule = new schedule.RecurrenceRule();
    rule.minute = [ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 ];
    schedule.scheduleJob(rule, function() {
        dbPool.connect(function(err, client, done) {
            if (err) {
                console.error('Error fetching client from pool: ' + err);
                return;
            }

            client.query('UPDATE sessions SET end_date = now() WHERE start_date < now() - INTERVAL \'2 hours\' AND end_date IS NULL RETURNING id', [ ], function(err, result) {
                done();

                if (err) {
                    console.error('Error running query: ' + err);
                    return;
                }

                for (var i = 0; i < result.rowCount; i++) {
                    that._sessions.remove(result.rows[i].id);
                }

                if (result.rowCount > 0) {
                    console.log(result.rowCount + ' open sessions closed')
                }
            });
        });
    });
};

var sessionManager = new SessionManager();

module.exports = sessionManager;
<commit_msg>Release untagged reserved buildings when corresponding session is closed<commit_after>'use strict';

var schedule = require('node-schedule');

var dbPool = require('./dbpool');
var Session = require('./session');
var SessionList = require('./sessionlist');

function SessionManager() {
    this._sessions = new SessionList();
}

SessionManager.prototype.hasOpenSession = function(sessionId, userId) {
    return this._sessions.has(sessionId, userId);
};

SessionManager.prototype.openSession = function(userId, callback) {
    if (this._sessions.hasForUser(userId)) {
        callback(403, { error: "a session is already open for user id " + userId });
        return;
    }

    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            callback(503, { error: 'error fetching client from pool: ' + err });
            return;
        }

        client.query('SELECT id FROM sessions WHERE user_id=$1::integer AND end_date IS NULL', [ userId ], function(err, result) {
            if (err) {
                done();
                callback(500, { error: 'error running query: ' + err });
                return;
            }

            if (result.rowCount != 0) {
                done();
                console.error("An open session was present in the database for user " + userId);
                callback(403, { error: "a session is already open for user id " + userId });
                return;
            }

            client.query('INSERT INTO sessions VALUES (DEFAULT, $1::integer, DEFAULT, NULL) RETURNING *', [ userId ], function(err, result) {
                done();

                if (err) {
                    callback(500, { error: 'error running query: ' + err });
                    return;
                }

                var row = result.rows[0];
                var session = new Session(row.id, row.user_id, row.start_date);
                that._sessions.add(session);

                console.log("opening session " + session.id + " for user " + session.userId);

                callback(200, { session_id: session.id, start_date: session.startDate });
            });
        });
    });
};

SessionManager.prototype.closeSession = function(sessionId, userId, callback) {
    if (!this._sessions.has(sessionId)) {
        callback(400, { error: "session " + sessionId + " is not an open session for user " + userId });
        return;
    }

    var session = this._sessions.get(sessionId);

    if (session.userId != userId) {
        callback(400, { error: "session " + sessionId + " is not an open session for user " + userId });
        return;
    }

    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            callback(503, { message: 'error fetching client from pool: ' + err });
            return;
        }

        client.query('UPDATE sessions SET end_date = now() WHERE id=$1::integer AND user_id=$2::integer AND end_date IS NULL', [ sessionId, userId ], function(err, result) {
            if (err) {
                callback(500, { message: 'error running query: ' + err });
                return;
            }

            that._sessions.remove(sessionId);

            console.log("closing session " + sessionId + " for user " + userId);

            that.releaseSessionBuildings([ sessionId ], client, function(err) {
                done();

                if (err) {
                    callback(500, { message: 'error running query: ' + err });
                    return;
                }

                callback(200, { message: 'session closed' });
            });
        });
    });
};

SessionManager.prototype.closeOpenSessions = function() {
    var that = this;

    dbPool.connect(function(err, client, done) {
        if (err) {
            console.error('Error fetching client from pool: ' + err);
            return;
        }

        client.query('UPDATE sessions SET end_date = now() WHERE end_date IS NULL RETURNING id', [ ], function(err, result) {
            done();

            if (err) {
                console.error('Error running query: ' + err);
                return;
            }

            that._sessions.clear();

            if (result.rowCount > 0) {
                console.log(result.rowCount + ' open sessions closed');

                var sessionIds = result.rows.map(function(row) {
                    return row.id;
                });

                that.releaseSessionBuildings(sessionIds, client, function(err) {
                    done();
                });
            }
        });
    });
};

SessionManager.prototype.scheduleSessionClosing = function() {
    var that = this;

    var rule = new schedule.RecurrenceRule();
    //rule.minute = [ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 ];
    rule.second = [ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 ];
    schedule.scheduleJob(rule, function() {
        dbPool.connect(function(err, client, done) {
            if (err) {
                console.error('Error fetching client from pool: ' + err);
                return;
            }

            client.query('UPDATE sessions SET end_date = now() WHERE start_date < now() - INTERVAL \'1 minutes\' AND end_date IS NULL RETURNING id', [ ], function(err, result) {
                if (err) {
                    console.error('Error running query: ' + err);
                    return;
                }

                for (var i = 0; i < result.rowCount; i++) {
                    that._sessions.remove(result.rows[i].id);
                }

                if (result.rowCount > 0) {
                    console.log(result.rowCount + ' open sessions closed');

                    var sessionIds = result.rows.map(function(row) {
                        return row.id;
                    });

                    that.releaseSessionBuildings(sessionIds, client, function(err) {
                        done();
                    });
                } else {
                    done();
                }
            });
        });
    });
};

SessionManager.prototype.releaseSessionBuildings = function(sessionIds, client, callback) {
    client.query('UPDATE buildings SET session_id = NULL WHERE session_id = ANY($1::integer[]) AND changeset_id IS NULL', [ sessionIds ], function(err, result) {
        if (err) {
            console.error('Error running query: ' + err);
            callback(err);
            return;
        }

        if (result.rowCount > 0) {
            console.log(result.rowCount + ' buildings released from sessions [' + sessionIds + ']');
        }

        callback();
    });
};

var sessionManager = new SessionManager();

module.exports = sessionManager;
<|endoftext|>


<commit_before>//helpers
h={
	showObj: function(o){
		//object to string including methods
		var s="";
		for (i in o) s+=i+":"+o[i]+"\n\n";
		h.showWin(s);
	},

	//displaying string in a new window
	showWin: function(s){
		var pattern=new RegExp('^','i');
		if(!pattern.test(s)){
			s=s.replace(new RegExp("[<]","g"),'&lt');s=s.replace(new RegExp("[>]","g"),'&gt');
			s='
'+s+'
';
		}
		var W = window.open("",'','Width=600,Height=480,resizable=yes,scrollbars=yes,left=0,top=0',true);
		W.focus();W.document.open();W.document.write(s);W.document.close();
	},

	// extraction for URL parameters
	getQueryVariable: function(variable) {
		var pair, i;
		var query = window.location.search.substring(1);
		var vars = query.split("&");
		for (i = 0; i < vars.length; i = i + 1) {
			pair = vars[i].split("=");
			if (pair[0].toUpperCase() === variable) {
				return pair[1];
			}
		}
		return false;
	}
};

// check WebGL
if (!window.WebGLRenderingContext) {
  // the browser doesn't even know what WebGL is
  window.location = "http://get.webgl.org";
}

Dim = {
  div: document.getElementById("webgl"),
  envelope: [],
  demWidth: 0,
  demHeight: 0,
  pixelsPerVertex: 8,//the resolution of the height model in the image
  wcsUrl: location.protocol+'://wms.geonorge.no/skwms1/wcs.dtm',
  wcsResolution: 10, //resolution of grid cell in meters
  proxy: "",

  bbox: h.getQueryVariable("BBOX")||'161244,6831251,171526,6837409',
  crs: h.getQueryVariable("CRS") || h.getQueryVariable("SRS") || 'EPSG:32633',
  coverage: h.getQueryVariable("COVERAGE") || 'land_utm33_10m',//'land_utm33_10m',
  imgUrl: h.getQueryVariable("IMAGE")||false,
  wmsUrl: h.getQueryVariable("WMS") || location.protocol+'://wms.geonorge.no/skwms1/wms.topo2',
  //wmsUrl: h.getQueryVariable("WMS") || 'http://openwms.statkart.no/skwms1/wms.topo2',
  wmsLayers: h.getQueryVariable("LAYERS") || 'topo2_WMS',//layers //'topo2_WMS'
  wmsFormat: h.getQueryVariable("FORMAT") || "image/png",
  wmsFormatMode: "",
  zMult: h.getQueryVariable("ZMULT")||1,
  wireframe: h.getQueryVariable("WIREFRAME")||false,
  imgWidth: h.getQueryVariable("WIDTH")||false,
  imgHeight: h.getQueryVariable("HEIGHT")||false,
  zInv: h.getQueryVariable("ZINV")||false,
  Z: h.getQueryVariable("Z")||0,

  init: function () {
	this.width = this.div.clientWidth;
	this.height = this.div.clientHeight;

	if (this.imgUrl) this.wmsUrl = false;

	//Bbox
    var bbox = this.bbox.split(',');
	this.envelope = [Number(bbox[0]), Number(bbox[1]), Number(bbox[2]), Number(bbox[3])];
    this.metersWidth = this.envelope[2] - this.envelope[0];
    this.metersHeight = this.envelope[3] - this.envelope[1];

	//Format mode
    if (h.getQueryVariable("WMSFORMATMODE")) {
      this.wmsFormatMode = '; mode=' + h.getQueryVariable("WMSFORMATMODE");
    }

	//Adjust output image to canvas if not specified
	if (!this.imgWidth || !this.imgHeight){
		var imgCoefficient = this.metersWidth / this.metersHeight;
		if ((this.width/this.height)<imgCoefficient){
			this.imgWidth = this.width;
			this.imgHeight = Math.round(this.imgWidth / imgCoefficient);
		} else {
			this.imgHeight = this.height;
			this.imgWidth = Math.round(this.imgHeight * imgCoefficient);
		}
	}
	//console.log(this.imgWidth, this.imgHeight);

	//Compute the resolution of the height model in the image (pixelsPerVertex)
	this.demWidth = Math.round(this.imgWidth /  this.pixelsPerVertex);
	this.demHeight = Math.round(this.imgHeight / this.pixelsPerVertex);

	//When zooming into large scales, the computed resolution of the height model will be larger than the actual resolution.
	//Adjustment to actual resolution is made to avoid stairs effect in the height model.
	if (this.demWidth>this.metersWidth/this.wcsResolution){
		//ajust to avoid stairs in the model - reduse dem to actual resolution
		this.demWidth = Math.round(this.metersWidth/this.wcsResolution);
		this.demHeight = Math.round(this.metersHeight/this.wcsResolution);
	}
	//console.log(this.demWidth, this.demHeight);

	//Adjust zMult
	var proportionWidth=this.metersWidth/this.demWidth; // mapunits between vertexes in x-dimention
	var proportionHeight=this.metersHeight/this.demHeight; // mapunits between vertexes in y-dimention
	var proportionAverage=((proportionWidth+proportionHeight)/2); // average mapunits between vertexes

	if (this.zInv){
      proportionAverage=proportionAverage*-1;
	}
	if (this.zMult){
		this.zMult=proportionAverage/this.zMult;
	} else {
		this.zMult=proportionAverage;
	}

	if (this.proxy) {
		if (this.wmsUrl) this.wmsUrl = this.proxy + this.wmsUrl;
		this.wcsUrl = this.proxy + this.wcsUrl;
	}

    return this;
  }
};

var dim = Dim.init();
var threeDMap = new wxs3.ThreeDMap(dim);
<commit_msg>wxs.threejs-NGU<commit_after>//helpers
h={
	showObj: function(o){
		//object to string including methods
		var s="";
		for (i in o) s+=i+":"+o[i]+"\n\n";
		h.showWin(s);
	},

	//displaying string in a new window
	showWin: function(s){
		var pattern=new RegExp('^','i');
		if(!pattern.test(s)){
			s=s.replace(new RegExp("[<]","g"),'&lt');s=s.replace(new RegExp("[>]","g"),'&gt');
			s='
'+s+'
';
		}
		var W = window.open("",'','Width=600,Height=480,resizable=yes,scrollbars=yes,left=0,top=0',true);
		W.focus();W.document.open();W.document.write(s);W.document.close();
	},

	// extraction for URL parameters
	getQueryVariable: function(variable) {
		var pair, i;
		var query = window.location.search.substring(1);
		var vars = query.split("&");
		for (i = 0; i < vars.length; i = i + 1) {
			pair = vars[i].split("=");
			if (pair[0].toUpperCase() === variable) {
				return pair[1];
			}
		}
		return false;
	}
};

// check WebGL
if (!window.WebGLRenderingContext) {
  // the browser doesn't even know what WebGL is
  window.location = "http://get.webgl.org";
}

Dim = {
  div: document.getElementById("webgl"),
  envelope: [],
  demWidth: 0,
  demHeight: 0,
  pixelsPerVertex: 8,//the resolution of the height model in the image
  wcsUrl: location.protocol+'//wms.geonorge.no/skwms1/wcs.dtm',
  wcsResolution: 10, //resolution of grid cell in meters
  proxy: "",

  bbox: h.getQueryVariable("BBOX")||'161244,6831251,171526,6837409',
  crs: h.getQueryVariable("CRS") || h.getQueryVariable("SRS") || 'EPSG:32633',
  coverage: h.getQueryVariable("COVERAGE") || 'land_utm33_10m',//'land_utm33_10m',
  imgUrl: h.getQueryVariable("IMAGE")||false,
  wmsUrl: h.getQueryVariable("WMS") || location.protocol+'//wms.geonorge.no/skwms1/wms.topo2',
  //wmsUrl: h.getQueryVariable("WMS") || 'http://openwms.statkart.no/skwms1/wms.topo2',
  wmsLayers: h.getQueryVariable("LAYERS") || 'topo2_WMS',//layers //'topo2_WMS'
  wmsFormat: h.getQueryVariable("FORMAT") || "image/png",
  wmsFormatMode: "",
  zMult: h.getQueryVariable("ZMULT")||1,
  wireframe: h.getQueryVariable("WIREFRAME")||false,
  imgWidth: h.getQueryVariable("WIDTH")||false,
  imgHeight: h.getQueryVariable("HEIGHT")||false,
  zInv: h.getQueryVariable("ZINV")||false,
  Z: h.getQueryVariable("Z")||0,

  init: function () {
	this.width = this.div.clientWidth;
	this.height = this.div.clientHeight;

	if (this.imgUrl) this.wmsUrl = false;

	//Bbox
    var bbox = this.bbox.split(',');
	this.envelope = [Number(bbox[0]), Number(bbox[1]), Number(bbox[2]), Number(bbox[3])];
    this.metersWidth = this.envelope[2] - this.envelope[0];
    this.metersHeight = this.envelope[3] - this.envelope[1];

	//Format mode
    if (h.getQueryVariable("WMSFORMATMODE")) {
      this.wmsFormatMode = '; mode=' + h.getQueryVariable("WMSFORMATMODE");
    }

	//Adjust output image to canvas if not specified
	if (!this.imgWidth || !this.imgHeight){
		var imgCoefficient = this.metersWidth / this.metersHeight;
		if ((this.width/this.height)<imgCoefficient){
			this.imgWidth = this.width;
			this.imgHeight = Math.round(this.imgWidth / imgCoefficient);
		} else {
			this.imgHeight = this.height;
			this.imgWidth = Math.round(this.imgHeight * imgCoefficient);
		}
	}
	//console.log(this.imgWidth, this.imgHeight);

	//Compute the resolution of the height model in the image (pixelsPerVertex)
	this.demWidth = Math.round(this.imgWidth /  this.pixelsPerVertex);
	this.demHeight = Math.round(this.imgHeight / this.pixelsPerVertex);

	//When zooming into large scales, the computed resolution of the height model will be larger than the actual resolution.
	//Adjustment to actual resolution is made to avoid stairs effect in the height model.
	if (this.demWidth>this.metersWidth/this.wcsResolution){
		//ajust to avoid stairs in the model - reduse dem to actual resolution
		this.demWidth = Math.round(this.metersWidth/this.wcsResolution);
		this.demHeight = Math.round(this.metersHeight/this.wcsResolution);
	}
	//console.log(this.demWidth, this.demHeight);

	//Adjust zMult
	var proportionWidth=this.metersWidth/this.demWidth; // mapunits between vertexes in x-dimention
	var proportionHeight=this.metersHeight/this.demHeight; // mapunits between vertexes in y-dimention
	var proportionAverage=((proportionWidth+proportionHeight)/2); // average mapunits between vertexes

	if (this.zInv){
      proportionAverage=proportionAverage*-1;
	}
	if (this.zMult){
		this.zMult=proportionAverage/this.zMult;
	} else {
		this.zMult=proportionAverage;
	}

	if (this.proxy) {
		if (this.wmsUrl) this.wmsUrl = this.proxy + this.wmsUrl;
		this.wcsUrl = this.proxy + this.wcsUrl;
	}

    return this;
  }
};

var dim = Dim.init();
var threeDMap = new wxs3.ThreeDMap(dim);
<|endoftext|>


<commit_before>/*
  Each batch of link maps (of length batchSize) in a batchList will have a forked rssProcessor
*/
const FeedParser = require('feedparser');
const requestStream = require('./request.js')
const sqlConnect = require('./sql/connect.js')
const sqlCmds = require('./sql/commands.js')
const logFeedErr = require('../util/logFeedErrs.js')
if (require('../config.json').logging.logDates) require('../util/logDates.js')()

let con

Object.defineProperty(Object.prototype, 'size', {
    value: function() {
      let c = 0
      for (var x in this) if (this.hasOwnProperty(x)) c++;
      return c
    },
    enumerable: false,
    writable: true
});

function rss(link, rssList, uniqueSettings, debugFeeds) {
  const feedparser = new FeedParser()
  const currentFeed = []

  const cookies = (uniqueSettings && uniqueSettings.cookies) ? uniqueSettings.cookies : undefined

  requestStream(link, cookies, feedparser, function(err) {
    if (err) {
      logFeedErr({link: link, content: err}, true);
      process.send({status: 'failed', link: link, rssList: rssList});
    }
  })

  feedparser.on('error', function(err) {
    logFeedErr({link: link, content: err}, true);
    process.send({status: 'failed', link: link, rssList: rssList})
    feedparser.removeAllListeners('end')
  });

  feedparser.on('readable', function() {
    let item

    while (item = this.read()) {
      currentFeed.push(item);
    }

  });

  feedparser.on('end', function() {

    if (currentFeed.length === 0) return process.send({status: 'success'});

    let sourcesCompleted = 0

    function getArticleId(article) {
      let equalGuids = (currentFeed.length > 1) ? true : false // default to true for most feeds
      if (equalGuids && currentFeed[0].guid) for (var x in currentFeed) {
        if (x > 0 && currentFeed[x].guid != currentFeed[x - 1].guid) equalGuids = false;
      }

      if ((!article.guid || equalGuids) && article.title) return article.title;
      if ((!article.guid || equalGuids) && !article.title && article.pubdate && article.pubdate !== "Invalid Date") return article.pubdate;
      return article.guid;
    }

    function processSource(rssName) {
      const channelId = rssList[rssName].channel
      checkTableExists()

      let newArticles = [];
      let processedItems = 0;
      let filteredItems = 0;

      function checkTableExists() {
        sqlCmds.selectTable(con, rssName, function(err, results) {
          if (err || results.length === 0) {
            if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
            if (results.length === 0) console.log(`RSS Info: '${rssName}' appears to have been deleted, skipping...`);
            return process.send({status: 'success'}); // Callback no error object because 99% of the time it is just a hiccup
          }

          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Table has been selected.`)

          const feedLength = currentFeed.length - 1;
          for (var x = feedLength; x >= 0; x--) {
            // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Checking table for (ID: ${getArticleId(currentFeed[x])}, TITLE: ${currentFeed[x].title})`);
            checkTable(currentFeed[x], getArticleId(currentFeed[x]));
            filteredItems++;
          }
        })
      }

      function checkTable(article, articleId) {
        let seenArticle = false
        sqlCmds.selectId(con, rssName, articleId, function(err, idMatches, fields) {
          if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
          if (idMatches.length > 0) {
            // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Matched ID in table for (ID: ${articleId}, TITLE: ${article.title}).`);
            return decideAction(true);
          }
          sqlCmds.selectTitle(con, rssName, article.title, function(err, titleMatches) { // Double check if title exists if ID was not found in table and is apparently a new article
            if (err) return decideAction(false);                                         // Preventing articles with different GUIDs but same titles from sending is a priority
            if (titleMatches.length > 0) {
              // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Matched TITLE in table for (ID: ${articleId}, TITLE: ${article.title}).`);
              return decideAction(true);
            }
            if (article.pubdate && article.pubdate !== 'Invalid Date' && article.pubdate > moment().subtract(1, 'days')) return decideAction(false); // Only send if newer than a day
            decideAction(false)
          })
        })

        function decideAction(seenArticle) {
          if (seenArticle) return gatherResults();
          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Never seen article (ID: ${articleId}, TITLE: ${article.title}), sending now`);
          article.rssName = rssName
          article.discordChannelId = channelId
          process.send({status: 'article', article: article})
          insertIntoTable({
            id: articleId,
            title: article.title
          })
        }
      }

      function insertIntoTable(articleInfo) {
        sqlCmds.insert(con, rssName, articleInfo, function(err, res) { // inserting the feed into the table marks it as "seen"
          if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Article (ID: ${articleInfo.id}, TITLE: ${articleInfo.title}) should have been sent, and now added into table`);
          gatherResults();
        })
      }

      function gatherResults() {
        processedItems++
        if (processedItems === filteredItems) return finishSource(); // Handling on a source ends when processedItems = filteredItems
      }
    }

    for (var rssName in rssList) processSource(rssName); // Per source in one link

    function finishSource() {
      sourcesCompleted++
      if (sourcesCompleted === rssList.size()) return process.send({status: 'success'});
    }

  })

}

process.on('message', function(m) {

  if (!con) con = sqlConnect(function() {
    rss(m.link, m.rssList, m.uniqueSettings, m.debugFeeds)
  })
  else rss(m.link, m.rssList, m.uniqueSettings, m.debugFeeds);

})
<commit_msg>Fixed crash from missing 'moment'<commit_after>/*
  Each batch of link maps (of length batchSize) in a batchList will have a forked rssProcessor
*/
const FeedParser = require('feedparser');
const requestStream = require('./request.js')
const sqlConnect = require('./sql/connect.js')
const sqlCmds = require('./sql/commands.js')
const logFeedErr = require('../util/logFeedErrs.js')
const moment = require('moment-timezone')
if (require('../config.json').logging.logDates) require('../util/logDates.js')()

let con

Object.defineProperty(Object.prototype, 'size', {
    value: function() {
      let c = 0
      for (var x in this) if (this.hasOwnProperty(x)) c++;
      return c
    },
    enumerable: false,
    writable: true
});

function rss(link, rssList, uniqueSettings, debugFeeds) {
  const feedparser = new FeedParser()
  const currentFeed = []

  const cookies = (uniqueSettings && uniqueSettings.cookies) ? uniqueSettings.cookies : undefined

  requestStream(link, cookies, feedparser, function(err) {
    if (err) {
      logFeedErr({link: link, content: err}, true);
      process.send({status: 'failed', link: link, rssList: rssList});
    }
  })

  feedparser.on('error', function(err) {
    logFeedErr({link: link, content: err}, true);
    process.send({status: 'failed', link: link, rssList: rssList})
    feedparser.removeAllListeners('end')
  });

  feedparser.on('readable', function() {
    let item

    while (item = this.read()) {
      currentFeed.push(item);
    }

  });

  feedparser.on('end', function() {

    if (currentFeed.length === 0) return process.send({status: 'success'});

    let sourcesCompleted = 0

    function getArticleId(article) {
      let equalGuids = (currentFeed.length > 1) ? true : false // default to true for most feeds
      if (equalGuids && currentFeed[0].guid) for (var x in currentFeed) {
        if (x > 0 && currentFeed[x].guid != currentFeed[x - 1].guid) equalGuids = false;
      }

      if ((!article.guid || equalGuids) && article.title) return article.title;
      if ((!article.guid || equalGuids) && !article.title && article.pubdate && article.pubdate !== "Invalid Date") return article.pubdate;
      return article.guid;
    }

    function processSource(rssName) {
      const channelId = rssList[rssName].channel
      checkTableExists()

      let newArticles = [];
      let processedItems = 0;
      let filteredItems = 0;

      function checkTableExists() {
        sqlCmds.selectTable(con, rssName, function(err, results) {
          if (err || results.length === 0) {
            if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
            if (results.length === 0) console.log(`RSS Info: '${rssName}' appears to have been deleted, skipping...`);
            return process.send({status: 'success'}); // Callback no error object because 99% of the time it is just a hiccup
          }

          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Table has been selected.`)

          const feedLength = currentFeed.length - 1;
          for (var x = feedLength; x >= 0; x--) {
            // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Checking table for (ID: ${getArticleId(currentFeed[x])}, TITLE: ${currentFeed[x].title})`);
            checkTable(currentFeed[x], getArticleId(currentFeed[x]));
            filteredItems++;
          }
        })
      }

      function checkTable(article, articleId) {
        let seenArticle = false
        sqlCmds.selectId(con, rssName, articleId, function(err, idMatches, fields) {
          if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
          if (idMatches.length > 0) {
            // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Matched ID in table for (ID: ${articleId}, TITLE: ${article.title}).`);
            return decideAction(true);
          }
          sqlCmds.selectTitle(con, rssName, article.title, function(err, titleMatches) { // Double check if title exists if ID was not found in table and is apparently a new article
            if (err) return decideAction(false);                                         // Preventing articles with different GUIDs but same titles from sending is a priority
            if (titleMatches.length > 0) {
              // if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Matched TITLE in table for (ID: ${articleId}, TITLE: ${article.title}).`);
              return decideAction(true);
            }
            if (article.pubdate && article.pubdate !== 'Invalid Date' && article.pubdate > moment().subtract(1, 'days')) return decideAction(false); // Only send if newer than a day
            decideAction(false)
          })
        })

        function decideAction(seenArticle) {
          if (seenArticle) return gatherResults();
          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Never seen article (ID: ${articleId}, TITLE: ${article.title}), sending now`);
          article.rssName = rssName
          article.discordChannelId = channelId
          process.send({status: 'article', article: article})
          insertIntoTable({
            id: articleId,
            title: article.title
          })
        }
      }

      function insertIntoTable(articleInfo) {
        sqlCmds.insert(con, rssName, articleInfo, function(err, res) { // inserting the feed into the table marks it as "seen"
          if (err) return logFeedErr({type: 'database', content: err, feed: rssList[rssName]});
          if (debugFeeds.includes(rssName)) console.log(`DEBUG ${rssName}: Article (ID: ${articleInfo.id}, TITLE: ${articleInfo.title}) should have been sent, and now added into table`);
          gatherResults();
        })
      }

      function gatherResults() {
        processedItems++
        if (processedItems === filteredItems) return finishSource(); // Handling on a source ends when processedItems = filteredItems
      }
    }

    for (var rssName in rssList) processSource(rssName); // Per source in one link

    function finishSource() {
      sourcesCompleted++
      if (sourcesCompleted === rssList.size()) return process.send({status: 'success'});
    }

  })

}

process.on('message', function(m) {

  if (!con) con = sqlConnect(function() {
    rss(m.link, m.rssList, m.uniqueSettings, m.debugFeeds)
  })
  else rss(m.link, m.rssList, m.uniqueSettings, m.debugFeeds);

})
<|endoftext|>


<commit_before>/*!
 * promises.js
 *
 * Copyright (c) 2011, Tom Lokhorst
 * Released under BSD licence, see LICENSE file.
 */

;

// A deferred is a value that can be resolved at a later time.
// The value can be observed via the promise object
function Deferred()
{
  var value = undefined;
  var isDone = false;
  var doneCallbacks = [];
  var promise = new Promise();

  promise.onDone = function (cb)
  {
    if (typeof cb !== "function")
      throw new Error("Callback is not a function");

    doneCallbacks.push(cb);
  };

  promise.isDone = function ()
  {
    return isDone;
  };

  this.done = function (val)
  {
    if (isDone)
      throw new Error("Deferred is already done");

    value = val;
    isDone = true;

    for (var i = 0, l = doneCallbacks.length; i < l; i++)
    {
      try
      {
        doneCallbacks[i](value);
      }
      catch (_)
      {
        // ignore exception, other callbacks might still have to be called
      }
    }

    // these can be GC'ed now
    doneCallbacks = null;

    // now that value is available, callbacks can be called immediately
    promise.onDone = function (cb)
    {
      if (typeof cb !== "function")
        throw new Error("Callback is not a function");

      try
      {
        cb(value);
      }
      catch (_)
      {
        // ignore exception, to be consistent with original onDone
      }
    };
  };

  this.promise = function ()
  {
    return promise;
  };

  this.toString = function ()
  {
    return "Deferred";
  };
}

// A promise is a value that may not be available yet.
// Use onDone to get called back when value is available.
function Promise()
{
  this.toString = function ()
  {
    return "Promise";
  };
}

Promise.map = function (f, pa) // (a -> b) -> Promise a -> Promise b
{
  if (typeof f !== "function")
    throw new Error("First argument is not a function");

  if (!(pa instanceof Promise))
    throw new Error("Second argument is not a Promise");

  var d = new Deferred();

  pa.onDone(function (val)
  {
    var x;
    try
    {
      x = f(val);
    }
    catch (_)
    {
      // Promises don't have support for exceptions, yet
      return;
    }

    d.done(x);
  });

  var p = d.promise();
  p.toString = function ()
  {
    return pa.toString() + ".map(f)";
  };

  return p;
};

Promise.flatten = function (pp) // Promise (Promise a) -> Promise a
{
  if (!(pa instanceof Promise))
    throw new Error("First argument is not a Promise");

  var d = new Deferred();

  pp.onDone(function (pa)
  {
    pa.onDone(d.done);
  });

  var p = d.promise();
  p.toString = function ()
  {
    return pp.toString() + ".flatten()";
  };

  return d.promise();
};

// For convenience, functions as methods
Promise.prototype = {
  map: function (f)
  {
    return Promise.map(f, this);
  },

  flatten: function ()
  {
    return Promise.flatten(this);
  }
};

<commit_msg>Added Listener object, to allow listeners to stop listening<commit_after>/*!
 * promises.js
 *
 * Copyright (c) 2011, Tom Lokhorst
 * Released under BSD licence, see LICENSE file.
 */

;

// A deferred is a value that can be resolved at a later time.
// The value can be observed via the promise object
function Deferred()
{
  var value = undefined;
  var isDone = false;
  var doneCallbacks = [];
  var promise = new Promise();

  promise.onDone = function (cb)
  {
    if (typeof cb !== "function")
      throw new Error("Callback is not a function");

    doneCallbacks.push(cb);

    var l = new Listener()
    l.stop = function ()
    {
      if (!doneCallbacks) return;

      var ix = doneCallbacks.indexOf(cb);
      if (ix > -1)
        doneCallbacks.splice(ix, 1);
    }
    l.toString = function ()
    {
      return "Listener to [" + promise.toString() + "]";
    };

    return l;
  };

  promise.isDone = function ()
  {
    return isDone;
  };

  this.done = function (val)
  {
    if (isDone)
      throw new Error("Deferred is already done");

    value = val;
    isDone = true;

    for (var i = 0, l = doneCallbacks.length; i < l; i++)
    {
      try
      {
        doneCallbacks[i](value);
      }
      catch (_)
      {
        // ignore exception, other callbacks might still have to be called
      }
    }

    // these can be GC'ed now
    doneCallbacks = null;

    // now that value is available, callbacks can be called immediately
    promise.onDone = function (cb)
    {
      if (typeof cb !== "function")
        throw new Error("Callback is not a function");

      try
      {
        cb(value);
      }
      catch (_)
      {
        // ignore exception, to be consistent with original onDone
      }

      var l = new Listener();
      l.stop = function () {} // already called back
      l.toString = function ()
      {
        return "Listener to [" + promise.toString() + "]";
      };

      return l;
    };
  };

  this.promise = function ()
  {
    return promise;
  };

  this.toString = function ()
  {
    return "Deferred";
  };
}

// A promise is a value that may not be available yet.
// Use onDone to get called back when value is available.
function Promise()
{
  this.toString = function ()
  {
    return "Promise";
  };
}

Promise.map = function (f, pa) // (a -> b) -> Promise a -> Promise b
{
  if (typeof f !== "function")
    throw new Error("First argument is not a function");

  if (!(pa instanceof Promise))
    throw new Error("Second argument is not a Promise");

  var d = new Deferred();

  pa.onDone(function (val)
  {
    var x;
    try
    {
      x = f(val);
    }
    catch (_)
    {
      // Promises don't have support for exceptions, yet
      return;
    }

    d.done(x);
  });

  var p = d.promise();
  p.toString = function ()
  {
    return pa.toString() + ".map(f)";
  };

  return p;
};

Promise.flatten = function (pp) // Promise (Promise a) -> Promise a
{
  if (!(pa instanceof Promise))
    throw new Error("First argument is not a Promise");

  var d = new Deferred();

  pp.onDone(function (pa)
  {
    pa.onDone(d.done);
  });

  var p = d.promise();
  p.toString = function ()
  {
    return pp.toString() + ".flatten()";
  };

  return d.promise();
};

// For convenience, functions as methods
Promise.prototype = {
  map: function (f)
  {
    return Promise.map(f, this);
  },

  flatten: function ()
  {
    return Promise.flatten(this);
  }
};

// Listener objects are returned by onDone and allow listeners to Promises to
// stop listening.
function Listener()
{
}

<|endoftext|>


<commit_before>var fs = require('fs'),
    Epoll = require('epoll').Epoll,
    gpioRootPath = '/sys/class/gpio/',
    zero = new Buffer('0'),
    one = new Buffer('1');

exports.version = '0.3.0';

/**
 * Constructor. Exports a GPIO to userspace.
 *
 * The constructor is written to function for both superusers and
 * non-superusers. See README.md for more details.
 *
 * gpio: number      // The Linux GPIO identifier; an unsigned integer.
 * direction: string // Specifies whether the GPIO should be configured as an
 *                   // input or output. The valid values are: 'in', 'out',
 *                   // 'high', and 'low'. 'high' and 'low' are variants of
 *                   // 'out' that configure the GPIO as an output with an
 *                   // initial level of high or low respectively.
 * [edge: string]    // The interrupt generating edge for the GPIO. Can be
 *                   // specified for GPIO inputs and outputs. The edge
 *                   // specified determine what watchers watch for. The valid
 *                   // values are: 'none', 'rising', 'falling' or 'both'.
 *                   // The default value is 'none'. [optional]
 * [options: object] // Additional options. [optional]
 *
 * The options argument supports the following:
 * debounceTimeout: number  // Can be used to software debounce a button or
 *                          // switch using a timeout. Specified in
 *                          // milliseconds. The default value is 0.
 */
function Gpio(gpio, direction, edge, options) {
    var valuePath;

    if (typeof edge === 'object' && !options) {
        options = edge;
        edge = undefined;
    }

    options = options || {};

    this.gpio = gpio;
    this.gpioPath = gpioRootPath + 'gpio' + this.gpio + '/';
    this.opts = {};
    this.opts.debounceTimeout = options.debounceTimeout || 0;
    this.readBuffer = new Buffer(16);
    this.listeners = [];

    valuePath = this.gpioPath + 'value';

    if (!fs.existsSync(this.gpioPath)) {
        // The pin hasn't been exported yet so export it.
        fs.writeFileSync(gpioRootPath + 'export', this.gpio);
        fs.writeFileSync(this.gpioPath + 'direction', direction);
        if (edge) {
            fs.writeFileSync(this.gpioPath + 'edge', edge);
        }

        // Allow all users to read and write the GPIO value file
        fs.chmodSync(valuePath, 0666);
    } else {
        // The pin has already been exported, perhaps by onoff itself, perhaps
        // by quick2wire gpio-admin on the Pi, perhaps by the WiringPi gpio
        // utility on the Pi, or perhaps by something else. In any case, an
        // attempt is made to set the direction and edge to the requested
        // values here. If quick2wire gpio-admin was used for the export, the
        // user should have access to both direction and edge files. This is
        // important as gpio-admin sets niether direction nor edge. If the
        // WiringPi gpio utility was used, the user should have access to edge
        // file, but not the direction file. This is also ok as the WiringPi
        // gpio utility can set both direction and edge. If there are any
        // errors while attempting to perform the modifications, just keep on
        // truckin'.
        try {
            fs.writeFileSync(this.gpioPath + 'direction', direction);
        } catch (e) {
        }
        try {
            if (edge) {
                fs.writeFileSync(this.gpioPath + 'edge', edge);
            }
        } catch (e) {
        }
    }

    this.valueFd = fs.openSync(valuePath, 'r+'); // Cache fd for performance.

    // Read current value before polling to prevent unauthentic interrupts.
    this.readSync();

    this.poller = new Epoll(pollerEventHandler.bind(this));
}
exports.Gpio = Gpio;

/**
 * Read GPIO value asynchronously.
 *
 * [callback: (err: error, value: number) => {}] // Optional callback
 */
Gpio.prototype.read = function(callback) {
    fs.read(this.valueFd, this.readBuffer, 0, 1, 0, function(err, bytes, buf) {
        if (typeof callback === 'function') {
            if (err) return callback(err);
            callback(null, buf[0] === one[0] ? 1 : 0);
        }
    });
};

/**
 * Read GPIO value synchronously.
 *
 * Returns - number // 0 or 1
 */
Gpio.prototype.readSync = function() {
    fs.readSync(this.valueFd, this.readBuffer, 0, 1, 0);
    return this.readBuffer[0] === one[0] ? 1 : 0;
};

/**
 * Write GPIO value asynchronously.
 *
 * value: number                  // 0 or 1
 * [callback: (err: error) => {}] // Optional callback
 */
Gpio.prototype.write = function(value, callback) {
    var writeBuffer = value === 1 ? one : zero;
    fs.write(this.valueFd, writeBuffer, 0, writeBuffer.length, 0, callback);
};

/**
 * Write GPIO value synchronously.
 *
 * value: number // 0 or 1
 */
Gpio.prototype.writeSync = function(value) {
    var writeBuffer = value === 1 ? one : zero;
    fs.writeSync(this.valueFd, writeBuffer, 0, writeBuffer.length, 0);
};

/**
 * Watch for hardware interrupts on the GPIO. Inputs and outputs can be
 * watched. The edge argument that was passed to the constructor determines
 * which hardware interrupts are watcher for.
 *
 * Note that the value passed to the callback does not represent the value of
 * the GPIO the instant the interrupt occured, it represents the value of the
 * GPIO the instant the GPIO value file is read which may be several
 * milliseconds after the actual interrupt. By the time the GPIO value is read
 * the value may have changed. There are scenarios where this is likely to
 * occur, for example, with buttons or switches that are not hadrware
 * debounced.
 *
 * callback: (err: error, value: number) => {}
 */
Gpio.prototype.watch = function(callback) {
    var events;

    this.listeners.push(callback);

    if (this.listeners.length === 1) {
        events = Epoll.EPOLLPRI;
        if (this.opts.debounceTimeout > 0) {
            events |= Epoll.EPOLLONESHOT;
        }
        this.poller.add(this.valueFd, events);
    }
};

/**
 * Stop watching for hardware interrupts on the GPIO.
 */
Gpio.prototype.unwatch = function(callback) {
    if (this.listeners.length > 0) {
        if (typeof callback !== 'function') {
            this.listeners = [];
        } else {
            this.listeners = this.listeners.filter(function (listener) {
                return callback !== listener;
            });
        }

        if (this.listeners.length === 0) {
            this.poller.remove(this.valueFd);
        }
    }
};

/**
 * Remove all watchers for the GPIO.
 */
Gpio.prototype.unwatchAll = function() {
    this.unwatch();
};

function pollerEventHandler(err, fd, events) {
    var value = this.readSync(),
        callbacks = this.listeners.slice(0);

    if (this.opts.debounceTimeout > 0) {
        setTimeout(function () {
            if (this.listeners.length > 0) {
                // Read current value before polling to prevent unauthentic interrupts.
                this.readSync();
                this.poller.modify(this.valueFd, Epoll.EPOLLPRI | Epoll.EPOLLONESHOT);
            }
        }.bind(this), this.opts.debounceTimeout);
    }

    callbacks.forEach(function (callback) {
        callback(err, value);
    });
}

/**
 * Read GPIO direction.
 *
 * Returns - string // 'in', or 'out'
 */
Gpio.prototype.direction = function() {
    return fs.readFileSync(this.gpioPath + 'direction').toString().trim();
};

/**
 * Read GPIO interrupt generating edge.
 *
 * Returns - string // 'none', 'rising', 'falling' or 'both'
 */
Gpio.prototype.edge = function() {
    return fs.readFileSync(this.gpioPath + 'edge').toString().trim();
};

/**
 * Get GPIO options.
 *
 * Returns - object // Must not be modified
 */
Gpio.prototype.options = function() {
    return this.opts;
};

/**
 * Reverse the effect of exporting the GPIO to userspace. The Gpio object
 * should not be used after calling this method.
 */
Gpio.prototype.unexport = function(callback) {
    this.unwatchAll();
    fs.closeSync(this.valueFd);
    fs.writeFileSync(gpioRootPath + 'unexport', this.gpio);
};

<commit_msg>Added setDirection functionality<commit_after>var fs = require('fs'),
    Epoll = require('epoll').Epoll,
    gpioRootPath = '/sys/class/gpio/',
    zero = new Buffer('0'),
    one = new Buffer('1');

exports.version = '0.3.0';

/**
 * Constructor. Exports a GPIO to userspace.
 *
 * The constructor is written to function for both superusers and
 * non-superusers. See README.md for more details.
 *
 * gpio: number      // The Linux GPIO identifier; an unsigned integer.
 * direction: string // Specifies whether the GPIO should be configured as an
 *                   // input or output. The valid values are: 'in', 'out',
 *                   // 'high', and 'low'. 'high' and 'low' are variants of
 *                   // 'out' that configure the GPIO as an output with an
 *                   // initial level of high or low respectively.
 * [edge: string]    // The interrupt generating edge for the GPIO. Can be
 *                   // specified for GPIO inputs and outputs. The edge
 *                   // specified determine what watchers watch for. The valid
 *                   // values are: 'none', 'rising', 'falling' or 'both'.
 *                   // The default value is 'none'. [optional]
 * [options: object] // Additional options. [optional]
 *
 * The options argument supports the following:
 * debounceTimeout: number  // Can be used to software debounce a button or
 *                          // switch using a timeout. Specified in
 *                          // milliseconds. The default value is 0.
 */
function Gpio(gpio, direction, edge, options) {
    var valuePath;

    if (typeof edge === 'object' && !options) {
        options = edge;
        edge = undefined;
    }

    options = options || {};

    this.gpio = gpio;
    this.gpioPath = gpioRootPath + 'gpio' + this.gpio + '/';
    this.opts = {};
    this.opts.debounceTimeout = options.debounceTimeout || 0;
    this.readBuffer = new Buffer(16);
    this.listeners = [];

    valuePath = this.gpioPath + 'value';

    if (!fs.existsSync(this.gpioPath)) {
        // The pin hasn't been exported yet so export it.
        fs.writeFileSync(gpioRootPath + 'export', this.gpio);
        fs.writeFileSync(this.gpioPath + 'direction', direction);
        if (edge) {
            fs.writeFileSync(this.gpioPath + 'edge', edge);
        }

        // Allow all users to read and write the GPIO value file
        fs.chmodSync(valuePath, 0666);
    } else {
        // The pin has already been exported, perhaps by onoff itself, perhaps
        // by quick2wire gpio-admin on the Pi, perhaps by the WiringPi gpio
        // utility on the Pi, or perhaps by something else. In any case, an
        // attempt is made to set the direction and edge to the requested
        // values here. If quick2wire gpio-admin was used for the export, the
        // user should have access to both direction and edge files. This is
        // important as gpio-admin sets niether direction nor edge. If the
        // WiringPi gpio utility was used, the user should have access to edge
        // file, but not the direction file. This is also ok as the WiringPi
        // gpio utility can set both direction and edge. If there are any
        // errors while attempting to perform the modifications, just keep on
        // truckin'.
        try {
            fs.writeFileSync(this.gpioPath + 'direction', direction);
        } catch (e) {
        }
        try {
            if (edge) {
                fs.writeFileSync(this.gpioPath + 'edge', edge);
            }
        } catch (e) {
        }
    }

    this.valueFd = fs.openSync(valuePath, 'r+'); // Cache fd for performance.

    // Read current value before polling to prevent unauthentic interrupts.
    this.readSync();

    this.poller = new Epoll(pollerEventHandler.bind(this));
}
exports.Gpio = Gpio;

/**
 * Read GPIO value asynchronously.
 *
 * [callback: (err: error, value: number) => {}] // Optional callback
 */
Gpio.prototype.read = function(callback) {
    fs.read(this.valueFd, this.readBuffer, 0, 1, 0, function(err, bytes, buf) {
        if (typeof callback === 'function') {
            if (err) return callback(err);
            callback(null, buf[0] === one[0] ? 1 : 0);
        }
    });
};

/**
 * Read GPIO value synchronously.
 *
 * Returns - number // 0 or 1
 */
Gpio.prototype.readSync = function() {
    fs.readSync(this.valueFd, this.readBuffer, 0, 1, 0);
    return this.readBuffer[0] === one[0] ? 1 : 0;
};

/**
 * Write GPIO value asynchronously.
 *
 * value: number                  // 0 or 1
 * [callback: (err: error) => {}] // Optional callback
 */
Gpio.prototype.write = function(value, callback) {
    var writeBuffer = value === 1 ? one : zero;
    fs.write(this.valueFd, writeBuffer, 0, writeBuffer.length, 0, callback);
};

/**
 * Write GPIO value synchronously.
 *
 * value: number // 0 or 1
 */
Gpio.prototype.writeSync = function(value) {
    var writeBuffer = value === 1 ? one : zero;
    fs.writeSync(this.valueFd, writeBuffer, 0, writeBuffer.length, 0);
};

/**
 * Watch for hardware interrupts on the GPIO. Inputs and outputs can be
 * watched. The edge argument that was passed to the constructor determines
 * which hardware interrupts are watcher for.
 *
 * Note that the value passed to the callback does not represent the value of
 * the GPIO the instant the interrupt occured, it represents the value of the
 * GPIO the instant the GPIO value file is read which may be several
 * milliseconds after the actual interrupt. By the time the GPIO value is read
 * the value may have changed. There are scenarios where this is likely to
 * occur, for example, with buttons or switches that are not hadrware
 * debounced.
 *
 * callback: (err: error, value: number) => {}
 */
Gpio.prototype.watch = function(callback) {
    var events;

    this.listeners.push(callback);

    if (this.listeners.length === 1) {
        events = Epoll.EPOLLPRI;
        if (this.opts.debounceTimeout > 0) {
            events |= Epoll.EPOLLONESHOT;
        }
        this.poller.add(this.valueFd, events);
    }
};

/**
 * Stop watching for hardware interrupts on the GPIO.
 */
Gpio.prototype.unwatch = function(callback) {
    if (this.listeners.length > 0) {
        if (typeof callback !== 'function') {
            this.listeners = [];
        } else {
            this.listeners = this.listeners.filter(function (listener) {
                return callback !== listener;
            });
        }

        if (this.listeners.length === 0) {
            this.poller.remove(this.valueFd);
        }
    }
};

/**
 * Remove all watchers for the GPIO.
 */
Gpio.prototype.unwatchAll = function() {
    this.unwatch();
};

function pollerEventHandler(err, fd, events) {
    var value = this.readSync(),
        callbacks = this.listeners.slice(0);

    if (this.opts.debounceTimeout > 0) {
        setTimeout(function () {
            if (this.listeners.length > 0) {
                // Read current value before polling to prevent unauthentic interrupts.
                this.readSync();
                this.poller.modify(this.valueFd, Epoll.EPOLLPRI | Epoll.EPOLLONESHOT);
            }
        }.bind(this), this.opts.debounceTimeout);
    }

    callbacks.forEach(function (callback) {
        callback(err, value);
    });
}

/**
 * Read GPIO direction.
 *
 * Returns - string // 'in', or 'out'
 */
Gpio.prototype.direction = function() {
    return fs.readFileSync(this.gpioPath + 'direction').toString().trim();
};

/**
 * Writes GPIO direction.
 *
 * value: direction // 'in' or 'out
 */
Gpio.prototype.setDirection = function(direction) {
    fs.writeFileSync(this.gpioPath + 'direction', direction);
};

/**
 * Read GPIO interrupt generating edge.
 *
 * Returns - string // 'none', 'rising', 'falling' or 'both'
 */
Gpio.prototype.edge = function() {
    return fs.readFileSync(this.gpioPath + 'edge').toString().trim();
};

/**
 * Get GPIO options.
 *
 * Returns - object // Must not be modified
 */
Gpio.prototype.options = function() {
    return this.opts;
};

/**
 * Reverse the effect of exporting the GPIO to userspace. The Gpio object
 * should not be used after calling this method.
 */
Gpio.prototype.unexport = function(callback) {
    this.unwatchAll();
    fs.closeSync(this.valueFd);
    fs.writeFileSync(gpioRootPath + 'unexport', this.gpio);
};

<|endoftext|>


<commit_before>angular.module('schemaForm').config(
    ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider',
        function (schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {

            var select = function (name, schema, options) {
                if ((schema.type === 'string') && ("enum" in schema)) {
                    var f = schemaFormProvider.stdFormObj(name, schema, options);
                    f.key = options.path;
                    f.type = 'strapselect';
                    options.lookup[sfPathProvider.stringify(options.path)] = f;
                    return f;
                }
            };

            schemaFormProvider.defaults.string.unshift(select);

            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselect',
                'directives/decorators/bootstrap/strap/strapselect.html')
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselect',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselectdynamic',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselectdynamic',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            // UI SELECT
            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselect',
                'directives/decorators/bootstrap/uiselect/uiselect.html')

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselectmultiple',
                'directives/decorators/bootstrap/uiselect/uiselectmultiple.html')

        }])
    .directive("toggleSingleModel", function () {
        // some how we get this to work ...
        return {
            require: 'ngModel',
            restrict: "A",
            scope: {},
            replace: true,
            controller: ['$scope', function ($scope) {
                $scope.$parent.$watch('select_model.selected', function () {
                    if ($scope.$parent.select_model.selected != undefined) {
                        $scope.$parent.insideModel = $scope.$parent.select_model.selected.value;
                        $scope.$parent.ngModel.$setViewValue($scope.$parent.select_model.selected.value);
                    }
                });
            }]
        };
    })

    .directive('multipleOn', function () {
        return {
            link: function ($scope, $element, $attrs) {
                $scope.$watch(
                    function () {
                        return $element.attr('multiple-on');
                    },
                    function (newVal) {

                        if (newVal == "true") {
                            var select_scope = angular.element($element).scope().$$childTail;
                            select_scope.$isMultiple = true;
                            select_scope.options.multiple = true;
                            select_scope.$select.$element.addClass('select-multiple');
                        }
                        else {
                            angular.element($element).scope().$$childTail.$isMultiple = false;
                        }
                    }
                );
            }
        };
    })
    .filter('whereMulti', function () {
        return function (items, key, values) {
            var out = [];

            if (angular.isArray(values) && items !== undefined) {
                values.forEach(function (value) {
                    for (var i = 0; i < items.length; i++) {
                        if (value == items[i][key]) {
                            out.push(items[i]);
                            break;
                        }
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    })
    .filter('propsFilter', function () {
        return function (items, props) {
            var out = [];

            if (angular.isArray(items)) {
                items.forEach(function (item) {
                    var itemMatches = false;

                    var keys = Object.keys(props);
                    for (var i = 0; i < keys.length; i++) {
                        var prop = keys[i];
                        if (item.hasOwnProperty(prop)) {
                            //only match if this property is actually in the item to avoid
                            var text = props[prop].toLowerCase();
                            //search for either a space before the text or the textg at the start of the string so that the middle of words are not matched
                            if (item[prop].toString().toLowerCase().indexOf(text) === 0 || ( item[prop].toString()).toLowerCase().indexOf(' ' + text) !== -1) {
                                itemMatches = true;
                                break;
                            }
                        }
                    }

                    if (itemMatches) {
                        out.push(item);
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    });

angular.module('schemaForm').controller('dynamicSelectController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {

    if (!$scope.form.options) {
        $scope.form.options = {};
    }

    $scope.select_model = {};

    console.log("Setting options." + $scope.form.options.toString());
    $scope.form.options.scope = $scope;

    $scope.triggerTitleMap = function () {
        console.log("listener triggered");
        // Ugly workaround to trigger titleMap expression re-evaluation so that the selectFilter it reapplied.
        $scope.form.titleMap.push({"value": "345890u340598u3405u9", "name": "34095u3p4ouij"})
        $timeout(function () {
            $scope.form.titleMap.pop()
        })

    };

    $scope.initFiltering = function (localModel) {
        if ($scope.form.options.filterTriggers) {
            $scope.form.options.filterTriggers.forEach(function (trigger) {
                $scope.$parent.$watch(trigger, $scope.triggerTitleMap)

            });
        }
        // This is set here, as the model value may become unitialized and typeless if validation fails.
        $scope.localModelType = Object.prototype.toString.call(localModel);
        $scope.filteringInitialized = true;
    };

    $scope.finalizeTitleMap = function (form, data, newOptions) {
        // Remap the data

        form.titleMap = [];

        if (newOptions && "map" in newOptions && newOptions.map) {
            var current_row = null,
                final = newOptions.map.nameProperty.length - 1,
                separator = newOptions.map.separatorValue ? newOptions.map.separatorValue : ' - ';
            data.forEach(function (current_row) {
                current_row["value"] = current_row[newOptions.map.valueProperty];
                current_row["name"] = ""; //init the 'name' property
                //check if the value passed is a string or not
                if (typeof newOptions.map.nameProperty != 'string') {
                    //loop through the object/array
                    for (var i in newOptions.map.nameProperty) {
                        current_row["name"] += current_row[newOptions.map.nameProperty[i]];
                        if (i != final) {
                            current_row["name"] += separator
                        };
                    }
                }
                else {
                    //if it is a string
                    current_row["name"] = current_row[newOptions.map.nameProperty];
                }
                form.titleMap.push(current_row);
            });

        }
        else {
            data.forEach(function (item) {
                    if ("text" in item) {
                        item.name = item.text
                    }
                }
            );
            form.titleMap = data;
        }

        if ($scope.insideModel && $scope.select_model.selected === undefined) {
            $scope.select_model.selected = $scope.find_in_titleMap($scope.insideModel);
        }

        // The ui-selects needs to be reinitialized (UI select sets the internalModel and externalModel.
        if ($scope.internalModel) {
            console.log("Call uiMultiSelectInitInternalModel");
            $scope.uiMultiSelectInitInternalModel($scope.externalModel);
        }
    };

    $scope.clone = function (obj) {
        // Clone an object (except references to this scope)
        if (null == obj || "object" != typeof(obj)) return obj;

        var copy = obj.constructor();
        for (var attr in obj) {
            // Do not clone if it is this scope
            if (obj[attr] != $scope) {
                if (obj.hasOwnProperty(attr)) copy[attr] = $scope.clone(obj[attr]);
            }
        }
        return copy;
    };

    $scope.getCallback = function (callback) {
        if (typeof(callback) == "string") {
            var _result = $scope.$parent.evalExpr(callback);
            if (typeof(_result) == "function") {
                return _result;
            }
            else {
                throw("A callback string must match name of a function in the parent scope")
            }

        }
        else if (typeof(callback) == "function") {
            return callback;
        }
        else {
            throw("A callback must either be a string matching the name of a function in the parent scope or a " +
            "direct function reference")

        }
    };

    $scope.getOptions = function (options, search) {
        // If defined, let the a callback function manipulate the options
        if (options.httpPost && options.httpPost.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpPost.optionsCallback)(newOptionInstance, search);
        }
        if (options.httpGet && options.httpGet.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpGet.optionsCallback)(newOptionInstance, search);
        }
        else {
            return options;
        }
    };

    $scope.test = function (form) {
        form.titleMap.pop();
    };

    $scope.populateTitleMap = function (form, search) {

        if (form.schema && "enum" in form.schema) {
            form.titleMap = [];
            form.schema.enum.forEach(function (item) {
                    form.titleMap.push({"value": item, "name": item})
                }
            );

        }
        else if (!form.options) {

            console.log("dynamicSelectController.populateTitleMap(key:" + form.key + ") : No options set, needed for dynamic selects");
        }
        else if (form.options.callback) {
            form.titleMap = $scope.getCallback(form.options.callback)(form.options, search);
            $scope.finalizeTitleMap(form, form.titleMap, form.options);
            console.log("callback items: ", form.titleMap);
        }
        else if (form.options.asyncCallback) {
            return $scope.getCallback(form.options.asyncCallback)(form.options, search).then(
                function (_data) {
                    $scope.finalizeTitleMap(form, _data.data, form.options);
                    console.log('asyncCallback items', form.titleMap);
                },
                function (data, status) {
                    if (form.options.onPopulationError) {
                        $scope.getCallback(form.options.onPopulationError)(form, data, status);
                    }
                    else {
                        alert("Loading select items failed(Key(field): " + form.key + "Options: '" + String(form.options) +
                            "\nError: " + status);
                    }

                });
        }
        else if (form.options.httpPost) {
            var finalOptions = $scope.getOptions(form.options, search);

            return $http.post(finalOptions.httpPost.url, finalOptions.httpPost.parameter).then(
                function (_data) {

                    $scope.finalizeTitleMap(form, _data.data, finalOptions);
                    console.log('httpPost items', form.titleMap);
                },
                function (data, status) {
                    if (finalOptions.onPopulationError) {
                        $scope.getCallback(finalOptions.onPopulationError)(form, data, status, finalOptions);
                    }
                    else {
                        alert("Loading select items failed (Key(field): " + form.key + " URL: '" + String(finalOptions.httpPost.url) +
                            "' Parameter: " + String(finalOptions.httpPost.parameter) + "\nStatus: " + status);
                    }

                });
        }
        else if (form.options.httpGet) {
            var finalOptions = $scope.getOptions(form.options, search);
            return $http.get(finalOptions.httpGet.url, finalOptions.httpGet.parameter).then(
                function (data) {
                    $scope.finalizeTitleMap(form, data.data, finalOptions);
                    console.log('httpGet items', form.titleMap);
                },
                function (data, status) {
                    if (finalOptions.onPopulationError) {
                        $scope.getCallback(finalOptions.onPopulationError)(form, data, status, finalOptions);
                    }
                    else {
                        alert("Loading select items failed (Key(field): " + form.key + " URL: '" + String(finalOptions.httpGet.url) +
                            "\nStatus: " + status);
                    }
                });
        }
    };

    $scope.find_in_titleMap = function (value) {
        for (i = 0; i < $scope.form.titleMap.length; i++) {
            if ($scope.form.titleMap[i].value == value) {
                return {"value": $scope.form.titleMap[i].value, "name": $scope.form.titleMap[i].name}
            }
        }

    };

    $scope.uiMultiSelectInitInternalModel = function (supplied_model) {

        console.log("$scope.externalModel: Key: " + $scope.form.key.toString() + " Model: " + supplied_model.toString());
        $scope.externalModel = supplied_model;
        $scope.internalModel = [];
        if ($scope.form.titleMap) {
            if (supplied_model !== undefined && angular.isArray(supplied_model)) {
                supplied_model.forEach(function (value) {
                        $scope.internalModel.push($scope.find_in_titleMap(value));
                    }
                )
            }
        }
    };

}]);

angular.module('schemaForm').filter('selectFilter', [function ($filter) {
    return function (inputArray, controller, localModel, strLocalModel) {
        // As the controllers' .model is the global and its form is the local, we need to get the local model as well.
        // We also need tp be able to set it if is undefined after a validation failure,so for that we need
        // its string representation as well as we do not know its name. A typical value if strLocalModel is model['groups']
        // This is very ugly, though. TODO: Find out why the model is set to undefined after validation failure.

        if (!angular.isDefined(inputArray) || !angular.isDefined(controller.form.options) || !angular.isDefined(controller.form.options.filter) || controller.form.options.filter == '') {
            return inputArray;
        }

        console.log("----- In filtering for " + controller.form.key + "(" + controller.form.title + "), model value: " + JSON.stringify(localModel) + "----");
        console.log("Filter:" + controller.form.options.filter);
        if (!controller.filteringInitialized) {
            console.log("Initialize filter");
            controller.initFiltering(localModel);
        }

        var data = [];

        angular.forEach(inputArray, function (curr_item) {
            //console.log("Compare: curr_item: " + JSON.stringify(curr_item) +
            //"with : " + JSON.stringify( controller.$eval(controller.form.options.filterTriggers[0])));
            if (controller.$eval(controller.form.options.filter, {item: curr_item})) {
                data.push(curr_item);
            }
            else if (localModel) {
                // If not in list, also remove the set value

                if (controller.localModelType == "[object Array]" && localModel.indexOf(curr_item.value) > -1) {
                    localModel.splice(localModel.indexOf(curr_item.value), 1);
                }
                else if (localModel == curr_item.value) {
                    console.log("Setting model of type " + controller.localModelType + "to null.");
                    localModel = null;
                }
            }
        });

        if (controller.localModelType == "[object Array]" && !localModel) {
            // An undefined local model seems to mess up bootstrap select's indicators
            console.log("Resetting model of type " + controller.localModelType + " to [].");

            controller.$eval(strLocalModel + "=[]");
        }

        //console.log("Input: " + JSON.stringify(inputArray));
        //console.log("Output: " + JSON.stringify(data));
        //console.log("Model value out : " + JSON.stringify(localModel));
        console.log("----- Exiting filter for " + controller.form.title + "-----");

        return data;
    };
}]);
<commit_msg>Fix for objects with nameProperty == "name"<commit_after>angular.module('schemaForm').config(
    ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider',
        function (schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {

            var select = function (name, schema, options) {
                if ((schema.type === 'string') && ("enum" in schema)) {
                    var f = schemaFormProvider.stdFormObj(name, schema, options);
                    f.key = options.path;
                    f.type = 'strapselect';
                    options.lookup[sfPathProvider.stringify(options.path)] = f;
                    return f;
                }
            };

            schemaFormProvider.defaults.string.unshift(select);

            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselect',
                'directives/decorators/bootstrap/strap/strapselect.html')
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselect',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapselectdynamic',
                'directives/decorators/bootstrap/strap/strapselect.html');

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'strapmultiselectdynamic',
                'directives/decorators/bootstrap/strap/strapmultiselect.html');

            // UI SELECT
            //Add to the bootstrap directive
            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselect',
                'directives/decorators/bootstrap/uiselect/uiselect.html')

            schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'uiselectmultiple',
                'directives/decorators/bootstrap/uiselect/uiselectmultiple.html')

        }])
    .directive("toggleSingleModel", function () {
        // some how we get this to work ...
        return {
            require: 'ngModel',
            restrict: "A",
            scope: {},
            replace: true,
            controller: ['$scope', function ($scope) {
                $scope.$parent.$watch('select_model.selected', function () {
                    if ($scope.$parent.select_model.selected != undefined) {
                        $scope.$parent.insideModel = $scope.$parent.select_model.selected.value;
                        $scope.$parent.ngModel.$setViewValue($scope.$parent.select_model.selected.value);
                    }
                });
            }]
        };
    })

    .directive('multipleOn', function () {
        return {
            link: function ($scope, $element, $attrs) {
                $scope.$watch(
                    function () {
                        return $element.attr('multiple-on');
                    },
                    function (newVal) {

                        if (newVal == "true") {
                            var select_scope = angular.element($element).scope().$$childTail;
                            select_scope.$isMultiple = true;
                            select_scope.options.multiple = true;
                            select_scope.$select.$element.addClass('select-multiple');
                        }
                        else {
                            angular.element($element).scope().$$childTail.$isMultiple = false;
                        }
                    }
                );
            }
        };
    })
    .filter('whereMulti', function () {
        return function (items, key, values) {
            var out = [];

            if (angular.isArray(values) && items !== undefined) {
                values.forEach(function (value) {
                    for (var i = 0; i < items.length; i++) {
                        if (value == items[i][key]) {
                            out.push(items[i]);
                            break;
                        }
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    })
    .filter('propsFilter', function () {
        return function (items, props) {
            var out = [];

            if (angular.isArray(items)) {
                items.forEach(function (item) {
                    var itemMatches = false;

                    var keys = Object.keys(props);
                    for (var i = 0; i < keys.length; i++) {
                        var prop = keys[i];
                        if (item.hasOwnProperty(prop)) {
                            //only match if this property is actually in the item to avoid
                            var text = props[prop].toLowerCase();
                            //search for either a space before the text or the textg at the start of the string so that the middle of words are not matched
                            if (item[prop].toString().toLowerCase().indexOf(text) === 0 || ( item[prop].toString()).toLowerCase().indexOf(' ' + text) !== -1) {
                                itemMatches = true;
                                break;
                            }
                        }
                    }

                    if (itemMatches) {
                        out.push(item);
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    });

angular.module('schemaForm').controller('dynamicSelectController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {

    if (!$scope.form.options) {
        $scope.form.options = {};
    }

    $scope.select_model = {};

    console.log("Setting options." + $scope.form.options.toString());
    $scope.form.options.scope = $scope;

    $scope.triggerTitleMap = function () {
        console.log("listener triggered");
        // Ugly workaround to trigger titleMap expression re-evaluation so that the selectFilter it reapplied.
        $scope.form.titleMap.push({"value": "345890u340598u3405u9", "name": "34095u3p4ouij"})
        $timeout(function () {
            $scope.form.titleMap.pop()
        })

    };

    $scope.initFiltering = function (localModel) {
        if ($scope.form.options.filterTriggers) {
            $scope.form.options.filterTriggers.forEach(function (trigger) {
                $scope.$parent.$watch(trigger, $scope.triggerTitleMap)

            });
        }
        // This is set here, as the model value may become unitialized and typeless if validation fails.
        $scope.localModelType = Object.prototype.toString.call(localModel);
        $scope.filteringInitialized = true;
    };

    $scope.finalizeTitleMap = function (form, data, newOptions) {
        // Remap the data

        form.titleMap = [];

        if (newOptions && "map" in newOptions && newOptions.map) {
            var current_row = null,
                final = newOptions.map.nameProperty.length - 1,
                separator = newOptions.map.separatorValue ? newOptions.map.separatorValue : ' - ';
            data.forEach(function (current_row) {
                current_row["value"] = current_row[newOptions.map.valueProperty];
                if (newOptions.map.nameProperty !== "name") {
                    current_row["name"] = ""; //init the 'name' property
                    //check if the value passed is a string or not
                    if (typeof newOptions.map.nameProperty != 'string') {
                        //loop through the object/array
                        for (var i in newOptions.map.nameProperty) {
                            current_row["name"] += current_row[newOptions.map.nameProperty[i]];
                            if (i != final) {
                                current_row["name"] += separator
                            };
                        }
                    }
                    else {
                        //if it is a string
                        current_row["name"] = current_row[newOptions.map.nameProperty];
                    }
                }
                form.titleMap.push(current_row);
            });

        }
        else {
            data.forEach(function (item) {
                    if ("text" in item) {
                        item.name = item.text
                    }
                }
            );
            form.titleMap = data;
        }

        if ($scope.insideModel && $scope.select_model.selected === undefined) {
            $scope.select_model.selected = $scope.find_in_titleMap($scope.insideModel);
        }

        // The ui-selects needs to be reinitialized (UI select sets the internalModel and externalModel.
        if ($scope.internalModel) {
            console.log("Call uiMultiSelectInitInternalModel");
            $scope.uiMultiSelectInitInternalModel($scope.externalModel);
        }
    };

    $scope.clone = function (obj) {
        // Clone an object (except references to this scope)
        if (null == obj || "object" != typeof(obj)) return obj;

        var copy = obj.constructor();
        for (var attr in obj) {
            // Do not clone if it is this scope
            if (obj[attr] != $scope) {
                if (obj.hasOwnProperty(attr)) copy[attr] = $scope.clone(obj[attr]);
            }
        }
        return copy;
    };

    $scope.getCallback = function (callback) {
        if (typeof(callback) == "string") {
            var _result = $scope.$parent.evalExpr(callback);
            if (typeof(_result) == "function") {
                return _result;
            }
            else {
                throw("A callback string must match name of a function in the parent scope")
            }

        }
        else if (typeof(callback) == "function") {
            return callback;
        }
        else {
            throw("A callback must either be a string matching the name of a function in the parent scope or a " +
            "direct function reference")

        }
    };

    $scope.getOptions = function (options, search) {
        // If defined, let the a callback function manipulate the options
        if (options.httpPost && options.httpPost.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpPost.optionsCallback)(newOptionInstance, search);
        }
        if (options.httpGet && options.httpGet.optionsCallback) {
            newOptionInstance = $scope.clone(options);
            return $scope.getCallback(options.httpGet.optionsCallback)(newOptionInstance, search);
        }
        else {
            return options;
        }
    };

    $scope.test = function (form) {
        form.titleMap.pop();
    };

    $scope.populateTitleMap = function (form, search) {

        if (form.schema && "enum" in form.schema) {
            form.titleMap = [];
            form.schema.enum.forEach(function (item) {
                    form.titleMap.push({"value": item, "name": item})
                }
            );

        }
        else if (!form.options) {

            console.log("dynamicSelectController.populateTitleMap(key:" + form.key + ") : No options set, needed for dynamic selects");
        }
        else if (form.options.callback) {
            form.titleMap = $scope.getCallback(form.options.callback)(form.options, search);
            $scope.finalizeTitleMap(form, form.titleMap, form.options);
            console.log("callback items: ", form.titleMap);
        }
        else if (form.options.asyncCallback) {
            return $scope.getCallback(form.options.asyncCallback)(form.options, search).then(
                function (_data) {
                    $scope.finalizeTitleMap(form, _data.data, form.options);
                    console.log('asyncCallback items', form.titleMap);
                },
                function (data, status) {
                    if (form.options.onPopulationError) {
                        $scope.getCallback(form.options.onPopulationError)(form, data, status);
                    }
                    else {
                        alert("Loading select items failed(Key(field): " + form.key + "Options: '" + String(form.options) +
                            "\nError: " + status);
                    }

                });
        }
        else if (form.options.httpPost) {
            var finalOptions = $scope.getOptions(form.options, search);

            return $http.post(finalOptions.httpPost.url, finalOptions.httpPost.parameter).then(
                function (_data) {

                    $scope.finalizeTitleMap(form, _data.data, finalOptions);
                    console.log('httpPost items', form.titleMap);
                },
                function (data, status) {
                    if (finalOptions.onPopulationError) {
                        $scope.getCallback(finalOptions.onPopulationError)(form, data, status, finalOptions);
                    }
                    else {
                        alert("Loading select items failed (Key(field): " + form.key + " URL: '" + String(finalOptions.httpPost.url) +
                            "' Parameter: " + String(finalOptions.httpPost.parameter) + "\nStatus: " + status);
                    }

                });
        }
        else if (form.options.httpGet) {
            var finalOptions = $scope.getOptions(form.options, search);
            return $http.get(finalOptions.httpGet.url, finalOptions.httpGet.parameter).then(
                function (data) {
                    $scope.finalizeTitleMap(form, data.data, finalOptions);
                    console.log('httpGet items', form.titleMap);
                },
                function (data, status) {
                    if (finalOptions.onPopulationError) {
                        $scope.getCallback(finalOptions.onPopulationError)(form, data, status, finalOptions);
                    }
                    else {
                        alert("Loading select items failed (Key(field): " + form.key + " URL: '" + String(finalOptions.httpGet.url) +
                            "\nStatus: " + status);
                    }
                });
        }
    };

    $scope.find_in_titleMap = function (value) {
        for (i = 0; i < $scope.form.titleMap.length; i++) {
            if ($scope.form.titleMap[i].value == value) {
                return {"value": $scope.form.titleMap[i].value, "name": $scope.form.titleMap[i].name}
            }
        }

    };

    $scope.uiMultiSelectInitInternalModel = function (supplied_model) {

        console.log("$scope.externalModel: Key: " + $scope.form.key.toString() + " Model: " + supplied_model.toString());
        $scope.externalModel = supplied_model;
        $scope.internalModel = [];
        if ($scope.form.titleMap) {
            if (supplied_model !== undefined && angular.isArray(supplied_model)) {
                supplied_model.forEach(function (value) {
                        $scope.internalModel.push($scope.find_in_titleMap(value));
                    }
                )
            }
        }
    };

}]);

angular.module('schemaForm').filter('selectFilter', [function ($filter) {
    return function (inputArray, controller, localModel, strLocalModel) {
        // As the controllers' .model is the global and its form is the local, we need to get the local model as well.
        // We also need tp be able to set it if is undefined after a validation failure,so for that we need
        // its string representation as well as we do not know its name. A typical value if strLocalModel is model['groups']
        // This is very ugly, though. TODO: Find out why the model is set to undefined after validation failure.

        if (!angular.isDefined(inputArray) || !angular.isDefined(controller.form.options) || !angular.isDefined(controller.form.options.filter) || controller.form.options.filter == '') {
            return inputArray;
        }

        console.log("----- In filtering for " + controller.form.key + "(" + controller.form.title + "), model value: " + JSON.stringify(localModel) + "----");
        console.log("Filter:" + controller.form.options.filter);
        if (!controller.filteringInitialized) {
            console.log("Initialize filter");
            controller.initFiltering(localModel);
        }

        var data = [];

        angular.forEach(inputArray, function (curr_item) {
            //console.log("Compare: curr_item: " + JSON.stringify(curr_item) +
            //"with : " + JSON.stringify( controller.$eval(controller.form.options.filterTriggers[0])));
            if (controller.$eval(controller.form.options.filter, {item: curr_item})) {
                data.push(curr_item);
            }
            else if (localModel) {
                // If not in list, also remove the set value

                if (controller.localModelType == "[object Array]" && localModel.indexOf(curr_item.value) > -1) {
                    localModel.splice(localModel.indexOf(curr_item.value), 1);
                }
                else if (localModel == curr_item.value) {
                    console.log("Setting model of type " + controller.localModelType + "to null.");
                    localModel = null;
                }
            }
        });

        if (controller.localModelType == "[object Array]" && !localModel) {
            // An undefined local model seems to mess up bootstrap select's indicators
            console.log("Resetting model of type " + controller.localModelType + " to [].");

            controller.$eval(strLocalModel + "=[]");
        }

        //console.log("Input: " + JSON.stringify(inputArray));
        //console.log("Output: " + JSON.stringify(data));
        //console.log("Model value out : " + JSON.stringify(localModel));
        console.log("----- Exiting filter for " + controller.form.title + "-----");

        return data;
    };
}]);
<|endoftext|>


<commit_before>/**
 * A createStore function with middleware and other store enhancers applied
 * @module createStore
 */
import { applyMiddleware, createStore, compose } from 'redux';
import getPlatformMiddleware from '../middleware/epic';
import { fetchQueries, makeCookieHeader } from '../util/fetchUtils';

const noopMiddleware = store => next => action => next(action);

/**
 * The platform has a specific set of middleware that must be applied to the
 * store in order for it to work. This store enhancer consumes a few app-
 * specific configuration options to set up the middleware enhancer correctly
 *
 * @param {Object} routes the React Router routes object
 * @param {Array} middleware additional middleware to inject into store
 * @param {Function} fetchQueriesFn a function that accepts queries and returns a Promise
 *   that resolves with API results
 * @return {Function} A Redux store enhancer function
 */
export function getPlatformMiddlewareEnhancer(routes, middleware, fetchQueriesFn) {
	// **All** middleware gets added here
	const middlewareToApply = [
		getPlatformMiddleware(routes, fetchQueriesFn),
		typeof window !== 'undefined' && window.mupDevTools ? window.mupDevTools() : noopMiddleware,
		...middleware,
	];
	const appliedMiddleware = applyMiddleware(...middlewareToApply);

	return compose(
		appliedMiddleware,
		typeof window !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : fn => fn
	);
}

/**
 * wrap the `fetchQueries` function with a function that injects cookies into
 * the request
 *
 * @param {Object} cookieState { name: value } object of cookies to inject
 * @return {Function} a fetchQueries function
 */
const serverFetchQueries = request => (api, options) => {
	const cookie = makeCookieHeader(request.state);
	options.headers = options.headers || {};
	options.headers.cookie = options.headers.cookie ?
		`${options.headers.cookie}; ${cookie}` :
		cookie;
	options.headers.referer = options.headers.referer || request.url.pathname;
	return fetchQueries(api, options);
};

/**
 * the server needs a slightly different store than the browser because the
 * server does not know which cookies to pass along to the `fetch` function
 * inside the sync middleware.
 *
 * This getServerCreateStore function will therefore return a store creator that
 * is specifically tailored to making API requests that correspond to the
 * incoming request from the browser
 *
 * @param {Object} routes the React Router routes object
 * @param {Array} middleware additional middleware to inject into store
 * @param {Object} request the Hapi request for this store
 */
export function getServerCreateStore(
	routes,
	middleware,
	request
) {
	const middlewareEnhancer = getPlatformMiddlewareEnhancer(
		routes,
		middleware,
		serverFetchQueries(request)
	);
	return middlewareEnhancer(createStore);
}

export function getBrowserCreateStore(
	routes,
	middleware
) {
	const middlewareEnhancer = getPlatformMiddlewareEnhancer(
		routes,
		middleware,
		fetchQueries
	);
	return middlewareEnhancer(createStore);
}

<commit_msg>add clickTrackEnhancer<commit_after>/**
 * A createStore function with middleware and other store enhancers applied
 * @module createStore
 */
import { applyMiddleware, createStore, compose } from 'redux';
import getPlatformMiddleware from '../middleware/epic';
import { fetchQueries, makeCookieHeader } from '../fetchUtils';
import getClickTracker from '../clickTracking';

const noopMiddleware = store => next => action => next(action);

export const clickTrackEnhancer = createStore => (reducer, initialState, enhancer) => {
	const store = createStore(reducer, initialState, enhancer);
	const clickTracker = getClickTracker(store);
	document.body.addEventListenter('click', clickTracker);
	document.body.addEventListenter('change', clickTracker);

	return store;
};

/**
 * The platform has a specific set of middleware that must be applied to the
 * store in order for it to work. This store enhancer consumes a few app-
 * specific configuration options to set up the middleware enhancer correctly
 *
 * @param {Object} routes the React Router routes object
 * @param {Array} middleware additional middleware to inject into store
 * @param {Function} fetchQueriesFn a function that accepts queries and returns a Promise
 *   that resolves with API results
 * @return {Function} A Redux store enhancer function
 */
export function getPlatformMiddlewareEnhancer(routes, middleware, fetchQueriesFn) {
	// **All** middleware gets added here
	const middlewareToApply = [
		getPlatformMiddleware(routes, fetchQueriesFn),
		typeof window !== 'undefined' && window.mupDevTools ? window.mupDevTools() : noopMiddleware,
		...middleware,
	];
	const appliedMiddleware = applyMiddleware(...middlewareToApply);

	return compose(
		appliedMiddleware,
		typeof window !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : fn => fn
	);
}

/**
 * wrap the `fetchQueries` function with a function that injects cookies into
 * the request
 *
 * @param {Object} cookieState { name: value } object of cookies to inject
 * @return {Function} a fetchQueries function
 */
const serverFetchQueries = request => (api, options) => {
	const cookie = makeCookieHeader(request.state);
	options.headers = options.headers || {};
	options.headers.cookie = options.headers.cookie ?
		`${options.headers.cookie}; ${cookie}` :
		cookie;
	options.headers.referer = options.headers.referer || request.url.pathname;
	return fetchQueries(api, options);
};

/**
 * the server needs a slightly different store than the browser because the
 * server does not know which cookies to pass along to the `fetch` function
 * inside the sync middleware.
 *
 * This getServerCreateStore function will therefore return a store creator that
 * is specifically tailored to making API requests that correspond to the
 * incoming request from the browser
 *
 * @param {Object} routes the React Router routes object
 * @param {Array} middleware additional middleware to inject into store
 * @param {Object} request the Hapi request for this store
 */
export function getServerCreateStore(
	routes,
	middleware,
	request
) {
	const middlewareEnhancer = getPlatformMiddlewareEnhancer(
		routes,
		middleware,
		serverFetchQueries(request)
	);
	return middlewareEnhancer(createStore);
}

export function getBrowserCreateStore(
	routes,
	middleware
) {
	const middlewareEnhancer = getPlatformMiddlewareEnhancer(
		routes,
		middleware,
		fetchQueries
	);
	const enhancer = compose(
		middlewareEnhancer,
		clickTrackEnhancer
	);
	return enhancer(createStore);
}

<|endoftext|>