498 lines
23 KiB
JavaScript
498 lines
23 KiB
JavaScript
// OSSS (Open Source Surveillance and Security)
|
|
// Simple Web App
|
|
//
|
|
// Donald Burr <dburr@vctlabs.com>, 12/12/2014
|
|
|
|
var async = require('/usr/local/lib/node_modules/async');
|
|
var http = require('http')
|
|
var url = require('url')
|
|
var fs = require('fs')
|
|
var path = require('path')
|
|
var os = require("os");
|
|
var metalib = require('/usr/local/lib/node_modules/fluent-ffmpeg').Metadata
|
|
var sprintf = require('/usr/local/lib/node_modules/sprintf-js').sprintf;
|
|
|
|
// workaround for some node.js api changes
|
|
fs.exists = fs.exists || require('path').exists;
|
|
fs.existsSync = fs.existsSync || require('path').existsSync;
|
|
|
|
// debug mode
|
|
var DEBUG = false;
|
|
|
|
// sort order defaults to forward
|
|
var reverse_sort_order = 0;
|
|
|
|
// get arguments
|
|
var args = process.argv.slice(2);
|
|
if (args.length > 0) {
|
|
if (args[0] === "-d") {
|
|
console.log("Debug mode enabled.");
|
|
DEBUG = true;
|
|
}
|
|
}
|
|
|
|
// port to use
|
|
var PORT = 80;
|
|
if (DEBUG) {
|
|
console.log("Using port 8000 since we are in DEBUG mode.");
|
|
PORT = 8000;
|
|
}
|
|
|
|
// these MUST match "width" and "height" settings in /etc/motion/motion.conf!
|
|
var WIDTH = 320;
|
|
var HEIGHT = 240;
|
|
|
|
// number of pixels of padding to add to IFRAME (required by some browsers,
|
|
// notably Firefox, to prevent scroll bars from appearing within the IFRAME)
|
|
var PADDING = 20;
|
|
|
|
// pretty-print # of seconds as hrs:mintes:seconds
|
|
function secondsToString(seconds, shortFormat)
|
|
{
|
|
shortFormat = typeof shortFormat !== 'undefined' ? shortFormat : false;
|
|
var str = "";
|
|
//var numyears = Math.floor(seconds / 31536000);
|
|
var numdays = Math.floor((seconds % 31536000) / 86400);
|
|
var numhours = Math.floor(((seconds % 31536000) % 86400) / 3600);
|
|
var numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60);
|
|
var numseconds = (((seconds % 31536000) % 86400) % 3600) % 60;
|
|
if (shortFormat) {
|
|
if (numdays > 0) {
|
|
str += sprintf("%02dd", numdays);
|
|
}
|
|
if (numhours > 0) {
|
|
str += sprintf("%02dh", numhours);
|
|
}
|
|
if (numminutes > 0) {
|
|
str += sprintf("%02dd", numminutes);
|
|
}
|
|
if (numseconds > 0) {
|
|
str += sprintf("%02ds", numseconds);
|
|
}
|
|
} else {
|
|
if (numdays > 0) {
|
|
str += numdays + " days, ";
|
|
}
|
|
if (numhours > 0) {
|
|
str += numhours + " hours, ";
|
|
}
|
|
if (numminutes > 0) {
|
|
str += numminutes + " minutes, ";
|
|
}
|
|
if (numseconds > 0) {
|
|
str += numseconds + " seconds";
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// fun with timezone offsets
|
|
function pad(value) {
|
|
return value < 10 ? '0' + value : value;
|
|
}
|
|
|
|
function createOffset(offset_in) {
|
|
var sign = (offset_in > 0) ? "-" : "+";
|
|
var offset = Math.abs(offset_in);
|
|
var hours = pad(Math.floor(offset / 60));
|
|
var minutes = pad(offset % 60);
|
|
return sign + hours + ":" + minutes;
|
|
}
|
|
|
|
function formatAMPM(date) {
|
|
var hours = date.getHours();
|
|
var minutes = date.getMinutes();
|
|
var ampm = hours >= 12 ? 'pm' : 'am';
|
|
hours = hours % 12;
|
|
hours = hours ? hours : 12; // the hour '0' should be '12'
|
|
minutes = minutes < 10 ? '0'+minutes : minutes;
|
|
var strTime = hours + ':' + minutes + ' ' + ampm;
|
|
return strTime;
|
|
}
|
|
|
|
function humanFileSize(bytes, si) {
|
|
var thresh = si ? 1000 : 1024;
|
|
if(bytes < thresh) return bytes + ' B';
|
|
var units = si ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
|
var u = -1;
|
|
do {
|
|
bytes /= thresh;
|
|
++u;
|
|
} while(bytes >= thresh);
|
|
return bytes.toFixed(1)+' '+units[u];
|
|
};
|
|
|
|
function endsWith(str, suffix) {
|
|
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
|
}
|
|
|
|
function r(max) {
|
|
return Math.floor(Math.random() * max);
|
|
}
|
|
|
|
function parseCookies (request) {
|
|
var list = {},
|
|
rc = request.headers.cookie;
|
|
|
|
rc && rc.split(';').forEach(function( cookie ) {
|
|
var parts = cookie.split('=');
|
|
list[parts.shift().trim()] = decodeURI(parts.join('='));
|
|
});
|
|
|
|
return list;
|
|
}
|
|
|
|
function respondToHttpRequest(req, res) {
|
|
var hostname = os.hostname();
|
|
var host = req.headers["host"];
|
|
var hostport = host
|
|
var cookies = parseCookies(req);
|
|
if (cookies["reverse_sort_order"]) {
|
|
reverse_sort_order = parseInt(cookies["reverse_sort_order"]);
|
|
}
|
|
if(host.indexOf(":") > -1) {
|
|
host = host.substring(0, host.indexOf(':'));
|
|
}
|
|
|
|
var ua = req.headers['user-agent'],
|
|
$ = {};
|
|
|
|
if (/mobile/i.test(ua))
|
|
$.Mobile = true;
|
|
|
|
if (/like Mac OS X/.test(ua)) {
|
|
$.iOS = /CPU( iPhone)? OS ([0-9\._]+) like Mac OS X/.exec(ua)[2].replace(/_/g, '.');
|
|
$.iPhone = /iPhone/.test(ua);
|
|
$.iPad = /iPad/.test(ua);
|
|
}
|
|
|
|
if (/Android/.test(ua))
|
|
$.Android = /Android ([0-9\.]+)[\);]/.exec(ua)[1];
|
|
|
|
if (/webOS\//.test(ua))
|
|
$.webOS = /webOS\/([0-9\.]+)[\);]/.exec(ua)[1];
|
|
|
|
if (/(Intel|PPC) Mac OS X/.test(ua))
|
|
$.Mac = /(Intel|PPC) Mac OS X ?([0-9\._]*)[\)\;]/.exec(ua)[2].replace(/_/g, '.') || true;
|
|
|
|
if (/[Ll]inux/.test(ua))
|
|
$.Linux = true;
|
|
|
|
if (/Windows NT/.test(ua))
|
|
$.Windows = /Windows NT ([0-9\._]+)[\);]/.exec(ua)[1];
|
|
|
|
console.log("USER AGENT DETECTION RESULTS:");
|
|
console.log($);
|
|
|
|
//console.log("host = " + host);
|
|
// console.log(url);
|
|
var queryData = url.parse(req.url, true, true).query;
|
|
console.log("Responding to http request: " + req.url);
|
|
//console.log("query data: " + queryData);
|
|
|
|
if (queryData.mode) {
|
|
console.log("mode = " + queryData.mode);
|
|
if (queryData.mode === "live_view") {
|
|
// <iframe src="http://www.w3schools.com"></iframe>
|
|
res.writeHead(200, "Content-Type: text/html");
|
|
var rhtml = "<HTML><HEAD>"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />"
|
|
+ "<TITLE>OSSS @ " + hostname + " - Live View"
|
|
+ "</TITLE></HEAD>"
|
|
+ "<BODY>"
|
|
+ "<DIV ALIGN=\"CENTER\">"
|
|
+ "<H1>OSSS @ " + hostname + " - Live View</H1>";
|
|
if ($.Android) {
|
|
//rhtml += "<A HREF=\"vlc://" + host + ":8080/\">Click here to view</A>.<br />(Requires the free VLC app for Android. <A HREF=\"https://play.google.com/store/apps/details?id=org.videolan.vlc.betav7neon&hl=en\">Download it here</A>.)</A>";
|
|
rhtml += "<IMG WIDTH=\"" + WIDTH + "\" HEIGHT=\"" + HEIGHT + "\" SRC=\"http://" + host + ":8080\"></IMG>";
|
|
} else if ($.iOS) {
|
|
//rhtml += "<A HREF=\"infuse://" + host + ":8080\">Click here to view</A>.<br />(Requires the free Infuse app. <A HREF=\"https://itunes.apple.com/us/app/infuse-3/id577130046\">Download it here</A>.)</A>";
|
|
rhtml += "<A HREF=\"vlc://http://" + host + ":8080/\">Click here to view</A>.<br />(Requires the free VLC app for iOS. <A HREF=\"http://get.videolan.org/vlc-iOS/2.3.0/vlc-iOS-2.3.0.ipa\">Download it here</A>.)</A>";
|
|
} else {
|
|
rhtml += "<IFRAME WIDTH=\"" + (WIDTH + PADDING) + "\" HEIGHT=\"" + (HEIGHT + PADDING) + "\" frameBorder=\"0\" seamless=\"seamless\" scrolling=\"no\" frameborder=\"0\" hspace=\"0\" vspace=\"0\" marginheight=\"0\" marginwidth=\"0\" SRC=\"http://" + host + ":8080\"></IFRAME>";
|
|
}
|
|
rhtml += "<P><A HREF=\"http://" + hostport + "/\">Back</A></p>"
|
|
+ "</DIV>"
|
|
+ "</BODY>"
|
|
+ "<FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER>"
|
|
+ "</HTML>";
|
|
res.end(rhtml);
|
|
} else if (queryData.mode === "file_list") {
|
|
var headers = {"Content-Type": "text/html"};
|
|
if (queryData.set_reverse_sort_order) {
|
|
reverse_sort_order = parseInt(queryData.set_reverse_sort_order);
|
|
headers["Set-Cookie"] = "reverse_sort_order=" + queryData.set_reverse_sort_order;
|
|
}
|
|
fs.readdir("/var/spool/motion", function(err, list) {
|
|
if(err) {
|
|
res.writeHead(200, headers);
|
|
res.end("<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /><TITLE>OSSS @ " + hostname + " - Error</TITLE></HEAD><BODY><h1>Error: unable to get file list: " + err + "</h1></BODY></HTML>");
|
|
} else {
|
|
res.writeHead(200, headers);
|
|
res.write("<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /><TITLE>OSSS @ " + hostname + " - Saved Recordings</TITLE></HEAD><BODY><h1>" + hostname + " - Saved Recordings</h1>");
|
|
var regex = new RegExp(".*\.avi");
|
|
var fileList = new Array()
|
|
var done = false;
|
|
console.log("start foreach");
|
|
async.forEachSeries(list, function(item) {
|
|
if(regex.test(item)) {
|
|
var metaobject = new metalib("/var/spool/motion/" + item);
|
|
var eventNumber = item.substr(0, 2);
|
|
var offset = new Date().getTimezoneOffset();
|
|
var dateString = item.substr(3, item.indexOf(".avi")-3).replace(/\./g, ':') + createOffset(offset);
|
|
var dateObject = new Date(dateString);
|
|
var stats = fs.statSync("/var/spool/motion/" + item)
|
|
var fileSizeInBytes = stats["size"]
|
|
var fileInfo = new Array();
|
|
fileInfo[0] = dateObject;
|
|
fileInfo[1] = item;
|
|
fileInfo[2] = eventNumber;
|
|
fileInfo[3] = fileSizeInBytes;
|
|
metaobject.get(function(metadata, err) {
|
|
if (!err) {
|
|
fileInfo[4] = metadata["durationsec"];
|
|
} else {
|
|
fileInfo[4] = -1;
|
|
}
|
|
fileList.push(fileInfo);
|
|
});
|
|
}
|
|
}, function(err) {
|
|
console.log("REALLY ALL DONE NOW");
|
|
done = true
|
|
});
|
|
console.log("about to start while");
|
|
console.log("done = " + done);
|
|
while (!done) {console.log('waiting');};
|
|
if (fileList.length > 0) {
|
|
// sort by date
|
|
fileList.sort(function(a,b){
|
|
a = a[0];
|
|
b = b[0];
|
|
if (reverse_sort_order) {
|
|
c = a > b ? -1 : a < b ? 1 : 0;
|
|
} else {
|
|
c = a < b ? -1 : a > b ? 1 : 0;
|
|
}
|
|
return c;
|
|
});
|
|
res.write("<p>Current Sort Order: " +
|
|
(reverse_sort_order ? "Reverse" : "Forward") +
|
|
" (<A HREF=\"http://" + hostport + "/?mode=file_list&set_reverse_sort_order=" + (reverse_sort_order ? "0" : "1") + "\">change</A>)</p>");
|
|
res.write("<UL>");
|
|
for (var i in fileList) {
|
|
var item = fileList[i];
|
|
res.write("<LI>" + item[0].toLocaleDateString() + " @ " + formatAMPM(item[0]) + " (Event #" + item[2] + ") (" + humanFileSize(item[3], true) + ", " + (item[4] == -1 ? "cannot calculate duration" : secondsToString(item[4], true)) + ") (<A HREF=\"http://" + hostport + "/?mode=view&filename=" + item[1] + ($.Linux && !$.Android ? "&use_vlc_plugin=true" : "") + "\">view</A>) (<A HREF=\"http://" + hostport + "/?mode=download&filename=" + item[1] + "\">download</A>) (<A HREF=\"http://" + hostport + "/?mode=delete&filename=" + item[1] + "\">delete</A>)</LI>");
|
|
}
|
|
res.end("</ul><h3><A HREF=\"http://" + hostport + "/?mode=delete&filename=ALL\">Delete ALL Videos</A></h3><p><A HREF=\"http://" + hostport + "/\">Back</A></p></BODY><FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER></HTML>");
|
|
} else {
|
|
res.write("<h3>No Files Found</h3>");
|
|
}
|
|
}
|
|
res.end("<p><A HREF=\"http://" + hostport + "/\">Back</A></p></BODY><FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER></HTML>");
|
|
});
|
|
} else if (queryData.mode === "delete") {
|
|
if (queryData.filename) {
|
|
var filename = queryData.filename;
|
|
if (queryData.confirmed) {
|
|
var confirmed = queryData.confirmed;
|
|
var rhtml = "<HTML><HEAD>"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />"
|
|
+ "<TITLE>OSSS @ " + hostname + " - Delete File(s)"
|
|
+ "</TITLE></HEAD>"
|
|
+ "<BODY>"
|
|
+ "<H1>OSSS @ " + hostname + " - Delete File(s)</H1>"
|
|
+ "<p><h3>";
|
|
if (confirmed === "yes") {
|
|
var numDeleted = 0;
|
|
var numErrors = 0;
|
|
var path = "/var/spool/motion/";
|
|
if (filename === "ALL") {
|
|
var files = fs.readdirSync(path);
|
|
var success = true
|
|
files.forEach(function(file) {
|
|
if (file.match("^.*avi$")) {
|
|
try {
|
|
fs.unlinkSync(path + file);
|
|
numDeleted++;
|
|
rhtml += "Deleted \"" + file + "\"<br />";
|
|
} catch (ex) {
|
|
numErrors++;
|
|
rhtml += "Could not delete \"" + file + "\": " + ex + "<br />";
|
|
success = false
|
|
}
|
|
}
|
|
});
|
|
rhtml += "<br />";
|
|
} else {
|
|
path += filename;
|
|
try {
|
|
fs.unlinkSync(path);
|
|
numDeleted++;
|
|
success = true;
|
|
} catch (ex) {
|
|
numErrors++;
|
|
rhtml += "Could not delete \"" + filename + "\": " + ex + "<br /><br />";
|
|
success = false;
|
|
}
|
|
}
|
|
if (success) {
|
|
rhtml += numDeleted + " file" + (numDeleted > 1 ? "s" : "") + " deleted successfully.";
|
|
} else {
|
|
rhtml += numDeleted > 0 ? numDeleted + " file" + (numDeleted > 1 ? "s" : "") + " deleted successfully." : "";
|
|
rhtml += numErrors + " delete failure" + (numErrors > 1 ? "s" : "") + ".";
|
|
}
|
|
} else {
|
|
rhtml += "Delete cancelled.";
|
|
}
|
|
rhtml += "<h3><A HREF=\"" + hostport + "/?mode=file_list\">OK</A></h3></p>"
|
|
+ "</BODY>"
|
|
+ "<FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER>"
|
|
+ "</HTML>";
|
|
res.end(rhtml);
|
|
} else {
|
|
var rhtml = "<HTML><HEAD>"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />"
|
|
+ "<TITLE>OSSS @ " + hostname + " - Delete File(s)"
|
|
+ "</TITLE></HEAD>"
|
|
+ "<BODY>"
|
|
+ "<H1>OSSS @ " + hostname + " - Delete File(s)</H1>"
|
|
+ "<p><h3>You are about to delete "
|
|
+ (filename === "ALL" ? "ALL videos!" : "\"" + filename + "\"")
|
|
+ "<br /><br />"
|
|
+ "Are you SURE you want to do this?<br /><br />"
|
|
+ "<A HREF=\"" + hostport + "/?mode=delete&filename=" + filename + "&confirmed=yes\">YES</A> <A HREF=\"" + hostport + "/?mode=delete&filename=" + filename + "&confirmed=no\">NO</A></h3></p>"
|
|
+ "</BODY>"
|
|
+ "<FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER>"
|
|
+ "</HTML>";
|
|
res.end(rhtml);
|
|
}
|
|
} else {
|
|
res.writeHead(200, "Content-Type: text/html");
|
|
res.end("<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /><TITLE>OSSS @ " + hostname + " - Error</TITLE></HEAD><BODY><h1>Error: filename not specified.</h1></BODY></HTML>");
|
|
}
|
|
} else if (queryData.mode === "view") {
|
|
if (queryData.filename) {
|
|
var filename = queryData.filename;
|
|
var filePath = "/var/spool/motion/" + filename;
|
|
fs.exists(filePath, function(exists) {
|
|
if (exists) {
|
|
console.log('viewing contents of local file: ' + filePath);
|
|
var stat = fs.statSync(filePath);
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/html'
|
|
});
|
|
var rhtml = "<HTML><HEAD>"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />"
|
|
+ "<TITLE>OSSS @ " + hostname + " - Video Playback - " + filename
|
|
+ "</TITLE></HEAD>"
|
|
+ "<BODY>"
|
|
+ "<DIV ALIGN=\"CENTER\">"
|
|
+ "<H1>OSSS @ " + hostname + " - " + filename + "</H1>";
|
|
if (queryData.use_vlc_plugin) {
|
|
rhtml += "<embed type=\"application/x-vlc-plugin\""
|
|
+ "pluginspage=\"http://www.videolan.org\""
|
|
+ "name=\"video1\""
|
|
+ "id=\"video1\""
|
|
+ "controls=\"yes\" toolbar=\"yes\" autoplay=\"no\" loop=\"yes\" width=\"" + WIDTH + "\" height=\"" + HEIGHT + "\""
|
|
+ "target=\"http://" + hostport + "/?mode=download&filename=" + filename + "\" />"
|
|
+ "<br />"
|
|
+ "(Note: if you don't see a video window above, then you must <A HREF=\"http://www.videolan.org/vlc/download-ubuntu.html\">install the VLC browser plugin</A>.)"
|
|
+ "<br />";
|
|
/*
|
|
+ "<a href=\"javascript:;\" onclick='document.video1.play()'>[Play]</a> "
|
|
+ "<a href=\"javascript:;\" onclick='document.video1.pause()'>[Pause]</a> "
|
|
+ "<a href=\"javascript:;\" onclick='document.video1.stop()'>[Stop]</a> "
|
|
+ "<a href=\"javascript:;\" onclick='document.video1.fullscreen()'>[fullscreen]</a>";
|
|
*/
|
|
} else {
|
|
if ($.Android) {
|
|
//rhtml += "<A HREF=\"vlc://" + hostport + "/?mode=download&filename=" + filename + "\">Tap here to play this video</A>.<br />(Requires the free VLC app for Android. <A HREF=\"https://play.google.com/store/apps/details?id=org.videolan.vlc.betav7neon&hl=en\">Download it here</A>.)";
|
|
rhtml += "<A HREF=\"http://" + hostport + "/?mode=download&filename=" + filename + "\">Tap here to download this file</A>.<br />Once the file is downloaded, swipe down Notification Center, and tap on the file to open it in VLC. (Requires the free VLC app for Android. <A HREF=\"https://play.google.com/store/apps/details?id=org.videolan.vlc.betav7neon&hl=en\">Download it here</A>.)";
|
|
rhtml += "<br />Note #2: you may get an error message saying VLC encounterd an error with this media. If so, tap the \"Refresh\" (two arrows going around in circles) button in VLC, and the downloaded file should appear. Seems to be a bug with the latest VLC, they broke it. :-P";
|
|
} else if ($.iOS) {
|
|
rhtml += "<A HREF=\"vlc://http://" + hostport + "/?mode=download&filename=" + filename + "\">Tap here to play this video</A>.<br />(Requires the free VLC app for iOS. <A HREF=\"http://get.videolan.org/vlc-iOS/2.3.0/vlc-iOS-2.3.0.ipa\">Download it here</A>.)";
|
|
//rhtml += "<A HREF=\"infuse://" + hostport + "/?mode=download&filename=" + filename + "\">Tap here to play this video</A>.<br />(Requires the free Infuse app. <A HREF=\"https://itunes.apple.com/us/app/infuse-3/id577130046?mt=8\">Download it here</A>.)";
|
|
} else {
|
|
rhtml += "<video width=\"" + WIDTH + "\" height=\"" + HEIGHT + "\" controls>"
|
|
+ "<source src=\"http://" + hostport + "/?mode=download&filename=" + filename + "\" type=\"video/x-msvideo\">"
|
|
+ "Your browser does not support the video tag."
|
|
+ "</video>"
|
|
+ "<br />";
|
|
// + "<A HREF=\"http://" + hostport + "/?mode=view&filename=" + filename + "&use_vlc_plugin=true\">(Don't see any video? Try this...)</A>";
|
|
}
|
|
}
|
|
rhtml += "<P><A HREF=\"http://" + hostport + "/?mode=file_list\">Back</A></p>"
|
|
+ "</DIV>"
|
|
+ "</BODY>"
|
|
+ "<FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER>"
|
|
+ "</HTML>";
|
|
res.end(rhtml);
|
|
} else {
|
|
console.log('returning 404, could not find: ' + filePath);
|
|
res.writeHead(404);
|
|
res.end('Not found. Go away kid, you\'re bothering me.');
|
|
}
|
|
});
|
|
} else {
|
|
res.writeHead(200, "Content-Type: text/html");
|
|
res.end("<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /><TITLE>OSSS @ " + hostname + " - Error</TITLE></HEAD><BODY><h1>Error: filename not specified.</h1></BODY></HTML>");
|
|
}
|
|
} else if (queryData.mode === "download") {
|
|
if (queryData.filename) {
|
|
var filename = queryData.filename;
|
|
var filePath = "/var/spool/motion/" + filename;
|
|
fs.exists(filePath, function(exists) {
|
|
if (exists) {
|
|
console.log('sending contents of local file: ' + filePath);
|
|
var stat = fs.statSync(filePath);
|
|
var contenttype = "video/x-msvideo";
|
|
res.writeHead(200, {
|
|
'Content-Type': contenttype,
|
|
'Content-Length': stat.size,
|
|
'Content-Disposition': "attachment; filename=" + filename
|
|
});
|
|
var readStream = fs.createReadStream(filePath);
|
|
readStream.pipe(res);
|
|
} else {
|
|
console.log('returning 404, could not find: ' + filePath);
|
|
res.writeHead(404);
|
|
res.end('Not found. Go away kid, you\'re bothering me.');
|
|
}
|
|
});
|
|
} else {
|
|
res.writeHead(200, "Content-Type: text/html");
|
|
res.end("<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /><TITLE>OSSS @ " + hostname + " - Error</TITLE></HEAD><BODY><h1>Error: filename not specified.</h1></BODY></HTML>");
|
|
}
|
|
}
|
|
} else {
|
|
// send the index page
|
|
console.log("Responding with index page");
|
|
res.writeHead(200, "Content-Type: text/html");
|
|
var rhtml = "<HTML>"
|
|
+ "<HEAD>"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />"
|
|
+ "<TITLE>OSSS (Open Source Surveillance and Security) - " + hostname + "</TITLE>"
|
|
+ "</HEAD>"
|
|
+ "<BODY>"
|
|
+ "<p><h1>OSSS (Open Source Surveillance and Security) - " + hostname + "</h1></p>"
|
|
+ "<p>"
|
|
+ "<ul>"
|
|
+ "<li><A HREF=\"http://" + hostport + "/?mode=live_view\">Live View</A></li>"
|
|
+ "<li><A HREF=\"http://" + hostport + "/?mode=file_list\">Saved Recordings</A></li>"
|
|
+ "</UL>"
|
|
+ "</BODY>"
|
|
+ "<FOOTER><HR><p>OSSS @ " + hostname + "</p></FOOTER>"
|
|
+ "</HTML>";
|
|
res.end(rhtml);
|
|
}
|
|
}
|
|
|
|
// Set up the HTTP listener
|
|
var server = http.createServer(function (req, res) {
|
|
respondToHttpRequest(req, res);
|
|
}).listen(PORT);
|
|
server.on('connection', function(sock) {
|
|
console.log('Incoming HTTP connection from ' + sock.remoteAddress);
|
|
});
|
|
console.log('HTTP server running on port ' + PORT + '.');
|