Commit 67f78c24 authored by Andreas Mieke's avatar Andreas Mieke 🚂

Initial commit

parents
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Development config file
config/config.development.json
\ No newline at end of file
var express = require('express'),
query = require('pg-query'),
swig = require('swig'),
morgan = require('morgan'),
cache_manager = require('cache-manager'),
bodyParser = require('body-parser'),
qs = require('qs'),
conf = require('konphyg')(__dirname + '/config');
var cache = cache_manager.caching({store: "memory", max: 100, ttl: 10});
var app = express();
var config = conf('config');
/* Controllers */
var index = require('./controllers/index.js')(query, cache);
var genres = require('./controllers/genres.js')(query, cache);
var yp_cgi = require('./controllers/yp-cgi.js')(query, qs);
query.connectionParameters = config.db;
swig.setDefaults({ cache: (process.env.NODE_ENV === 'production') ? true : false });
/* Middlewares */
app.use(morgan((process.env.NODE_ENV === 'production') ? 'short' : 'dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/assets', express.static(__dirname + '/assets'));
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
/* Routes */
app.get('/', index);
app.get('/by_genre/:genre', genres);
app.post('/cgi-bin/yp-cgi', yp_cgi);
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
process.on('SIGINT', function() {
server.close();
process.exit();
});
var interval = setInterval(deleteOldServers, 240000, 4);
function deleteOldServers(time, cb) {
query('DELETE FROM servers WHERE lasttouch < NOW() - INTERVAL \'' + time.toString() + ' minutes\';', cb);
}
app.get('/view/', function(req, res) {
//console.log("[GET " + req.originalUrl + "]:")
res.set('Content-Type', 'application/json')
if(!req.param('genre')) {
query('SELECT array_to_json(array_agg(row_to_json(servers))) FROM servers;', function(err, rows, result) {
//console.log(err)
res.send(rows[0].array_to_json)
});
} else {
query('SELECT array_to_json(array_agg(row_to_json(servers))) FROM servers WHERE $1 = ANY (genres);',
[req.param('genre')],
function(err, rows, result) {
//console.log(err)
res.send(rows[0].array_to_json)
});
}
});
app.get('/insert/', function(req, res) {
//console.log("[GET " + req.originalUrl + "]:")
query('INSERT INTO servers VALUES (\
uuid_generate_v4(),\
$1,\
$2,\
$3,\
$4,\
$5,\
$6,\
$7,\
$8,\
$9,\
now()\
);', [req.param('sn'), req.param('type'), [req.param('genres')], req.param('b'), req.param('listenurl'),
req.param('cpswd'), req.param('desc'), req.param('url'), [req.param('stype')]], function(err, rows, result) {
//console.log(err)
res.send(err)
});
});
app.get('/genres/', function(req, res) {
var genresq = 'SELECT DISTINCT val FROM (SELECT unnest(genres) as val FROM servers) s;';
query(genresq, function(err, rows, result) {
res.send(rows);
});
});
\ No newline at end of file
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
/* Adaptations on the Xiph.org default CSS */
#content {
padding-left: 3em;
}
#navbar {
width: 150px;
}
#navbar h3 {
font-size: 0.9em;
}
#content {
margin-left: 160px;
}
h2 {
font-size: 1.5em;
}
#thepage {
width: 90%;
}
/* Top logo */
#xiphlogo {
background: transparent url('/assets/images/logo-dirxiphorg.png') no-repeat scroll top left;
}
#xiphlogo h1 a {
display: block;
text-indent: -9999px;
height: 75px;
width: 514px;
}
/* Search box */
#sidebar-search label {
display: none;
}
#search {
width: 100px;
}
/* Tag cloud */
.tag-cloud li {
display: inline;
}
.tag-cloud .context {
position: absolute;
left: -999px;
width: 990px;
}
.tag-cloud .popularity-0 { font-size: 0.7em !important; }
.tag-cloud .popularity-1 { font-size: 0.9em !important; }
.tag-cloud .popularity-2 { font-size: 1.1em !important; }
.tag-cloud .popularity-3 { font-size: 1.3em !important; }
.tag-cloud .popularity-4 { font-size: 1.4em !important; }
.tag-cloud .popularity-5 { font-size: 1.5em !important; }
.tag-cloud .popularity-2 a, .tag-cloud .popularity-3 a,
.tag-cloud .popularity-4 a, .tag-cloud .popularity-5 a {
font-weight: normal !important;
}
/* Servers list */
table.servers-list {
list-style-type: none;
margin: 0;
padding: 0;
width: 100%;
}
table.servers-list tr {
border-bottom: 1px dashed #F33;
margin-bottom: 0.3em;
min-height: 80px;
}
table.servers-list tr p {
margin: 0;
padding: 0;
}
table.servers-list tr td {
border-bottom: 1px solid #9FC0D7;
}
table.servers-list td.rank {
font-family: Palatino, Palatino Linotype, Georgia, Times New Roman, serif;
font-weight: bold;
font-style: italic;
color: #999;
font-size: 3em;
padding-right: 0.3em;
text-align: right;
vertical-align: top;
}
table.servers-list td.description {
}
table.servers-list span.name {
font-size: 1.3em;
font-weight: bold;
}
table.servers-list span.listeners {
font-variant: small-caps;
}
table.servers-list p.stream-description {
margin: 0.5em 0 0.4em;
}
table.servers-list p.stream-onair {
font-style: italic;
font-size: 0.9em;
}
table.servers-list p.stream-onair strong {
font-weight: bold;
font-style: normal;
}
table.servers-list div.stream-tags {
font-size: 0.9em;
}
table.servers-list div.stream-tags ul {
display: inline;
}
table.servers-list td.tune-in {
width: 20%;
text-align: center;
vertical-align: top;
}
table.servers-list td.tune-in a.tune-in-button {
border: 1px solid #C33;
background-color: #F33;
color: #FFF;
font-weight: bold;
padding: 0.5em;
display: block;
text-align: center;
margin: 1em 0.5em 0 0.5em;
}
table.servers-list td.tune-in a.tune-in-button:hover {
background-color: #FEE;
color: #000;
}
table.servers-list td.tune-in a.tune-in-button img {
margin-bottom: -0.3em;
}
table.servers-list td.tune-in p.format {
font-size: 0.9em;
}
table.servers-list td.tune-in p.format span.stream {
position: absolute;
left: -999px;
width: 990px;
}
.no-link {
color: #333;
text-decoration: none;
}
/* Tag list */
.inline-tags {
list-style-type: none;
padding: 0;
margin: 0;
}
.inline-tags li {
display: inline;
}
/* Pager */
.pager {
list-style-type: none;
padding: 0;
margin: 0;
}
.pager li {
display: inline;
}
.pager li a.active {
font-weight: bold;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"db" : "postgres://user:passwd@host:port/dbname"
}
\ No newline at end of file
var query, cache;
function init(q, c) {
query = q;
cache = c;
return byGenre;
}
function byGenre(req, res) {
getCachedGenreStreams(req.param("genre"), function(err, result) {
if (err) {
res.send(503);
} else {
var error;
if (result.rowCount === 0) {
error = "No streams for this genre.";
} else {
error = false;
}
res.render("by_genre", {
title: req.param("genre"),
servers: result.rows,
error: error
});
}
});
}
function getCachedGenreStreams(genre, cb) {
cache.wrap(genre, function (_cb) {
getGenreStreams(genre, _cb);
}, 5, cb);
}
function getGenreStreams(genre, cb) {
query("SELECT id, server_name, server_type, genres, bitrate, listenurl, description, url, \
codec_sub_types, songname, listeners FROM servers WHERE $1 = ANY (genres);", [genre],
function(err, rows, result) {
cb(err, result);
});
}
module.exports = init;
\ No newline at end of file
var query, cache;
function init(q, c) {
query = q;
cache = c;
return index;
}
function index(req, res) {
getCachedRandomStreams(20, function(err, result) {
if (err) {
res.send(503);
} else {
var error;
if (result.rowCount === 0) {
error = "No streams found.";
} else {
error = false;
}
res.render("index", {
title: '',
servers: result.rows,
error: error
});
}
});
}
function getCachedRandomStreams(count, cb) {
cache.wrap(count, function (_cb) {
getRandomStreams(count, _cb);
}, 5, cb);
}
function getRandomStreams(count, cb) {
query('SELECT id, server_name, server_type, genres, bitrate, listenurl, description, url, \
codec_sub_types, songname, listeners FROM servers ORDER BY random() LIMIT $1;', [count],
function(err, rows, result) {
cb(err, result);
});
}
module.exports = init;
\ No newline at end of file
var query, qs;
function init(q, q_) {
query = q;
qs = q_;
return dispatcher;
}
function dispatcher(req, res) {
if(req.body.action == "add") {
ypAdd(req, res);
} else if(req.body.action == "touch") {
ypTouch(req, res);
} else if (req.body.action == "remove") {
ypRemove(req, res);
}
}
function ypAdd(req, res) {
if (req.body.listenurl.indexOf("http://localhost") != -1 ||
req.body.listenurl.indexOf("https://localhost") != -1) {
ypRes(res, false, "Your hostname is localhost, this is an indicator of a misconfigured server", -1, null);
return;
}
if (req.body.sn === 'Unspecified name') {
ypRes(res, false, "You have to specify a name for your stream.", -1, null);
return;
}
var final = parseBody(req.body);
var insert = 'INSERT INTO servers (id, lasttouch, server_name, server_type, genres, bitrate, listenurl, \
cluster_pass, description, url, codec_sub_types, channels, samplerate, quality) \
VALUES (uuid_generate_v4(), now(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) \
RETURNING id;'
query(insert, [final.server_name, final.server_type, final.genres, final.bitrate,
final.listenurl, final.cluster_pass, final.description, final.url,
final.codec_sub_types, final.channels, final.samplerate, final.quality],
function(err, rows, result) {
console.log(err);
if (err) {
ypRes(res, false, "Error adding the stream", -1, null);
} else {
ypRes(res, true, "Successfully Added", rows[0].id, 200);
};
});
}
function ypTouch(req, res) {
var final = parseBody(req.body);
var touch = "UPDATE servers SET lasttouch = now(), songname = $2, listeners = $3, max_listeners = $4, codec_sub_types = $5 WHERE id = $1;";
query(touch, [final.id, final.songname, final.listeners, final.max_listeners, final.codec_sub_types], function(err, row, result) {
if (err || result.rowCount === 0) {
ypRes(res, false, "Could not find database entry", null, null);
} else {
ypRes(res, true, "Scucessfully touched", null, null);
}
});
}
function ypRemove(req, res) {
var final = parseBody(req.body);
var remove = "DELETE FROM servers WHERE id = $1;";
query(remove, [final.id], function(err, row, result) {
if (err) {
ypRes(res, false, "Could not find database entry", null, null);
} else {
ypRes(res, true, "Scucessfully removed", null, null);
}
});
}
function ypRes(res, success, message, sid, tfreq) {
res.set("YPResponse", (success === true) ? "1" : "0");
if (message !== null) {
res.set("YPMessage", message);
}
if (sid !== null) {
res.set("SID", sid);
}
if (tfreq !== null) {
res.set("TouchFreq", tfreq.toString());
}
res.send();
}
function parseBody(body) {
var audio_info, final = {};
/* Parse audio_info */
/* Parse broken body */
Object.keys(body).forEach(function(key) {
if (~key.indexOf('\r\n')) {
key = key.substring(0, key.indexOf('\r\n'));
audio_info = qs.parse(key, {delimiter: ';'});
}
});
if (body.stype) {
body.stype = body.stype.substring(0, body.stype.indexOf('\r\n'));
}
/* End of hacky part */
/* Validate bitrate */
if (body.b && !isNaN(body.b)) {
final.bitrate = body.b;
} else if (audio_info) {
if (audio_info['ice-bitrate']) audio_info.bitrate = audio_info['ice-bitrate'];
if (audio_info.bitrate) {
if (!isNaN(audio_info.bitrate)) {
final.bitrate = audio_info.bitrate;
}
}
}
/* Validate Samplerate */
if (audio_info) {
if (audio_info['ice-samplerate']) audio_info.samplerate = audio_info['ice-samplerate'];
if (audio_info.samplerate) {
if (!isNaN(audio_info.samplerate)) {
final.samplerate = audio_info.samplerate;
}
}
}
/* Validate Channels */
if (audio_info) {
if (audio_info['ice-channels']) audio_info.channels = audio_info['ice-channels'];
if (audio_info.channels) {
if (!isNaN(audio_info.channels)) {
final.channels = audio_info.channels;
}
}
}
/* Validate Quality */
if (audio_info) {
if (audio_info['ice-quality']) audio_info.quality = audio_info['ice-quality'];
if (audio_info.quality) {
if (!isNaN(audio_info.quality)) {
final.quality = audio_info.quality;
}
}
}
/* End of audio_info */
/* Parse Icecast params */
if (body.sid) {
final.id = body.sid;
}
if (body.sn) {
final.server_name = body.sn;
}
if (body.type) {
final.server_type = body.type;
}
if (body.genre) {
final.genres = body.genre.split(' ');
}
if (body.listenurl) {
final.listenurl = body.listenurl;
}
if (body.cpswd) {
final.cluster_pass = body.cpswd;
}
if (body.desc) {
final.description = body.desc;
}
if (body.url) {
final.url = body.url;
}
if (body.stype) {
final.codec_sub_types = body.stype.split(' ');
}
if (body.st) {
final.songname = body.st;
}
if (body.listeners && !isNaN(body.listeners)) {
final.listeners = body.listeners;
}
if (body.max_listeners && !isNaN(body.max_listeners)) {
final.max_listeners = body.max_listeners;
}