Skip to content

Commit

Permalink
Big refactor with better dependency model
Browse files Browse the repository at this point in the history
Modules don't know about each others and are orchestrated by app.js main
application file
  • Loading branch information
Aquassaut committed Dec 13, 2014
1 parent d9a4a51 commit 23a6bc5
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 100 deletions.
102 changes: 20 additions & 82 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,94 +1,32 @@
var http = require('http'),
fs = require('fs'),
watch = require('node-watch'),
path = require('path'),
util = require('util'),
log = require('npmlog'),
url = require('url'),
cfg = require('./config.json'),
streamDirectoryContent = require('./stream.js'),
generateFeed = require('./feed.js');
var log = require('npmlog'),
cfg = require('./lib/config.json'),
server = require('./lib/server.js'),
watcher = require('./lib/watcher.js'),
feed = require('./lib/feed.js');

/**
* TODO:
* Use more async calls with promises.
* Prevent watcher to get crazy when copying a big file over
*/

//Fix music folder setting so we accept string, but work with array
if (! util.isArray(cfg.music_folder)) {
cfg.music_folder = [ cfg.music_folder ];
}
var xml = "generating xml...";

/* Server part */
http.createServer(function (req, res) {
res.on('sendStarted', function() {
log.info(
'server', '[%s] %s %s (%d)',
req.connection.remoteAddress, req.method, req.url, res.statusCode
);
var folders = cfg.music_folder || ".";
var port = cfg.port || 3333;
var feedinfo = cfg.feed || {
"title": "Feed Title",
"description": "A great feed",
"feed_url": "127.0.0.1",
"site_url": "127.0.0.1"
};
var url = feedinfo.feed_url || "127.0.0.1";

var webserver = server.startServer(xml, folders, port, url);
watcher.startWatching(folders, function() {
feed.generate(folders, port, feedinfo, function(err, xml) {
if (err) return log.error(err);
webserver.reloadXml(xml);
});
if (req.url === '/') {
res.writeHead(200, {'Content-Type': 'text/xml; charset=UTF-8'});
res.end(xml.replace(/@@HOST@@/g, (req.headers.host || cfg.site_url)));
res.emit('sendStarted');
} else {
var queried = url.parse(decodeURI(req.url).replace(/\.mp3$/g, '')).path.slice(1);
var found = false;
cfg.music_folder.forEach(function(folder, idx, ary) {
if (found) return;
log.info('matcher', 'Trying to find %s in %s', queried, folder);
var isLastFolder = (idx === ary.length - 1);
var directory = path.resolve(folder, queried);
if (fs.existsSync(directory)) {
found = true;
streamDirectoryContent(directory, res);
} else if (isLastFolder) {
res.writeHead(404);
res.end();
res.emit('sendStarted');
}
});
}
}).listen(cfg.port, function() {
log.info('server', 'Server listening on port %d', cfg.port);
});

/* Directory watcher part */
var regenerator = (function() {
var _generate = function() {
if (buildlater) {
buildlater = false;
build.close();
build = setTimeout(_generate, timeout);
return;
}
log.info("feed generation", "generating feed");
generateFeed().then(function(output) { xml = output; });
buildlater = false;
timeout = 5000;
}

var build = setTimeout(_generate, 1);
var timeout = 5000;
var buildlater = false;

return function(filename) {
if (buildlater) {
return;
} else if (build._idleNext !== null) {
//build already queued
log.info('watcher', 'Quick changes detected in filesystem, waiting before taking action');
timeout *= 2;
buildlater = true;
} else {
log.info('watcher', '%s changed on disk, queuing RSS regeneration', filename);
}
build.close();
build = setTimeout(_generate, timeout);
};
})();

watch(cfg.music_folder, { followSymLinks:true, maxSymLevel:10 }, regenerator);

43 changes: 25 additions & 18 deletions feed.js → lib/feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,62 @@ var rss = require('rss'),
url = require('url'),
log = require('npmlog'),
Q = require('q'),
path = require('path'),
cfg = require('./config.json');
path = require('path');


function getStatsFromFile(file) {
log.silly('feed', 'getting stats from file %s', file);
var stat = Q.denodeify(fs.stat);
return stat(file).then(function(s) {
s.name = path.basename(file);
return s;
});
}
function getFilesFromDir(wd) {
log.silly('feed', 'getting files from dir %s', wd);
var readdir = Q.denodeify(fs.readdir);
return readdir(wd).then(function(content) {
return content.map(function(file) {
return path.join(wd, file);
});
});
}
function getStatsFromDirs(wds) {
return Q.all(wds.map(function(elem) {
function getStatsFromDirs(folders) {
log.silly('feed', 'getting all stats in dirs %j', folders);
return Q.all(folders.map(function(elem) {
return getFilesFromDir(elem).then(function(files) {
return Q.all(files.map(getStatsFromFile));
});
}));
}
function discover() {
var deferred = Q.defer();

log.info('resource discovery', 'Starting discovery');
var generate = function(folders, port, feedinfo, callback) {
log.info('feed', 'Starting discovery');

if (typeof folders === 'string') folders = [folders];

var wds = cfg.music_folder;
var baseurl = "http://@@HOST@@" + (cfg.port !== 80 ? "" : (":" + cfg.port));
var baseurl = "http://@@HOST@@" + (port !== 80 ? "" : (":" + port));

getStatsFromDirs(wds).then(function(nestedArrays) {
getStatsFromDirs(folders).then(function(nestedArrays) {
log.info('feed', 'Un-nesting array');
return nestedArrays.reduce(function(a, b) {
return a.concat(b);
});
}).then(function(dirs) {
log.silly('feed', 'Filtering out non directories');
return dirs.filter(function(file) {
return file.isDirectory();
});
}).then(function(dirs) {
log.silly('feed', 'sorting dirs by mtime');
var nb = 0;
return dirs.sort(function(d1, d2) {
log.silly('feed', '%d comparision', ++nb );
return d2.mtime.getTime() - d1.mtime.getTime();
});
}).then(function(dirs) {
var feed = new rss(cfg.feed);
log.info('resource discovery', 'Found %d dirs in music dirs', dirs.length);
log.info('feed', 'Found %d dirs in music dirs', dirs.length);
var feed = new rss(feedinfo);

for (var i = 0; i < dirs.length; i += 1) {
var dir = dirs[i];
Expand All @@ -66,13 +73,13 @@ function discover() {
enclosure: {
url: url
}
}
log.info('resource discovery', 'adding item to feed: %s', item.title);
};
log.info('feed', 'adding item to feed: %s', item.title);
feed.item(item);
}
deferred.resolve(feed.xml());
log.info('feed', 'Discovery over, calling back');
callback(null, feed.xml());
});
return deferred.promise;
}
};

module.exports = discover;
exports.generate = generate;
57 changes: 57 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
var http = require('http'),
fs = require('fs'),
path = require('path'),
log = require('npmlog'),
streamDirectoryContent = require('./stream.js');

var _getResourceFromPath = function(path) {
return decodeURIComponent(path) //cleaning
.replace(/\.mp3$/g, '') //Triming extension
.replace(/^\//g, ''); //removing leading slash
};

var _findResourceFullPath = function(dirs, resource) {
if (typeof dirs === 'string') dirs = [dirs];
for (var i = 0; i < dirs.length; i+= 1) {
var dir = dirs[i];
log.info('server', 'Trying to find %s in %s', resource, dir);
var fullpath = path.resolve(dir, resource);
if (fs.existsSync(fullpath)) return fullpath;
}
return null;
};

var startServer = function(xml, folders, port, url) {
var server = http.createServer(function (req, res) {
res.on('sendStarted', function() {
log.info(
'server', '[%s] %s %s (%d)',
req.connection.remoteAddress, req.method, req.url, res.statusCode
);
});
if (req.url === '/') {
res.writeHead(200, {'Content-Type': 'text/xml; charset=UTF-8'});
res.end(xml.replace(/@@HOST@@/g, (req.headers.host || url)));
return res.emit('sendStarted');
} else {
var resource = _getResourceFromPath(req.url);
var fullpath = _findResourceFullPath(folders, resource);

if (fullpath === null) {
res.writeHead(404);
res.end();
return res.emit('sendStarted');
}
return streamDirectoryContent(fullpath, res);
}
}).listen(port, function() {
log.info('server', 'Server listening on port %d', port);
});
server.reloadXml = function(new_xml) {
log.info('server', 'Received new XML');
xml = new_xml;
};
return server;
};

exports.startServer = startServer;
File renamed without changes.
42 changes: 42 additions & 0 deletions lib/watcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var watch = require('node-watch'),
log = require('npmlog');
var regenerator = function(cb) {
var _generate = function() {
if (buildlater) {
buildlater = false;
build.close();
build = setTimeout(_generate, timeout);
return;
}
buildlater = false;
timeout = 5000;
log.info("watcher", "building");
cb();
};

var build = setTimeout(_generate, 1);
var timeout = 5000;
var buildlater = false;

return function(filename) {
if (buildlater) {
return;
} else if (build._idleNext !== null) {
//build already queued
log.info('watcher', 'Quick changes detected in filesystem, waiting before taking action');
timeout *= 2;
buildlater = true;
} else {
log.info('watcher', '%s changed on disk, queuing RSS regeneration', filename);
}
build.close();
build = setTimeout(_generate, timeout);
};
};

var startWatching = function(folders, cb) {
var opts = { followSymLinks:true, maxSymLevel:10 };
watch(folders, opts, regenerator(cb));
};

exports.startWatching = startWatching;

0 comments on commit 23a6bc5

Please sign in to comment.