|
@@ -3,50 +3,120 @@
|
|
|
const async = require('async');
|
|
|
const request = require('request');
|
|
|
|
|
|
+const io = require('../io');
|
|
|
const db = require('../db');
|
|
|
const cache = require('../cache');
|
|
|
+const notifications = require('../notifications');
|
|
|
const utils = require('../utils');
|
|
|
|
|
|
+/**
|
|
|
+ * Loads a station into the cache, and sets up all the related logic
|
|
|
+ *
|
|
|
+ * @param {String} stationId - the id of the station
|
|
|
+ * @param {Function} cb - gets called when this function completes
|
|
|
+ */
|
|
|
+function initializeAndReturnStation (stationId, cb) {
|
|
|
+
|
|
|
+ async.waterfall([
|
|
|
+
|
|
|
+ // first check the cache for the station
|
|
|
+ (next) => cache.hget('stations', stationId, next),
|
|
|
+
|
|
|
+ // if the cached version exist
|
|
|
+ (station, next) => {
|
|
|
+ if (station) return next(true, station);
|
|
|
+ db.models.station.find({ id: stationId }, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ // if the station exists in the DB, add it to the cache
|
|
|
+ (station, next) => {
|
|
|
+ if (!station) return cb('Station by that id does not exist');
|
|
|
+ station = cache.schemas.station(station);
|
|
|
+ cache.hset('stations', station.id, station, (err) => next(err, station));
|
|
|
+ }
|
|
|
+
|
|
|
+ ], (err, station) => {
|
|
|
+
|
|
|
+ if (err && err !== true) return cb(err);
|
|
|
+
|
|
|
+ // get notified when the next song for this station should play, so that we can notify our sockets
|
|
|
+ let notification = notifications.subscribe(`stations.nextSong?id=${station.id}`, () => {
|
|
|
+ // get the station from the cache
|
|
|
+ cache.hget('stations', station.id, (err, station) => {
|
|
|
+ if (station) {
|
|
|
+ // notify all the sockets on this station to go to the next song
|
|
|
+ async.waterfall(io.sockets.clients().map((socket) => (next) => {
|
|
|
+ // fetch the sockets session
|
|
|
+ cache.hget('sessions', socket.sessionId, (err, session) => {
|
|
|
+ if (session.stationId == station.id) {
|
|
|
+ socket.emit('notification:stations.nextSong');
|
|
|
+ }
|
|
|
+ next();
|
|
|
+ });
|
|
|
+ }), (err) => {
|
|
|
+ // schedule a notification to be dispatched when the next song ends
|
|
|
+ notifications.schedule(`stations.nextSong?id=${station.id}`, 5000);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // the station doesn't exist anymore, unsubscribe from it
|
|
|
+ else {
|
|
|
+ notifications.remove(notification);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ cb(null, station);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
module.exports = {
|
|
|
|
|
|
+ /**
|
|
|
+ * Get a list of all the stations
|
|
|
+ *
|
|
|
+ * @param session
|
|
|
+ * @param cb
|
|
|
+ * @return {{ status: String, stations: Array }}
|
|
|
+ */
|
|
|
index: (session, cb) => {
|
|
|
- cb(cache.getAllRows('stations'));
|
|
|
- },
|
|
|
+ // TODO: the logic should be a bit more personalized to the users preferred genres
|
|
|
+ // and it should probably just a different cache table then 'stations'
|
|
|
+ cache.hgetall('stations', (err, stations) => {
|
|
|
|
|
|
- join: (session, stationId, cb) => {
|
|
|
-
|
|
|
- async.waterfall([
|
|
|
-
|
|
|
- // first check the cache for the station
|
|
|
- (next) => next(null, cache.findRow('stations', 'id', stationId)),
|
|
|
-
|
|
|
- // if the cached version exist use it, otherwise check the DB for it
|
|
|
- (station, next) => {
|
|
|
- if (station) return next(true, station);
|
|
|
- db.models.station.find({ id: stationId }, next);
|
|
|
- },
|
|
|
-
|
|
|
- // add the station from the DB to the cache, adding the temporary data
|
|
|
- (station, next) => {
|
|
|
- cache.addRow('stations', Object.assign(station, {
|
|
|
- skips: 0,
|
|
|
- userCount: 0,
|
|
|
- currentSongIndex: 0,
|
|
|
- paused: false
|
|
|
- }));
|
|
|
- next(null, station);
|
|
|
+ if (err && err !== true) {
|
|
|
+ return cb({
|
|
|
+ status: 'error',
|
|
|
+ message: 'An error occurred while obtaining the stations'
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- ], (err, station) => {
|
|
|
+ cb({ status: 'success', stations });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Joins the station by its id
|
|
|
+ *
|
|
|
+ * @param session
|
|
|
+ * @param stationId - the station join
|
|
|
+ * @param cb
|
|
|
+ * @return {{ status: String, userCount: Integer }}
|
|
|
+ */
|
|
|
+ join: (session, stationId, cb) => {
|
|
|
+ initializeAndReturnStation(stationId, (err, station) => {
|
|
|
|
|
|
if (err && err !== true) {
|
|
|
return cb({ status: 'error', message: 'An error occurred while joining the station' });
|
|
|
}
|
|
|
|
|
|
if (station) {
|
|
|
+
|
|
|
if (session) session.stationId = stationId;
|
|
|
- station.userCount++;
|
|
|
- cb({ status: 'success', userCount: station.userCount });
|
|
|
+
|
|
|
+ cache.client.hincrby('station.userCounts', stationId, 1, (err, userCount) => {
|
|
|
+ if (err) return cb({ status: 'error', message: 'An error occurred while joining the station' });
|
|
|
+ cb({ status: 'success', userCount });
|
|
|
+ });
|
|
|
}
|
|
|
else {
|
|
|
cb({ status: 'failure', message: `That station doesn't exist` });
|
|
@@ -54,44 +124,34 @@ module.exports = {
|
|
|
});
|
|
|
},
|
|
|
|
|
|
+ /**
|
|
|
+ * Skips the users current station
|
|
|
+ *
|
|
|
+ * @param session
|
|
|
+ * @param cb
|
|
|
+ * @return {{ status: String, skipCount: Integer }}
|
|
|
+ */
|
|
|
skip: (session, cb) => {
|
|
|
|
|
|
if (!session) return cb({ status: 'failure', message: 'You must be logged in to skip a song!' });
|
|
|
|
|
|
- async.waterfall([
|
|
|
-
|
|
|
- // first check the cache for the station
|
|
|
- (next) => next(null, cache.findRow('stations', 'id', session.stationId)),
|
|
|
-
|
|
|
- // if the cached version exist use it, otherwise check the DB for it
|
|
|
- (station, next) => {
|
|
|
- if (station) return next(true, station);
|
|
|
- db.models.station.find({ id: session.stationId }, next);
|
|
|
- },
|
|
|
-
|
|
|
- // add the station from the DB to the cache, adding the temporary data
|
|
|
- (station, next) => {
|
|
|
- cache.addRow('stations', Object.assign(station, {
|
|
|
- skips: 0,
|
|
|
- userCount: 0,
|
|
|
- currentSongIndex: 0,
|
|
|
- paused: false
|
|
|
- }));
|
|
|
- next(null, station);
|
|
|
- }
|
|
|
-
|
|
|
- ], (err, station) => {
|
|
|
+ initializeAndReturnStation(stationId, (err, station) => {
|
|
|
|
|
|
if (err && err !== true) {
|
|
|
return cb({ status: 'error', message: 'An error occurred while skipping the station' });
|
|
|
}
|
|
|
|
|
|
if (station) {
|
|
|
- station.skips++;
|
|
|
- if (station.skips > Math.floor(station.userCount / 2)) {
|
|
|
- // TODO: skip to the next song if the skips is higher then half the total users
|
|
|
- }
|
|
|
- cb({ status: 'success', skips: station.skips });
|
|
|
+ cache.client.hincrby('station.skipCounts', session.stationId, 1, (err, skipCount) => {
|
|
|
+
|
|
|
+ session.skippedSong = true;
|
|
|
+
|
|
|
+ if (err) return cb({ status: 'error', message: 'An error occurred while skipping the station' });
|
|
|
+
|
|
|
+ cache.hget('station.userCounts', session.stationId, (err, userCount) => {
|
|
|
+ cb({ status: 'success', skipCount });
|
|
|
+ });
|
|
|
+ });
|
|
|
}
|
|
|
else {
|
|
|
cb({ status: 'failure', message: `That station doesn't exist` });
|
|
@@ -99,49 +159,30 @@ module.exports = {
|
|
|
});
|
|
|
},
|
|
|
|
|
|
- // leaves the users current station
|
|
|
- // returns the count of users that are still in that station
|
|
|
+ /**
|
|
|
+ * Leaves the users current station
|
|
|
+ *
|
|
|
+ * @param session
|
|
|
+ * @param cb
|
|
|
+ * @return {{ status: String, userCount: Integer }}
|
|
|
+ */
|
|
|
leave: (session, cb) => {
|
|
|
-
|
|
|
- // if they're not logged in, we don't need to do anything below
|
|
|
- if (!session) return cb({ status: 'success' });
|
|
|
-
|
|
|
- async.waterfall([
|
|
|
-
|
|
|
- // first check the cache for the station
|
|
|
- (next) => next(null, cache.findRow('stations', 'id', session.stationId)),
|
|
|
-
|
|
|
- // if the cached version exist use it, otherwise check the DB for it
|
|
|
- (station, next) => {
|
|
|
- if (station) return next(true, station);
|
|
|
- db.models.station.find({ id: session.stationId }, next);
|
|
|
- },
|
|
|
-
|
|
|
- // add the station from the DB to the cache, adding the temporary data
|
|
|
- (station, next) => {
|
|
|
- cache.addRow('stations', Object.assign(station, {
|
|
|
- skips: 0,
|
|
|
- userCount: 0,
|
|
|
- currentSongIndex: 0,
|
|
|
- paused: false
|
|
|
- }));
|
|
|
- next(null, station);
|
|
|
- }
|
|
|
-
|
|
|
- ], (err, station) => {
|
|
|
+ initializeAndReturnStation(stationId, (err, station) => {
|
|
|
|
|
|
if (err && err !== true) {
|
|
|
return cb({ status: 'error', message: 'An error occurred while leaving the station' });
|
|
|
}
|
|
|
|
|
|
- session.stationId = null;
|
|
|
+ if (session) session.stationId = null;
|
|
|
|
|
|
if (station) {
|
|
|
- station.userCount--;
|
|
|
- cb({ status: 'success', userCount: station.userCount });
|
|
|
+ cache.client.hincrby('station.userCounts', stationId, -1, (err, userCount) => {
|
|
|
+ if (err) return cb({ status: 'error', message: 'An error occurred while leaving the station' });
|
|
|
+ cb({ status: 'success', userCount });
|
|
|
+ });
|
|
|
}
|
|
|
else {
|
|
|
- cb({ status: 'failure', message: `That station doesn't exist` });
|
|
|
+ cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
|
|
|
}
|
|
|
});
|
|
|
},
|