Skip to content

Instantly share code, notes, and snippets.

@mrfabbri
Last active August 29, 2015 14:12
Show Gist options
  • Save mrfabbri/d7b5665527a702632040 to your computer and use it in GitHub Desktop.
Save mrfabbri/d7b5665527a702632040 to your computer and use it in GitHub Desktop.
Noderunner + Dropbox
//The MIT License (MIT)
//Copyright (c) 2014 Dave Winer
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
var myVersion = "0.57", myProductName = "Noderunner";
var fs = require ("fs");
var request = require ("request");
var urlpack = require ("url");
var http = require ("http");
var Dropbox = require ("disk-dropbox");
var folderPathFromEnv = process.env.folderPath; //12/30/14 by DW
var noderunnerPrefs = {
myPort: process.env.PORT || 80,
secondToRunEveryMinuteScripts: 0,
minuteToRunHourlyScripts: 0,
hourToRunOvernightScripts: 0
};
var noderunnerStats = {
ctStarts: 0, whenLastStart: new Date (0),
ctStatsReadErrors: 0, ctStatsReads: 0,
whenLastEverySecond: new Date (), whenLastEveryMinute: new Date (), whenLastEveryHour: new Date (), whenLastOvernight: new Date (),
ctEverySecond: 0, ctLastEveryMinute: 0, ctEveryHour: 0, ctOvernight: 0
};
var localStorage = {
};
var fnameStats = "prefs/stats.json", fnamePrefs = "prefs/prefs.json", fnameLocalStorage = "prefs/localStorage.json";
var userScriptsPath = "scripts/";
var startupScriptsFolderName = "startup";
var everySecondScriptsFolderName = "everySecond";
var everyMinuteScriptsFolderName = "everyMinute";
var everyHourScriptsFolderName = "everyHour";
var overnightScriptsFolderName = "overnight";
var userFilesPath = "files/";
var lastLocalStorageJson;
//routines from utils.js, fs.js
function getBoolean (val) {
switch (typeof (val)) {
case "string":
if (val.toLowerCase () == "true") {
return (true);
}
break;
case "boolean":
return (val);
break;
case "number":
if (val != 0) {
return (true);
}
break;
}
return (false);
}
function jsonStringify (jstruct) {
return (JSON.stringify (jstruct, undefined, 4));
}
function secondsSince (when) {
var now = new Date ();
when = new Date (when);
return ((now - when) / 1000);
}
function beginsWith (s, possibleBeginning, flUnicase) {
if (s.length == 0) { //1/1/14 by DW
return (false);
}
if (flUnicase == undefined) {
flUnicase = true;
}
if (flUnicase) {
for (var i = 0; i < possibleBeginning.length; i++) {
if (s [i].toLowerCase () != possibleBeginning [i].toLowerCase ()) {
return (false);
}
}
}
else {
for (var i = 0; i < possibleBeginning.length; i++) {
if (s [i] != possibleBeginning [i]) {
return (false);
}
}
}
return (true);
}
function endsWith (s, possibleEnding, flUnicase) {
if ((s == undefined) || (s.length == 0)) {
return (false);
}
var ixstring = s.length - 1;
if (flUnicase == undefined) {
flUnicase = true;
}
if (flUnicase) {
for (var i = possibleEnding.length - 1; i >= 0; i--) {
if (s [ixstring--].toLowerCase () != possibleEnding [i].toLowerCase ()) {
return (false);
}
}
}
else {
for (var i = possibleEnding.length - 1; i >= 0; i--) {
if (s [ixstring--] != possibleEnding [i]) {
return (false);
}
}
}
return (true);
}
function stringContains (s, whatItMightContain, flUnicase) {
if (flUnicase == undefined) {
flUnicase = true;
}
if (flUnicase) {
s = s.toLowerCase ();
whatItMightContain = whatItMightContain.toLowerCase ();
}
return (s.indexOf (whatItMightContain) != -1);
}
function stringCountFields (s, chdelim) {
var ct = 1;
if (s.length == 0) {
return (0);
}
for (var i = 0; i < s.length; i++) {
if (s [i] == chdelim) {
ct++;
}
}
return (ct)
}
function stringNthField (s, chdelim, n) {
var splits = s.split (chdelim);
if (splits.length >= n) {
return splits [n-1];
}
return ("");
}
function stringDelete (s, ix, ct) {
var start = ix - 1;
var end = (ix + ct) - 1;
var s1 = s.substr (0, start);
var s2 = s.substr (end);
return (s1 + s2);
}
function stringMid (s, ix, len) {
return (s.substr (ix-1, len));
}
function padWithZeros (num, ctplaces) {
var s = num.toString ();
while (s.length < ctplaces) {
s = "0" + s;
}
return (s);
}
function getDatePath (theDate, flLastSeparator) {
if (theDate == undefined) {
theDate = new Date ();
}
else {
theDate = new Date (theDate); //8/12/14 by DW -- make sure it's a date type
}
if (flLastSeparator == undefined) {
flLastSeparator = true;
}
var month = padWithZeros (theDate.getMonth () + 1, 2);
var day = padWithZeros (theDate.getDate (), 2);
var year = theDate.getFullYear ();
if (flLastSeparator) {
return (year + "/" + month + "/" + day + "/");
}
else {
return (year + "/" + month + "/" + day);
}
}
function fsSureFilePath (path, callback) {
var splits = path.split ("/"), path = "";
if (splits.length > 0) {
function doLevel (levelnum) {
if (levelnum < (splits.length - 1)) {
path += splits [levelnum] + "/";
fs.exists (path, function (flExists) {
if (flExists) {
doLevel (levelnum + 1);
}
else {
fs.mkdir (path, undefined, function () {
doLevel (levelnum + 1);
});
}
});
}
else {
if (callback != undefined) {
callback ();
}
}
}
doLevel (0);
}
else {
if (callback != undefined) {
callback ();
}
}
}
//functions that are useful to scripts run from one of the folders
function getFullFilePath (relpath) { //12/30/14 by DW
var folderpath = folderPathFromEnv;
if (folderpath == undefined) { //the environment variable wasn't specified
return (relpath);
}
if (!endsWith (folderpath, "/")) {
folderpath += "/";
}
if (beginsWith (relpath, "/")) {
relpath = stringDelete (relpath, 1, 1);
}
return (folderpath + relpath);
}
function httpReadUrl (url, callback) {
request (url, function (error, response, body) {
if (!error && (response.statusCode == 200)) {
callback (body)
}
});
}
function fileExists (f, callback) {
var path = getFullFilePath (userFilesPath + f);
fs.exists (path, function (flExists) {
callback (flExists);
});
}
function readWholeFile (f, callback) {
var path = getFullFilePath (userFilesPath + f);
fsSureFilePath (path, function () {
fs.readFile (path, function (err, data) {
if (callback != undefined) {
callback (err, data);
}
});
});
}
function writeWholeFile (f, data, callback) {
var path = getFullFilePath (userFilesPath + f);
fsSureFilePath (path, function () {
fs.writeFile (path, data, function (err) {
if (callback != undefined) {
callback (err);
}
});
});
}
function writeStats (fname, stats) {
var f = getFullFilePath (fname);
fsSureFilePath (f, function () {
fs.writeFile (f, jsonStringify (stats), function (err) {
if (err) {
console.log ("writeStats: error == " + err.message);
}
});
});
}
function readStats (fname, stats, callback) {
var f = getFullFilePath (fname);
fs.exists (f, function (flExists) {
if (flExists) {
fs.readFile (f, function (err, data) {
if (err) {
console.log ("readStats: error reading file " + f + " == " + err.message)
}
else {
var storedStats = JSON.parse (data.toString ());
for (var x in storedStats) {
stats [x] = storedStats [x];
}
writeStats (fname, stats);
}
if (callback != undefined) {
callback ();
}
});
}
else {
writeStats (fname, stats);
if (callback != undefined) {
callback ();
}
}
});
}
function writeLocalStorageIfChanged () {
var s = jsonStringify (localStorage);
if (s != lastLocalStorageJson) {
lastLocalStorageJson = s;
writeStats (fnameLocalStorage, localStorage);
}
}
function runUserScript (s, scriptName) {
try {
eval (s);
}
catch (err) {
console.log ("runUserScript: error running \"" + scriptName + "\" == " + err.message);
}
}
function runScriptsInFolder (foldername, callback) {
var path = getFullFilePath (userScriptsPath + foldername);
if (!endsWith (path, "/")) {
path += "/";
}
fsSureFilePath (path, function () {
fs.readdir (path, function (err, list) {
if (!endsWith (path, "/")) {
path += "/";
}
if (err) { console.error("ERROR: ", err); return; }
for (var i = 0; i < list.length; i++) {
var fname = list [i];
if (endsWith (fname.toLowerCase (), ".js")) {
var f = path + fname;
fs.readFile (f, function (err, data) {
if (err) {
console.log ("runScriptsInFolder: error == " + err.message);
}
else {
runUserScript (data.toString (), f);
}
});
}
}
if (callback != undefined) {
callback ();
}
});
});
}
function handleHttpRequest (httpRequest, httpResponse) {
try {
var parsedUrl = urlpack.parse (httpRequest.url, true), host, port;
var lowerpath = parsedUrl.pathname.toLowerCase (), now = new Date ();
//set host, port
host = httpRequest.headers.host;
if (stringContains (host, ":")) {
port = stringNthField (host, ":", 2);
host = stringNthField (host, ":", 1);
}
console.log (now.toLocaleTimeString () + " " + httpRequest.method + " " + host + ":" + port + " " + lowerpath);
switch (lowerpath) {
case "/":
httpResponse.writeHead (200, {"Content-Type": "text/plain"});
httpResponse.end ('/version \n/now \n/status');
case "/version":
httpResponse.writeHead (200, {"Content-Type": "text/plain"});
httpResponse.end (myVersion);
break;
case "/now":
httpResponse.writeHead (200, {"Content-Type": "text/plain"});
httpResponse.end (now.toString ());
break;
case "/status":
var status = {
prefs: noderunnerPrefs,
status: noderunnerStats
}
httpResponse.writeHead (200, {"Content-Type": "text/plain"});
httpResponse.end (jsonStringify (status));
break;
default:
httpResponse.writeHead (404, {"Content-Type": "text/plain"});
httpResponse.end ("The file was not found.");
break;
}
}
catch (tryError) {
httpResponse.writeHead (500, {"Content-Type": "text/plain"});
httpResponse.end (tryError.message);
}
}
function overnight () {
var now = new Date ();
console.log ("Running overnight scripts.");
noderunnerStats.whenLastOvernight = now;
noderunnerStats.ctOvernight++;
runScriptsInFolder (overnightScriptsFolderName);
}
function everyHour () {
var now = new Date ();
console.log ("Running hourly scripts.");
noderunnerStats.whenLastEveryHour = now;
noderunnerStats.ctEveryHour++;
runScriptsInFolder (everyHourScriptsFolderName);
}
function everyMinute () {
var now = new Date ();
console.log ("");
console.log ("everyMinute: " + now.toLocaleTimeString ());
runScriptsInFolder (everyMinuteScriptsFolderName);
if (now.getMinutes () == noderunnerPrefs.minuteToRunHourlyScripts) {
everyHour ();
}
if ((now.getMinutes () == 0) && (now.getHours () == noderunnerPrefs.hourToRunOvernightScripts)) {
overnight ();
}
noderunnerStats.whenLastEveryMinute = now;
noderunnerStats.ctEveryMinute++;
writeStats (fnameStats, noderunnerStats);
}
function everySecond () {
var now = new Date ();
noderunnerStats.whenLastEverySecond = now;
noderunnerStats.ctEverySecond++;
runScriptsInFolder (everySecondScriptsFolderName);
if (now.getSeconds () == noderunnerPrefs.secondToRunEveryMinuteScripts) {
everyMinute ();
}
writeLocalStorageIfChanged ();
//sleep until the next second
var ctmilliseconds = 1000 - (Number (new Date ().getMilliseconds ()) + 1000) % 1000;
setTimeout (everySecond, ctmilliseconds);
}
function startup () {
readStats (fnamePrefs, noderunnerPrefs, function () {
readStats (fnameLocalStorage, localStorage, function () {
lastLocalStorageJson = jsonStringify (localStorage);
readStats (fnameStats, noderunnerStats, function () {
var now = new Date ();
console.log (myProductName + " v" + myVersion + ".");
if (folderPathFromEnv != undefined) { //12/30/14 by DW
console.log ("Data and scripts are in: " + folderPathFromEnv);
}
noderunnerStats.ctStarts++;
noderunnerStats.whenLastStart = now;
writeStats (fnameStats, noderunnerStats);
runScriptsInFolder (startupScriptsFolderName, function () {
everySecond ();
http.createServer (handleHttpRequest).listen (noderunnerPrefs.myPort);
});
});
});
});
}
function getDropboxConf () {
if (process.env.DROPBOX_APP_KEY && process.env.DROPBOX_APP_SECRET &&
process.env.DROPBOX_ACCESS_TOKEN) {
return {
key: process.env.DROPBOX_APP_KEY,
secret: process.env.DROPBOX_APP_SECRET,
token: process.env.DROPBOX_ACCESS_TOKEN,
uid: '00000000'
};
}
}
if (getDropboxConf ()) {
var volume = new Dropbox.Volume({
key: process.env.DROPBOX_APP_KEY,
secret: process.env.DROPBOX_APP_SECRET,
token: process.env.DROPBOX_ACCESS_TOKEN,
uid: '00000000'
});
volume.init(function (err) {
if (err) {
console.error(err);
process.exit(1);
} else {
fs = volume.fs;
// assuming Noderunner folderPath is the root folder of the Dropbox app
folderPathFromEnv = '/';
startup();
}
})
} else {
startup();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment