From b72261cd8fae5d522308a53bc09bdfb6e18a4629 Mon Sep 17 00:00:00 2001
From: tomsmeding <tom.smeding@gmail.com>
Date: Tue, 8 Nov 2016 11:00:51 +0100
Subject: changes: Add e-mail notifications

---
 modules/changes/changes.js | 149 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 136 insertions(+), 13 deletions(-)

(limited to 'modules/changes')

diff --git a/modules/changes/changes.js b/modules/changes/changes.js
index 04b7f6e..67e4e43 100644
--- a/modules/changes/changes.js
+++ b/modules/changes/changes.js
@@ -5,7 +5,8 @@ var cmn=require("../$common.js"),
     crypto=require("crypto"),
     http=require("http"),
     https=require("https"),
-    URL=require("url");
+    URL=require("url"),
+    child_process=require("child_process");
 
 var moddir=null;
 
@@ -20,19 +21,53 @@ persist.initSync();
 //url: String
 //URLobject: {url, timeline: [[Date, hash, contents]]}
 var urls=persist.getItemSync("urls");
-(function(){
-	if(!urls){
-		urls={};
-		persist.setItemSync("urls",urls);
-	} else {
+if(!urls){
+	urls={};
+	persist.setItemSync("urls",urls);
+} else {
+	(function(){
 		var url,i;
 		for(url in urls){
 			for(i=0;i<urls[url].timeline.length;i++){
 				urls[url].timeline[i][0]=new Date(urls[url].timeline[i][0]);
 			}
 		}
+	})();
+}
+//gsettings: {notify: null/{to: "email", interval: ms}}
+var gsettings=persist.getItemSync("gsettings");
+if(!gsettings){
+	gsettings={notify: null};
+	persist.setItemSync("gsettings",gsettings);
+}
+
+if(gsettings.notify){
+	scheduleNotify();
+}
+
+var notifyIsScheduled=false;
+function scheduleNotify(){
+	if(notifyIsScheduled){
+		console.log("WARNING: requesting notify schedule when already scheduled");
+		return;
 	}
-})();
+	notifyIsScheduled=true;
+	var timeoutFunc=function(){
+		if(!gsettings.notify){
+			notifyIsScheduled=false;
+			return;
+		}
+		performNotify(gsettings.notify,function(){
+			performCleanup();
+			if(gsettings.notify){
+				setTimeout(timeoutFunc,gsettings.notify.interval);
+			} else {
+				notifyIsScheduled=false;
+			}
+		});
+	};
+	setTimeout(timeoutFunc,gsettings.notify.interval);
+}
 
 
 function URLobject(url){
@@ -92,13 +127,16 @@ function fetch(method,url,data/*?*/,cb){
 
 var refresh_persist_timeout=null;
 
-function refreshURLs(){
+function refreshURLs(cb){
 	var hashes={};
 	var i;
 	var url;
+	var nwaiting=0;
 	for(url in urls){
 		console.log("Fetching <"+url+">");
+		nwaiting++;
 		fetch("GET",url,function(url,status,body){
+			nwaiting--;
 			console.log("Got <"+url+">; status = "+status);
 			if(status==-1){
 				hashes[url]=[new Date(),null,null];
@@ -110,12 +148,14 @@ function refreshURLs(){
 
 			if(!urls[url]){
 				console.log("WARNING: url <"+url+"> from hashes not found in urls!");
-				return;
+			} else {
+				//var last=urls[url].timeline[urls[url].timeline.length-1];
+				//if(last==undefined||hashes[url][1]==null||hashes[url][1]!=last[1]){
+					urls[url].timeline.push(hashes[url]);
+				//}
 			}
-			//var last=urls[url].timeline[urls[url].timeline.length-1];
-			//if(last==undefined||hashes[url][1]==null||hashes[url][1]!=last[1]){
-				urls[url].timeline.push(hashes[url]);
-			//}
+
+			if(nwaiting==0&&cb)process.nextTick(cb);
 
 			if(refresh_persist_timeout!=null)clearTimeout(refresh_persist_timeout);
 			refresh_persist_timeout=setTimeout(function(){
@@ -127,6 +167,89 @@ function refreshURLs(){
 	}
 }
 
+function collectLastHashes(){
+	var lasthashes={};
+	for(url in urls){
+		if(urls[url].timeline.length==0){
+			lasthashes[url]=null;
+		} else {
+			lasthashes[url]=urls[url].timeline[urls[url].timeline.length-1][1];
+		}
+	}
+	return lasthashes;
+}
+
+function sendMail(tomail,body,cb){
+	var proc=child_process.spawn("sendmail",[tomail],{stdio:["pipe",process.stdout,process.stderr]});
+	proc.stdin.error(function(e){
+		console.log(e);
+		cb(e);
+	});
+	proc.stdin.write(body,function(){
+		proc.stdin.end();
+		cb();
+	});
+}
+
+function constructMailBody(tomail,diffs){
+	var body="To: "+tomail+"\n"+
+	         "From: changes <changes_module@tomsmeding.com>\n"+
+	         "Subject: changes notification mail\n\n"+
+	         "The <a href=\"http://tomsmeding.com/changes\">changes</a> module recorded "+
+	           "some changes in watched webpages.\n"+
+	         "The URLs and their old and new content hashes are shown below.\n\n";
+	var i;
+	for(i=0;i<diffs.length;i++){
+		body+="- <a href=\""+diffs[i].url+"\">"+diffs[i].url+"</a>\n"+
+		      "  was: "+diffs[i].oldhash+"\n"+
+		      "  now: "+diffs[i].newhash+"\n";
+	}
+	body+="\n\n"+
+	      "- webserver\n";
+	return body;
+}
+
+function performNotify(settings,cb){
+	var oldhashes=collectLastHashes();
+	refreshURLs(function(){
+		var newhashes=collectLastHashes();
+		var diffs=[]; // [{url, oldhash, newhash}]
+		for(url in newhashes){
+			if(newhashes[url]==oldhashes[url])continue;
+			diffs.push({
+				url: url,
+				oldhash: oldhashes[url],
+				newhash: newhashes[url]
+			});
+		}
+		sendMail(settings.to,constructMailBody(settings.to,diffs),cb);
+	});
+}
+
+function performCleanup(){
+	//keep only the last hash and the last different hash intact
+	//(if there is no different hash, delete everything but the last)
+	for(url in urls){
+		if(urls[url].timeline.length<=2)continue;
+		var i,lasthash,prevhash=null,prevhashidx=-1,tllen;
+		tllen=urls[url].timeline.length;
+		lasthash=urls[url].timeline[tllen-1][1];
+		for(i=tllen-2;i>=0;i--){
+			if(urls[url].timeline[i][1]!=lasthash){
+				prevhash=urls[url].timeline[i][1];
+				prevhashidx=i;
+				break;
+			}
+		}
+		if(prevhash==null){
+			urls[url].timeline.splice(0,tllen-1);
+		} else {
+			urls[url].timeline.splice(prevhashidx+1,tllen-prevhashidx-2);
+			urls[url].timeline.splice(0,prevhashidx);
+		}
+	}
+}
+
 
 module.exports=function(app,io,_moddir){
 	moddir=_moddir;
-- 
cgit v1.2.3-70-g09d2