From a41541d07b336850f68e23be3bda512579c5492f Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Tue, 11 Feb 2025 07:39:20 -0500 Subject: [PATCH] Added persistent rescheduling of nowPlaying after server goes down. --- src/app/channel/activeChannel.js | 3 +- src/app/channel/media/queue.js | 51 +++++++++++++++++-- src/app/channel/media/queuedMedia.js | 15 ++++-- src/schemas/channel/channelSchema.js | 6 +++ src/schemas/channel/media/mediaSchema.js | 6 ++- .../channel/media/queuedMediaSchema.js | 30 ++++++++++- src/utils/loggerUtils.js | 14 ++--- src/views/partial/popup/changePassword.ejs | 8 +-- www/js/channel/panels/queuePanel.js | 12 ++++- www/js/profile.js | 4 +- 10 files changed, 124 insertions(+), 25 deletions(-) diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 7eb1091..62f03f4 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -19,6 +19,7 @@ const connectedUser = require('./connectedUser'); const queue = require('./media/queue'); const flairModel = require('../../schemas/flairSchema'); const permissionModel = require('../../schemas/permissionSchema'); +const channelModel = require('../../schemas/channel/channelSchema'); module.exports = class{ constructor(server, chanDB){ @@ -27,7 +28,7 @@ module.exports = class{ this.tokeCommands = chanDB.tokeCommands; //Keeping these in a map was originally a vestige but it's more preformant than an array or object so :P this.userList = new Map(); - this.queue = new queue(server, this); + this.queue = new queue(server, chanDB, this); } async handleConnection(userDB, chanDB, socket){ diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 1b14147..fcc2bb1 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -24,7 +24,7 @@ const loggerUtils = require('../../../utils/loggerUtils'); const channelModel = require('../../../schemas/channel/channelSchema'); module.exports = class{ - constructor(server, channel){ + constructor(server, chanDB, channel){ //Set server this.server = server //Set channel @@ -47,6 +47,9 @@ module.exports = class{ //create boolean to hold schedule lock this.locked = false; + + //Rehydrate channel queue from database + this.rehydrateQueue(chanDB); } defineListeners(socket){ @@ -441,8 +444,8 @@ module.exports = class{ return false; } - //If the item has already started and it's not being forced - if((mediaObj.startTime < new Date().getTime())){ + //If the item has already started + if((mediaObj.startTime < new Date().getTime()) && !force){ //Set time stamp to existing timestamp plus the difference between the orginal start-date and now mediaObj.startTimeStamp = mediaObj.startTimeStamp + ((new Date().getTime() - mediaObj.startTime) / 1000) //Start the item now @@ -493,7 +496,7 @@ module.exports = class{ return mediaObj; } - start(mediaObj, timestamp = mediaObj.startTimeStamp){ + async start(mediaObj, timestamp = mediaObj.startTimeStamp){ //Silently end the media this.end(true); @@ -503,6 +506,19 @@ module.exports = class{ //Set current playing media this.nowPlaying = mediaObj; + try{ + //Get our channel + const chanDB = await channelModel.findOne({name: this.channel.name}); + + //Set the now playing queued media document + chanDB.media.nowPlaying = mediaObj; + + //Save the channel + await chanDB.save(); + }catch(err){ + loggerUtils.localExceptionHandler(err); + } + //Send play signal out to the channel this.sendMedia(); @@ -655,4 +671,31 @@ module.exports = class{ broadcastQueue(){ this.server.io.in(this.channel.name).emit('queue',{queue: Array.from(this.schedule)}) } + + async rehydrateQueue(chanDB){ + try{ + //If we didn't get handed a freebie + if(chanDB == null){ + //Go out and get it done ourselves + chanDB = await channelModel.findOne({name:this.channel.name}); + } + + //If we couldn't find the channel + if(chanDB == null){ + //FUCK + throw new Error(`Unable to find channel document ${this.channel.name} while rehydrating queue!`); + } + + //Rehydrate the currently playing item + const wasPlaying = chanDB.media.nowPlaying.rehydrate(); + + //Schedule it + this.scheduleMedia(wasPlaying, null, true); + + //if something fucked up + }catch(err){ + //bitch about it in the server console + loggerUtils.localExceptionHandler(err); + } + } } \ No newline at end of file diff --git a/src/app/channel/media/queuedMedia.js b/src/app/channel/media/queuedMedia.js index 99b6a5a..7cc2994 100644 --- a/src/app/channel/media/queuedMedia.js +++ b/src/app/channel/media/queuedMedia.js @@ -18,7 +18,7 @@ along with this program. If not, see .*/ const media = require('./media'); module.exports = class extends media{ - constructor(title, fileName, url, id, type, duration, startTime, startTimeStamp){ + constructor(title, fileName, url, id, type, duration, startTime, startTimeStamp, earlyEnd, uuid){ //Call derived constructor super(title, fileName, url, id, type, duration); //Set media start time @@ -27,10 +27,17 @@ module.exports = class extends media{ this.startTimeStamp = startTimeStamp; //Create empty variable to hold early end if media is stopped early this.earlyEnd = null; + //Set status for discriminator key + this.status = 'queued'; - //Generate id unique to this specific entry of this specific file within this specific channel's queue - //That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance - this.genUUID(); + //If we have a null uuid (can't use default argument because of 'this') + if(uuid == null){ + //Generate id unique to this specific entry of this specific file within this specific channel's queue + //That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance + this.genUUID(); + }else{ + this.uuid = uuid; + } } //statics diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js index dbac81b..0ae5b7a 100644 --- a/src/schemas/channel/channelSchema.js +++ b/src/schemas/channel/channelSchema.js @@ -29,6 +29,7 @@ const emoteModel = require('../emoteSchema'); //DB Schemas const channelPermissionSchema = require('./channelPermissionSchema'); const channelBanSchema = require('./channelBanSchema'); +const queuedMediaSchema = require('./media/queuedMediaSchema'); //Utils const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils'); @@ -98,6 +99,11 @@ const channelSchema = new mongoose.Schema({ default: emoteModel.typeEnum[0] } }], + media: { + nowPlaying: queuedMediaSchema, + scheduled: [queuedMediaSchema], + archived: [queuedMediaSchema], + }, //Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a lot easier than site-wide bans :P banList: [channelBanSchema] }); diff --git a/src/schemas/channel/media/mediaSchema.js b/src/schemas/channel/media/mediaSchema.js index 7454dc3..c1d1f4d 100644 --- a/src/schemas/channel/media/mediaSchema.js +++ b/src/schemas/channel/media/mediaSchema.js @@ -42,6 +42,10 @@ const mediaSchema = new mongoose.Schema({ type: mongoose.SchemaTypes.Number, required: true, }, -}); +}, +{ + discriminatorKey: 'status' +} +); module.exports = mediaSchema; \ No newline at end of file diff --git a/src/schemas/channel/media/queuedMediaSchema.js b/src/schemas/channel/media/queuedMediaSchema.js index fbf0a3e..eef12d5 100644 --- a/src/schemas/channel/media/queuedMediaSchema.js +++ b/src/schemas/channel/media/queuedMediaSchema.js @@ -19,16 +19,44 @@ const {mongoose} = require('mongoose'); //Local Imports const mediaSchema = require('./mediaSchema'); +const queuedMedia = require('../../../app/channel/media/queuedMedia'); const queuedProperties = new mongoose.Schema({ startTime: { type: mongoose.SchemaTypes.Number, required: true, }, + startTimeStamp: { + type: mongoose.SchemaTypes.Number, + required: false, + }, + earlyEnd: { + type: mongoose.SchemaTypes.Number, + required: false, + }, uuid: { type: mongoose.SchemaTypes.UUID, required: true, } +}, +{ + discriminatorKey: 'status' }); -module.exports = mediaSchema.descriminiator('queued', queuedProperties); \ No newline at end of file +//methods +queuedProperties.methods.rehydrate = function(){ + return new queuedMedia( + this.title, + this.fileName, + this.url, + this.id, + this.type, + this.duration, + this.startTime, + this.startTimeStamp, + this.earlyEnd, + this.uuid.toString() + ); +} + +module.exports = mediaSchema.discriminator('queued', queuedProperties); \ No newline at end of file diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index a82b336..e8033df 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -26,12 +26,17 @@ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400) } } -module.exports.exceptionHandler = function(res, err){ +module.exports.localExceptionHandler = function(err){ //If we're being verbose if(config.verbose){ //Log the error console.log(err) } +} + +module.exports.exceptionHandler = function(res, err){ + //Locally handle the exception + module.exports.localExceptionHandler(err); //if not yell at the browser for fucking up, and tell it what it did wrong. module.exports.errorHandler(res, err.message, "Caught Exception"); @@ -42,11 +47,8 @@ module.exports.socketErrorHandler = function(socket, msg, type = "Generic"){ } module.exports.socketExceptionHandler = function(socket, err){ - //If we're being verbose - if(config.verbose){ - //Log the error - console.log(err) - } + //Locally handle the exception + module.exports.localExceptionHandler(err); //if not yell at the browser for fucking up, and tell it what it did wrong. return module.exports.socketErrorHandler(socket, err.msg, "Caught Exception"); diff --git a/src/views/partial/popup/changePassword.ejs b/src/views/partial/popup/changePassword.ejs index 76abca8..b726362 100644 --- a/src/views/partial/popup/changePassword.ejs +++ b/src/views/partial/popup/changePassword.ejs @@ -16,8 +16,8 @@ along with this program. If not, see . %>
-

Enter new email and current password below:

- - - +

Enter new and confirm current password below:

+ + +
\ No newline at end of file diff --git a/www/js/channel/panels/queuePanel.js b/www/js/channel/panels/queuePanel.js index 65349ab..264d1e0 100644 --- a/www/js/channel/panels/queuePanel.js +++ b/www/js/channel/panels/queuePanel.js @@ -80,8 +80,10 @@ class queuePanel extends panelObj{ defineListeners(){ //Render queue when we receive a new copy of the queue data from the server - this.client.socket.on("clientMetadata", (data) => {this.renderQueue();}); - this.client.socket.on("queue", (data) => {this.renderQueue();}); + this.client.socket.on("clientMetadata", () => {this.renderQueue();}); + this.client.socket.on("queue", () => {this.renderQueue();}); + this.client.socket.on("start", () => {this.renderQueue();}); + this.client.socket.on("end", () => {this.renderQueue();}); this.client.socket.on("lock", this.handleScheduleLock.bind(this)); this.client.socket.on("error", this.handleQueueError.bind(this)); } @@ -415,6 +417,12 @@ class queuePanel extends panelObj{ } clearQueue(){ + //If we have no body + if(this.ownerDoc.body == null){ + //We have bigger issues + return; + } + //Clear out queue container this.queueContainer.innerHTML = '';; //Clear out queue marker container diff --git a/www/js/profile.js b/www/js/profile.js index a2aae0e..7cc1e85 100644 --- a/www/js/profile.js +++ b/www/js/profile.js @@ -227,7 +227,7 @@ class accountSettingsButton{ class changeEmailPopup{ constructor(){ - this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this), this.asyncConstructor); + this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this)); } asyncConstructor(){ @@ -251,7 +251,7 @@ class changeEmailPopup{ class changePasswordPopup{ constructor(){ - this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this), this.asyncConstructor); + this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this)); } asyncConstructor(){