diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 0a5ebaa..f74b0f0 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -131,6 +131,7 @@ class queue{ socket.on("lock", () => {this.toggleLock(socket)}); socket.on("goLive", (data) => {this.goLive(socket, data)}); socket.on("getStreamType", (data) => {this.getStreamType(socket)}); + socket.on("getArchive", (data) => {this.getArchive(socket, data)}); //If debug mode is enabled if(config.debug){ @@ -510,6 +511,57 @@ class queue{ } } + /** + * Returns current channel stream type upon request + * @param {Socket} socket - Socket we received the request from + * @param {Object} data - Event payload + */ + async getArchive(socket, data){ + try{ + //If we get an invalid start date + if(typeof data.startDate != "number" || data.startDate < 0){ + //Bitch, moan, an' complain! + loggerUtils.socketErrorHandler(socket, "Invalid Start Date!", "validation"); + //Yeetus-Deletus bitch + return; + } + + //If we get an invalid start date + if(typeof data.endDate != "number" || data.endDate < 0){ + //Bitch, moan, an' complain! + loggerUtils.socketErrorHandler(socket, "Invalid End Date!", "validation"); + //Yeetus-Deletus bitch + return; + } + + //If our startDate isn't before our endDate + if(data.startDate >= data.endDate){ + //Bitch, moan, an' complain! + loggerUtils.socketErrorHandler(socket, "Invalid Date Range!", "validation"); + //Yeetus-Deletus bitch + return; + } + + //Get the current channel from the database + const chanDB = await channelModel.findOne({name: socket.chan}); + + //If the user isn't allowed to read the schedule + if(!await chanDB.permCheck(socket.user, 'readSchedule')){ + //Fuckin ignore em' + return; + } + + //Create new date objects to represent range of data requested + const startDate = new Date(data.startDate); + const endDate = new Date(data.endDate); + + //Grab archives from given range, and send them back to the requesting socket + socket.emit("archive", {archive: await this.prepArchive(startDate, endDate)}); + }catch(err){ + return loggerUtils.socketExceptionHandler(socket, err); + } + } + async dumpQueue(socket, data){ //If we somehow got here while config.debug is disabled, or the user isn't allowed to preform server-side debugging if(!(config.debug && await permissionModel.permCheck(socket.user, "debug"))){ @@ -522,7 +574,7 @@ class queue{ //Pull the channel DB doc const chanDB = await channelModel.findOne({name:this.channel.name}); //Pull archived media by channel name - const archived = await this.getArchive(); + const archived = await this.queryArchive(); //Cook and emit a new object from all of the data socket.emit("dumpQueue", { @@ -948,7 +1000,7 @@ class queue{ //If broadcasting is enabled if(!noBroadcast){ //Broadcast the channel - this.broadcastQueue(chanDB); + this.broadcastQueue(); } }catch(err){ @@ -1238,7 +1290,7 @@ class queue{ await chanDB.save(); //Broadcast queue since we ended our media item early - this.broadcastQueue(chanDB); + this.broadcastQueue(); }catch(err){ loggerUtils.localExceptionHandler(err); } @@ -1384,7 +1436,7 @@ class queue{ } //broadcast queue using unsaved archive, run this before chanDB.save() for better responsiveness - this.broadcastQueue(chanDB); + this.broadcastQueue(); //Save our changes to the DB await chanDB.save(); @@ -1625,7 +1677,7 @@ class queue{ await this.scheduleMedia(newSched, null, chanDB); //Broadcast the queue now that everything is hunky dory - await this.broadcastQueue(chanDB); + await this.broadcastQueue(); }catch(err){ //Null out live remainder for the next stream this.liveRemainder = null; @@ -1799,71 +1851,49 @@ class queue{ /** * Broadcasts channel queue - * @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions */ - async broadcastQueue(chanDB){ + async broadcastQueue(){ //Broadcast queue to authenticated sockets within the channels room inside the protected 'queue-broadcast' namespace - this.server.queueBroadcastManager.namespace.in(this.channel.name).emit('queue',{queue: await this.prepQueue(chanDB)}); + this.server.queueBroadcastManager.namespace.in(this.channel.name).emit('queue',{queue: await this.prepQueue()}); } /** * Broadcasts channel queue to a single socket - * @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions * @param {Socket} socket - Socket to send queue to */ - async emitQueue(chanDB, socket){ + async emitQueue(socket){ //Broadcast queue to authenticated sockets within the channels room inside the protected 'queue-broadcast' namespace - socket.emit('queue',{queue: await this.prepQueue(chanDB)}); + socket.emit('queue',{queue: await this.prepQueue()}); + } + + /** + * Broadcasts channel queue to a single socket + * @param {Date} startDate - Start date to pull archives from + * @param {Date} endDate - Start date to pull archives from + * @param {Socket} socket - Socket to send queue to + */ + async emitArchive(startDate, endDate, socket){ + //Broadcast queue to authenticated sockets within the channels room inside the protected 'queue-broadcast' namespace + socket.emit('queue',{queue: await this.prepArchive(startDate, endDate)}); } /** * Prepares channel queue for network transmission - * @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions * @returns de-hydrated scehdule information */ - async prepQueue(chanDB){ + async prepQueue(){ 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 loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while rehydrating queue!`, "queue"); - } - - //Create an empty array to hold our schedule - let schedule = []; - //Get todays epoch const yesterday = new Date(); //Make it yesterdays yesterday.setDate(yesterday.getDate() - 1); - //Get archive since yesterday - const archive = await this.getArchive(yesterday); - - //Iterate through the channel archive backwards to save time - for(let mediaIndex = archive.length - 1; mediaIndex >= 0; mediaIndex--){ - //Grab the current media record - let media = archive[mediaIndex].rehydrate(); - - //If we're sending out the live remainder during a live stream - if(this.liveRemainder != null && media.uuid.toString() == this.liveRemainder.uuid.toString()){ - //Throw out the early end before sending it off, so it looks like it hasn't been cut off yet (smoke n mirrors :P) - media.earlyEnd = null; - } - - //Add it to the temporary schedule array as if it where part of the actual schedule map - schedule.unshift([media.startTime, media]); - } + //Prep archive from yesterday + const archive = await this.prepArchive(yesterday); //Concatonate the actual schedule to the items we pulled out of the archive return it - return schedule.concat(Array.from(this.schedule)); + return archive.concat(Array.from(this.schedule)); //If we can't get shit from the database }catch(err){ //Complain @@ -1873,6 +1903,31 @@ class queue{ } } + async prepArchive(startDate, endDate = new Date()){ + //Define array to hold prepped archive objects + const preppedArchive = []; + + //Get archive since yesterday + const archive = await this.queryArchive(startDate, endDate); + + //Iterate through the channel archive backwards to save time + for(let mediaIndex = archive.length - 1; mediaIndex >= 0; mediaIndex--){ + //Grab the current media record + let media = archive[mediaIndex].rehydrate(); + + //If we're sending out the live remainder during a live stream + if(this.liveRemainder != null && media.uuid.toString() == this.liveRemainder.uuid.toString()){ + //Throw out the early end before sending it off, so it looks like it hasn't been cut off yet (smoke n mirrors :P) + media.earlyEnd = null; + } + + //Add it to the temporary preppd archive array as if it where part of the actual schedule map + preppedArchive.unshift([media.startTime, media]); + } + + return preppedArchive; + } + /** * Rehydrates media schedule from DB * @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions @@ -1946,7 +2001,7 @@ class queue{ //If we have a remainder from a livestream if(chanDB.media.liveRemainder){ - let archive = await this.getArchive(); + let archive = await this.queryArchive(); //Iterate backwards through channel archive @@ -2005,15 +2060,17 @@ class queue{ /** * Get Channel Archive + * @param {Date} startDate - Start of range to grab from + * @param {Date} endDate - End of range to grab from */ - async getArchive(startDate){ + async queryArchive(startDate, endDate = new Date()){ //If we provided a start date if(startDate != null){ //Return archived media after start date return await archivedMediaModel.find({ channel:this.channel._id, //Prevent mongoose from attempting to sanatize and invalidate our query object, plus I think we can trust an epoch that came from server-side :) - startTime: mongoose.trusted({$gt: startDate.getTime()}) + startTime: mongoose.trusted({$gt: startDate.getTime(), $lt: endDate.getTime()}) }); //Otherwise }else{ diff --git a/src/app/channel/media/queueBroadcastManager.js b/src/app/channel/media/queueBroadcastManager.js index 7f8e319..f2ee20f 100644 --- a/src/app/channel/media/queueBroadcastManager.js +++ b/src/app/channel/media/queueBroadcastManager.js @@ -79,7 +79,7 @@ class queueBroadcastManager extends auxServer{ socket.join(socket.chan); //Send the queue down to our newly connected user - activeChannel.queue.emitQueue(chanDB, socket); + activeChannel.queue.emitQueue(socket); //Define listeners this.defineListeners(socket); diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 6b5e535..13d2bda 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -126,6 +126,10 @@ class channel{ this.queueBroadcastSocket.on("queue", (data) => { this.queue = new Map(data.queue); }); + + this.socket.on("archive", (data) =>{ + this.sortQueue(data.archive); + }) } /** @@ -157,6 +161,39 @@ class channel{ this.streamType = data.streamType; } + /** + * Sorts queue, optionally adding new items along the way + * @param {object} queueData - Contains list of entries to add to the queue + */ + sortQueue(queueData){ + //Create array to temporary sort schedule within + const tempSched = []; + //Get current queue as entries + let entries = Array.from(this.queue.entries()); + + //Blow-out existing queue since we've already pulled out the data we need from it + this.queue = new Map(); + + + //If we have new queue data + if(queueData != null){ + //Concat passed queue data to beggining of entries pulled from queue map + entries = queueData.concat(entries); + } + + //For each media entry accounted for + for(let entry of entries){ + //Store entry item in array indexed to its start epoch in seconds + tempSched[Math.round(entry[0] / 1000)] = entry[1]; + } + + //Iterate through populated indices of tempSched to skip over empty indexes, just like in the server-side + for(let startTime of Object.keys(tempSched)){ + //Add each entry to the queue in order of start time + this.queue.set(tempSched[startTime].startTime, tempSched[startTime]); + } + } + /** * Processes and applies default config on any unset settings * @param {Boolean} force - Whether or not to forcefully reset already set settings diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js index 5f49000..50a9dee 100644 --- a/www/js/channel/panels/queuePanel/queuePanel.js +++ b/www/js/channel/panels/queuePanel/queuePanel.js @@ -142,7 +142,8 @@ class queuePanel extends panelObj{ defineListeners(){ //Render queue when we receive a new copy of the queue data from the server //Render queue should be called within an arrow function so that it's called with default parameters, and not handed an event as a date - this.client.queueBroadcastSocket.on("queue", () => {this.renderQueue();}); + this.client.queueBroadcastSocket.on("queue", this.renderQueue.bind(this)); + this.client.socket.on("archive", this.renderQueue.bind(this)); this.client.socket.on("start", this.handleStart.bind(this)); this.client.socket.on("end", this.handleEnd.bind(this)); this.client.socket.on("lock", this.handleScheduleLock.bind(this)); @@ -491,6 +492,20 @@ class queuePanel extends panelObj{ //Re-render/hide the time marker this.renderTimeMarker(); + //If we're not looking at today + if(!utils.isSameDate(date, new Date())){ + //Clone given epoch twice + const dayBefore = new Date(date.getTime()); + const dayAfter = new Date(date.getTime()); + + //Make it a day before/after + dayBefore.setDate(dayBefore.getDate() - 1); + dayAfter.setDate(dayAfter.getDate() + 1); + + //Request the archives from that day + this.client.socket.emit("getArchive", {startDate: dayBefore.getTime(), endDate: dayAfter.getTime()}); + } + //If autoscroll is enabled if(this.autoscroll){ //Simulate a button click to un/re-light the button and trigger a scroll when the date is set to today