webamp
Version:
Ampache web interface
238 lines (207 loc) • 6.33 kB
JavaScript
// Includes and constants
var http = require('http'),
fs = require('fs'),
path = require('path'),
open = require('open'),
url = require('url'),
util = require('util'),
router = new require('routes').Router(),
index_html = fs.readFileSync(
path.join(__dirname, 'site', 'index.html')
),
mount = require('st')({
/*
'cache': {
'content': {
'maxAge': 1,
'max': 1
},
'fd': {
'max': 1,
'maxAge': 1,
},
'stat': {
'max': 1,
'maxAge': 1,
}
},
*/
'path': path.join('site', 'static'),
'url': 'static/'
}),
AmpacheSession = require('ampache'),
conn,
conf,
cache_ready = false,
cache_dir = 'cache',
cache = {};
// make the routes
router.addRoute('/', index);
router.addRoute('/api/:type?/:filter?/:new?', api);
// Export the function to create the server
module.exports = function(config) {
conf = config;
cache_dir = path.join(conf.webamp_dir, cache_dir);
// Create the Ampache Object
conn = new AmpacheSession(conf.ampache.user, conf.ampache.pass,
conf.ampache.url, {debug: conf.ampache.debug || false});
// Authenticate to Ampache
conn.authenticate(function(err, body) {
if (err) {
console.error('Failed to authenticate!');
console.error('Username: %s', conf.ampache.user);
console.error('URL: %s', conf.ampache.url);
throw err;
}
console.log('Successfully Authenticated!');
populate_cache(body);
// Save the auth data for the update/add/clean times
fs.writeFile(path.join(cache_dir, 'update.json'), JSON.stringify(body), function(err) {
if (err) return console.error(err);
});
// Keep-Alive
setInterval(function() {
console.log('Keep Alive');
conn.ping(function(err, body) {
if (err) conn.authenticate(function (err, body) {
if (err) throw err;
});
});
}, +conf.ampache.ping || 10 * 60 * 1000);
});
// Create the server
return http.createServer(on_request).listen(conf.web.port, conf.web.host, function() {
console.log('Server running at http://%s:%d/', conf.web.host, conf.web.port);
});
};
// Request received
function on_request(req, res) {
// Log it
console.log('[%s] [%s] request received from %s for %s',
Date(), req.method, req.connection.remoteAddress, req.url);
// static hit
if (mount(req, res)) return;
// Extract the URL
var uri = url.parse(req.url),
normalized_path = path.normalize(uri.pathname),
route = router.match(normalized_path);
// Route not found
if (!route) {
res.statusCode = 404;
return res.end();
}
// Route it
return route.fn(req, res, route.params);
}
// Index route hit
function index(req, res, params) {
if (!cache_ready) return res.end('Cache\'s not ready');
res.end(index_html);
}
// API Route hit
function api(req, res, params) {
var type = params.type,
filter = params.filter;
if (params.new === 'new') {
// User is requesting new information
var func = (type === 'artists') ? AmpacheSession.prototype.get_artist
: (type === 'albums') ? AmpacheSession.prototype.get_album
: (type === 'songs') ? AmpacheSession.prototype.get_song
: function() { res.end('[]'); };
func.call(conn, filter, function(err, body) {
if (err) throw err;
res.end(JSON.stringify(body));
});
} else {
// User wants it from the cache
var data;
if (!type) {
data = ['artists', 'albums', 'songs'];
} else if (!cache[type]) {
data = [];
} else if (filter) {
data = cache[type][filter];
} else {
data = cache[type];
}
res.end(JSON.stringify(data));
}
}
// Populate the caches with data
function populate_cache(body) {
console.log('Populating cache');
var funcs = {
'artists': AmpacheSession.prototype.get_artists,
'albums': AmpacheSession.prototype.get_albums,
'songs': AmpacheSession.prototype.get_songs
},
to_get = {},
albums_by_artist = 0,
songs_by_album = 0;
if (cache_up_to_date(body)) {
['artists', 'albums', 'songs'].forEach(function(key) {
try {
cache[key] = require(path.join(cache_dir, key + '.json'));
console.log('Loaded %s from local cache', key);
try_to_process(key);
} catch (e) {
console.error('Failed to load %s from local cache', key);
to_get[key] = funcs[key];
}
});
} else {
to_get = funcs;
}
// Loop the caches to build from remote source
Object.keys(to_get).forEach(function(key) {
to_get[key].call(conn, function(err, body) {
if (err) throw err;
cache[key] = body;
// Save the cache
fs.writeFile(path.join(cache_dir, key + '.json'), JSON.stringify(body), function(err) {
if (err) return console.error(err);
});
console.log('Loaded %s from remote source', key);
try_to_process(key);
});
});
function try_to_process(key) {
if ((key === 'artists' || key === 'albums')
&& ++albums_by_artist >= 2) cache_x_by_y('albums', 'artist');
if ((key === 'albums' || key === 'songs')
&& ++songs_by_album >= 2) cache_x_by_y('songs', 'album');
if (songs_by_album >= 2 && albums_by_artist >=2)
caches_ready();
}
}
function cache_x_by_y(x, y) {
console.log('Calculating %s by %s', x, y);
var key = (x === 'albums') ? 'albums_by_artist' : 'songs_by_album';
cache[key] = {};
Object.keys(cache[x]).forEach(function(id) {
var _id = +cache[x][id][y]['@'].id;
cache[key][_id] = cache[key][_id] || [];
cache[key][_id].push(+id);
});
console.log('Finished %s by %s', x, y);
}
function caches_ready() {
cache_ready = true;
console.log('All caches ready');
open(util.format('http://%s:%d/', conf.web.host, conf.web.port));
}
function cache_up_to_date(body) {
var ok = true;
try {
var old_body = require(path.join(cache_dir, 'update.json'));
} catch (e) {
return false;
}
['add', 'update', 'clean'].forEach(function(key) {
if (body[key].toJSON() !== old_body[key]) {
console.warn('Cache not up-to-date - pulling from remote source');
ok = false;
}
});
return ok;
}