diff --git a/basehttpstreamprocessor.js b/basehttpstreamprocessor.js index eb188d1..e1f4206 100644 --- a/basehttpstreamprocessor.js +++ b/basehttpstreamprocessor.js @@ -1,5 +1,6 @@ var util = require('util'); var http = require('http'); +var logger = require('./logger'); var BaseProcessor = require('./baseprocessor'); @@ -51,7 +52,7 @@ BaseHTTPStreamProcessor.prototype.run = function() req.on('error', function(e) { - console.log(e); + logger.error(e); doCleanup(); }); diff --git a/capture.js b/capture.js index 1945542..5d2a1b8 100644 --- a/capture.js +++ b/capture.js @@ -1,7 +1,6 @@ var http = require('http'); var stream = require('stream'); - -var config = require('./config'); +var logger = require('./logger'); function runCommand(command, displayName, callback) @@ -18,7 +17,7 @@ function runCommand(command, displayName, callback) if (command.url) { - console.log('Running command: ' + (command.displayName ? command.displayName : displayName)); + logger.verbose('Running command: ' + (command.displayName ? command.displayName : displayName)); req = http.request(command.url, function(res) { @@ -28,7 +27,7 @@ function runCommand(command, displayName, callback) req.on('error', function(e) { - console.log(e); + logger.error(e); wait(); }); diff --git a/index.js b/index.js index 07c8915..2263a48 100644 --- a/index.js +++ b/index.js @@ -1,70 +1,174 @@ var moment = require('moment'); +var fs = require('fs'); +var chokidar = require('chokidar'); var express = require('express'); - -var config = require('./config'); var capture = require('./capture'); - +var logger = require('./logger'); var app = express(); +var server = null; +var serverPort = 0; +var config = null; +var configError = null; -// Some sanity checking to avoid issues later on -if (!config.cams) - config.cams = []; +var loadConfig = function() +{ + try + { + // Unload existing config + if (config !== null) + { + var name = require.resolve('./config'); + delete require.cache[name]; + } + + config = require('./config'); + + // Some sanity checking to avoid issues later on + if (!config.cams) + config.cams = []; + + capture.init(config.cams); + configError = null; + + if (server === null || config.port !== serverPort) + { + if (server !== null) + { + server.close(); + server = null; + } + + serverPort = config.port; + server = app.listen(config.port, function() + { + logger.info('SecurityCam.js running on port ' + config.port); + }); + } + } + catch (e) + { + config = null; + configError = e.message; + + // Don't close the server if it was running before. The port is + // unlikely to change, and this will allow the status page to + // report the error. + + logger.error('Error loading configuration:'); + + // Tried to use e.stack to get more information about where the syntax + // error is, but it seems SyntaxError doesn't provide it yet: + // http://stackoverflow.com/questions/20570477/line-number-of-syntaxerror-in-node-js + logger.error(e); + } +} -capture.init(config.cams); app.get('/', function(req, res) { - res.send(JSON.stringify( + if (config !== null) { - status: 'running', - cams: Object.keys(config.cams) - })); + res.send(JSON.stringify( + { + status: 'running', + cams: Object.keys(config.cams) + })); + } + else if (configError !== null) + { + res.send(JSON.stringify( + { + status: 'error', + error: configError + })); + } + else + { + res.send(JSON.stringify( + { + status: 'error', + error: 'An unhandled error has occured. See the console for more information, hopefully.' + })); + } }); app.get('/capture', function(req, res) { - var now = moment(); - var cams = []; - - for (var camId in config.cams) + if (config !== null) { - if (config.cams.hasOwnProperty(camId)) + var now = moment(); + var cams = []; + + for (var camId in config.cams) { - cams.push(camId); - capture.start(camId, config.cams[camId], now); + if (config.cams.hasOwnProperty(camId)) + { + cams.push(camId); + capture.start(camId, config.cams[camId], now); + } } + + logger.info('Started capture for: ' + cams.join(', ')); + + res.send(JSON.stringify(cams)); + } + else + { + res.status(500).send({ error: 'See / for more information' }); } - - console.log('Started capture for: ' + cams.join(', ')); - - res.send(JSON.stringify(cams)); }); app.get('/capture/:camId', function(req, res) { - var camId = req.params.camId; - - if (config.cams.hasOwnProperty(camId)) + if (config !== null) { - capture.start(camId, config.cams[camId], moment()); + var camId = req.params.camId; - console.log('Started capture for: ' + camId); - res.send(JSON.stringify([camId])); + if (config.cams.hasOwnProperty(camId)) + { + capture.start(camId, config.cams[camId], moment()); + + logger.info('Started capture for: ' + camId); + res.send(JSON.stringify([camId])); + } + else + { + logger.warning('Cam not found: ' + camId); + res.sendStatus(404); + } } else { - console.log('Cam not found: ' + camId); - res.sendStatus(404); + res.status(500).send({ error: 'See / for more information' }); } }); +var watcher = chokidar.watch('./config.js'); +var firstLoad = true; -app.listen(config.port, function() +watcher.on('all', function() { - console.log('SecurityCam.js running on port ' + config.port); -}); \ No newline at end of file + if (!firstLoad) + logger.verbose('Configuration change detected, reloading...'); + + loadConfig(); + firstLoad = false; +}); + + +// It looks like the add event from chokidar will always be fired immediately, +// at least on Windows, but I couldn't find anything in the documentation so +// let's not make that assumption and double-check. +setTimeout(function() +{ + if (firstLoad) + { + loadConfig(); + firstLoad = false; + } +}, 1000); \ No newline at end of file diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..e972368 --- /dev/null +++ b/logger.js @@ -0,0 +1,32 @@ +var moment = require('moment'); +var logger = {}; + + +function log(level, message) +{ + console.log('[' + moment().format() + '] ' + level + ': ' + message); +} + + +logger.verbose = function(message) +{ + log('Verbose', message); +} + +logger.info = function(message) +{ + log('Info', message); +} + +logger.warning = function(message) +{ + log('Warning', message); +} + +logger.error = function(message) +{ + log('Error', message); +} + + +module.exports = logger; \ No newline at end of file diff --git a/package.json b/package.json index 2339cc6..8ae239c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "author": "", "license": "ISC", "dependencies": { + "chokidar": "^1.6.0", "express": "^4.14.0", "file-on-write": "^1.1.1", "fluent-ffmpeg": "^2.1.0", diff --git a/processor-ffmpeg.js b/processor-ffmpeg.js index 9905f53..78097b7 100644 --- a/processor-ffmpeg.js +++ b/processor-ffmpeg.js @@ -1,10 +1,10 @@ var fs = require('fs'); var util = require('util'); var stream = require('stream'); - var FfmpegCommand = require('fluent-ffmpeg'); var helpers = require('./helpers'); +var logger = require('./logger'); var BaseProcessor = require('./baseprocessor'); @@ -30,7 +30,7 @@ FFMPEGProcessor.prototype.run = function() { fs.rename(tempFilename, filename, function(err) { - console.log('Error: could not move ' + tempFilename + ' to ' + filename + ': ' + err.message); + logger.error('Could not move ' + tempFilename + ' to ' + filename + ': ' + err.message); self.doEnd(); }); } @@ -55,7 +55,7 @@ FFMPEGProcessor.prototype.run = function() command.on('error', function(err, stdout, stderr) { - console.log('Error: FFmpeg output:' + err.message); + logger.error('FFmpeg output:' + err.message); cleanup(); }); diff --git a/processor-http-ffmpeg.js b/processor-http-ffmpeg.js index 183854d..92fedfa 100644 --- a/processor-http-ffmpeg.js +++ b/processor-http-ffmpeg.js @@ -5,6 +5,7 @@ var stream = require('stream'); var FfmpegCommand = require('fluent-ffmpeg'); var helpers = require('./helpers'); +var logger = require('./logger'); var BaseHTTPStreamProcessor = require('./basehttpstreamprocessor'); @@ -43,7 +44,7 @@ HTTPFFMPEGProcessor.prototype.run = function() command.on('error', function(err, stdout, stderr) { - console.log('Error: FFmpeg output:' + err.message); + logger.error('FFmpeg output:' + err.message); }); command.run(); @@ -68,7 +69,7 @@ HTTPFFMPEGProcessor.prototype.cleanup = function() fs.rename(this.tempFilename, this.filename, function(err) { - console.log('Error: could not move ' + this.tempFilename + ' to ' + this.filename + ': ' + err.message); + logger.error('Could not move ' + this.tempFilename + ' to ' + this.filename + ': ' + err.message); }); } diff --git a/processor-http-raw.js b/processor-http-raw.js index 05a6c21..1625946 100644 --- a/processor-http-raw.js +++ b/processor-http-raw.js @@ -2,6 +2,7 @@ var fs = require('fs'); var util = require('util'); var helpers = require('./helpers'); +var logger = require('./logger'); var BaseHTTPStreamProcessor = require('./basehttpstreamprocessor'); function HTTPRawProcessor() @@ -47,7 +48,7 @@ HTTPRawProcessor.prototype.cleanup = function() fs.rename(this.tempFilename, this.filename, function(err) { - console.log('Error: could not move ' + this.tempFilename + ' to ' + this.filename + ': ' + err.message); + logger.error('Could not move ' + this.tempFilename + ' to ' + this.filename + ': ' + err.message); }); }