Last active
August 29, 2015 14:12
-
-
Save mrfabbri/d7b5665527a702632040 to your computer and use it in GitHub Desktop.
Noderunner + Dropbox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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