Browse Source

refactor(Station): Continued adapting includedPlaylists/playMode into autofill

Owen Diffey 3 years ago
parent
commit
354fa8a72a

+ 2 - 2
backend/logic/actions/playlists.js

@@ -1224,7 +1224,7 @@ export default {
 					});
 				}
 
-				StationsModule.runJob("GET_STATIONS_THAT_INCLUDE_OR_BLACKLIST_PLAYLIST", { playlistId })
+				StationsModule.runJob("GET_STATIONS_THAT_AUTOFILL_OR_BLACKLIST_PLAYLIST", { playlistId })
 					.then(response => {
 						response.stationIds.forEach(stationId => {
 							PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }).then().catch();
@@ -1509,7 +1509,7 @@ export default {
 				},
 
 				(playlist, next) => {
-					StationsModule.runJob("GET_STATIONS_THAT_INCLUDE_OR_BLACKLIST_PLAYLIST", { playlistId })
+					StationsModule.runJob("GET_STATIONS_THAT_AUTOFILL_OR_BLACKLIST_PLAYLIST", { playlistId })
 						.then(response => {
 							response.stationIds.forEach(stationId => {
 								PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }).then().catch();

+ 64 - 54
backend/logic/actions/stations.js

@@ -85,14 +85,14 @@ CacheModule.runJob("SUB", {
 });
 
 CacheModule.runJob("SUB", {
-	channel: "station.includedPlaylist",
+	channel: "station.autofillPlaylist",
 	cb: data => {
 		const { stationId, playlistId } = data;
 
 		PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }).then(playlist =>
 			WSModule.runJob("EMIT_TO_ROOMS", {
 				rooms: [`station.${stationId}`, `manage-station.${stationId}`],
-				args: ["event:station.includedPlaylist", { data: { stationId, playlist } }]
+				args: ["event:station.autofillPlaylist", { data: { stationId, playlist } }]
 			})
 		);
 	}
@@ -113,12 +113,12 @@ CacheModule.runJob("SUB", {
 });
 
 CacheModule.runJob("SUB", {
-	channel: "station.removedIncludedPlaylist",
+	channel: "station.removedAutofillPlaylist",
 	cb: data => {
 		const { stationId, playlistId } = data;
 		WSModule.runJob("EMIT_TO_ROOMS", {
 			rooms: [`station.${stationId}`, `manage-station.${stationId}`],
-			args: ["event:station.removedIncludedPlaylist", { data: { stationId, playlistId } }]
+			args: ["event:station.removedAutofillPlaylist", { data: { stationId, playlistId } }]
 		});
 	}
 });
@@ -194,6 +194,12 @@ CacheModule.runJob("SUB", {
 	channel: "station.queueUpdate",
 	cb: stationId => {
 		StationsModule.runJob("GET_STATION", { stationId }).then(station => {
+			if (!station.currentSong && station.queue.length > 0) {
+				StationsModule.runJob("INITIALIZE_STATION", {
+					stationId
+				}).then();
+			}
+
 			WSModule.runJob("EMIT_TO_ROOM", {
 				room: `station.${stationId}`,
 				args: ["event:station.queue.updated", { data: { queue: station.queue } }]
@@ -321,7 +327,7 @@ CacheModule.runJob("SUB", {
 
 		stationModel.findOne(
 			{ _id: stationId },
-			["_id", "name", "displayName", "description", "type", "privacy", "owner", "requests", "playMode", "theme"],
+			["_id", "name", "displayName", "description", "type", "privacy", "owner", "requests", "autofill", "theme"],
 			(err, station) => {
 				WSModule.runJob("EMIT_TO_ROOMS", {
 					rooms: [`station.${stationId}`, `manage-station.${stationId}`, "admin.stations"],
@@ -818,9 +824,8 @@ export default {
 						name: station.name,
 						privacy: station.privacy,
 						requests: station.requests,
-						playMode: station.playMode,
+						autofill: station.autofill,
 						owner: station.owner,
-						includedPlaylists: station.includedPlaylists,
 						blacklist: station.blacklist,
 						theme: station.theme
 					};
@@ -945,7 +950,7 @@ export default {
 						name: station.name,
 						privacy: station.privacy,
 						requests: station.requests,
-						playMode: station.playMode,
+						autofill: station.autofill,
 						owner: station.owner,
 						theme: station.theme,
 						paused: station.paused,
@@ -967,7 +972,7 @@ export default {
 		);
 	},
 
-	getStationIncludedPlaylistsById(session, stationId, cb) {
+	getStationAutofillPlaylistsById(session, stationId, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -999,7 +1004,7 @@ export default {
 					const playlists = [];
 
 					async.eachLimit(
-						station.includedPlaylists,
+						station.autofill.playlists,
 						1,
 						(playlistId, next) => {
 							PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
@@ -1023,15 +1028,15 @@ export default {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
 						"ERROR",
-						"GET_STATION_INCLUDED_PLAYLISTS_BY_ID",
-						`Getting station "${stationId}"'s included playlists failed. "${err}"`
+						"GET_STATION_AUTOFILL_PLAYLISTS_BY_ID",
+						`Getting station "${stationId}"'s autofilling playlists failed. "${err}"`
 					);
 					return cb({ status: "error", message: err });
 				}
 				this.log(
 					"SUCCESS",
-					"GET_STATION_INCLUDED_PLAYLISTS_BY_ID",
-					`Got station "${stationId}"'s included playlists successfully.`
+					"GET_STATION_AUTOFILL_PLAYLISTS_BY_ID",
+					`Got station "${stationId}"'s autofilling playlists successfully.`
 				);
 				return cb({ status: "success", data: { playlists } });
 			}
@@ -1342,6 +1347,21 @@ export default {
 						.catch(next);
 				},
 
+				(station, previousStation, next) => {
+					if (newStation.autofill.enabled && !previousStation.autofill.enabled)
+						StationsModule.runJob("AUTOFILL_STATION", { stationId }, this)
+							.then(() => {
+								CacheModule.runJob("PUB", {
+									channel: "station.queueUpdate",
+									value: stationId
+								})
+									.then(() => next(null, station, previousStation))
+									.catch(next);
+							})
+							.catch(next);
+					else next(null, station, previousStation);
+				},
+
 				(station, previousStation, next) => {
 					playlistModel.updateOne(
 						{ _id: station.playlist },
@@ -1706,8 +1726,7 @@ export default {
 								type,
 								privacy: "private",
 								queue: [],
-								currentSong: null,
-								playMode: "random"
+								currentSong: null
 							},
 							next
 						);
@@ -1723,8 +1742,7 @@ export default {
 								privacy: "private",
 								owner: session.userId,
 								queue: [],
-								currentSong: null,
-								playMode: "random"
+								currentSong: null
 							},
 							next
 						);
@@ -2244,14 +2262,14 @@ export default {
 	}),
 
 	/**
-	 * Includes a playlist in a station
+	 * Autofill a playlist in a station
 	 *
 	 * @param session
 	 * @param stationId - the station id
 	 * @param playlistId - the playlist id
 	 * @param cb
 	 */
-	includePlaylist: isOwnerRequired(async function includePlaylist(session, stationId, playlistId, cb) {
+	autofillPlaylist: isOwnerRequired(async function autofillPlaylist(session, stationId, playlistId, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -2262,15 +2280,15 @@ export default {
 
 				(station, next) => {
 					if (!station) return next("Station not found.");
-					if (station.includedPlaylists.indexOf(playlistId) !== -1)
-						return next("That playlist is already included.");
-					if (station.playMode === "sequential" && station.includedPlaylists.length > 0)
-						return next("Error: Only 1 playlist can be included in sequential play mode.");
+					if (station.autofill.playlists.indexOf(playlistId) !== -1)
+						return next("That playlist is already autofilling.");
+					if (station.autofill.mode === "sequential" && station.autofill.playlists.length > 0)
+						return next("Error: Only 1 playlist can be autofilling in sequential mode.");
 					return next();
 				},
 
 				next => {
-					StationsModule.runJob("INCLUDE_PLAYLIST", { stationId, playlistId }, this)
+					StationsModule.runJob("AUTOFILL_PLAYLIST", { stationId, playlistId }, this)
 						.then(() => {
 							next();
 						})
@@ -2282,7 +2300,7 @@ export default {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
 						"ERROR",
-						"STATIONS_INCLUDE_PLAYLIST",
+						"STATIONS_AUTOFILL_PLAYLIST",
 						`Including playlist "${playlistId}" for station "${stationId}" failed. "${err}"`
 					);
 					return cb({ status: "error", message: err });
@@ -2290,14 +2308,14 @@ export default {
 
 				this.log(
 					"SUCCESS",
-					"STATIONS_INCLUDE_PLAYLIST",
+					"STATIONS_AUTOFILL_PLAYLIST",
 					`Including playlist "${playlistId}" for station "${stationId}" successfully.`
 				);
 
 				PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }).then().catch();
 
 				CacheModule.runJob("PUB", {
-					channel: "station.includedPlaylist",
+					channel: "station.autofillPlaylist",
 					value: {
 						playlistId,
 						stationId
@@ -2306,21 +2324,21 @@ export default {
 
 				return cb({
 					status: "success",
-					message: "Successfully included playlist."
+					message: "Successfully added autofill playlist."
 				});
 			}
 		);
 	}),
 
 	/**
-	 * Remove included a playlist from a station
+	 * Remove autofilled playlist from a station
 	 *
 	 * @param session
 	 * @param stationId - the station id
 	 * @param playlistId - the playlist id
 	 * @param cb
 	 */
-	removeIncludedPlaylist: isOwnerRequired(async function removeIncludedPlaylist(session, stationId, playlistId, cb) {
+	removeAutofillPlaylist: isOwnerRequired(async function removeAutofillPlaylist(session, stationId, playlistId, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -2331,13 +2349,13 @@ export default {
 
 				(station, next) => {
 					if (!station) return next("Station not found.");
-					if (station.includedPlaylists.indexOf(playlistId) === -1)
-						return next("That playlist is not included.");
+					if (station.autofill.playlists.indexOf(playlistId) === -1)
+						return next("That playlist is not autofilling.");
 					return next();
 				},
 
 				next => {
-					StationsModule.runJob("REMOVE_INCLUDED_PLAYLIST", { stationId, playlistId }, this)
+					StationsModule.runJob("REMOVE_AUTOFILL_PLAYLIST", { stationId, playlistId }, this)
 						.then(() => {
 							next();
 						})
@@ -2349,22 +2367,22 @@ export default {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
 						"ERROR",
-						"STATIONS_REMOVE_INCLUDED_PLAYLIST",
-						`Removing included playlist "${playlistId}" for station "${stationId}" failed. "${err}"`
+						"STATIONS_REMOVE_AUTOFILL_PLAYLIST",
+						`Removing autofill playlist "${playlistId}" for station "${stationId}" failed. "${err}"`
 					);
 					return cb({ status: "error", message: err });
 				}
 
 				this.log(
 					"SUCCESS",
-					"STATIONS_REMOVE_INCLUDED_PLAYLIST",
-					`Removing included playlist "${playlistId}" for station "${stationId}" successfully.`
+					"STATIONS_REMOVE_AUTOFILL_PLAYLIST",
+					`Removing autofill playlist "${playlistId}" for station "${stationId}" successfully.`
 				);
 
 				PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }).then().catch();
 
 				CacheModule.runJob("PUB", {
-					channel: "station.removedIncludedPlaylist",
+					channel: "station.removedAutofillPlaylist",
 					value: {
 						playlistId,
 						stationId
@@ -2373,7 +2391,7 @@ export default {
 
 				return cb({
 					status: "success",
-					message: "Successfully removed included playlist."
+					message: "Successfully removed autofill playlist."
 				});
 			}
 		);
@@ -2666,17 +2684,17 @@ export default {
 	}),
 
 	/**
-	 * Clears and refills a station queue
+	 * Reset a station queue
 	 *
 	 * @param {object} session - the session object automatically added by socket.io
 	 * @param {string} stationId - the station id
 	 * @param {Function} cb - gets called with the result
 	 */
-	clearAndRefillStationQueue: isAdminRequired(async function clearAndRefillStationQueue(session, stationId, cb) {
+	resetQueue: isAdminRequired(async function resetQueue(session, stationId, cb) {
 		async.waterfall(
 			[
 				next => {
-					StationsModule.runJob("CLEAR_AND_REFILL_STATION_QUEUE", { stationId }, this)
+					StationsModule.runJob("RESET_QUEUE", { stationId }, this)
 						.then(() => next())
 						.catch(next);
 				}
@@ -2684,19 +2702,11 @@ export default {
 			async err => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"CLEAR_AND_REFILL_STATION_QUEUE",
-						`Clearing and refilling station queue failed. "${err}"`
-					);
+					this.log("ERROR", "RESET_QUEUE", `Resetting station queue failed. "${err}"`);
 					return cb({ status: "error", message: err });
 				}
-				this.log(
-					"SUCCESS",
-					"CLEAR_AND_REFILL_STATION_QUEUE",
-					"Clearing and refilling station queue was successful."
-				);
-				return cb({ status: "success", message: "Successfully cleared and refilled station queue." });
+				this.log("SUCCESS", "RESET_QUEUE", "Resetting station queue was successful.");
+				return cb({ status: "success", message: "Successfully reset station queue." });
 			}
 		);
 	}),

+ 6 - 2
backend/logic/db/schemas/station.js

@@ -45,9 +45,13 @@ export default {
 		access: { type: String, enum: ["owner", "user"], default: "owner" },
 		limit: { type: Number, min: 1, max: 50, default: 3 }
 	},
-	playMode: { type: String, enum: ["random", "sequential"], default: "random" },
+	autofill: {
+		enabled: { type: Boolean, default: true },
+		playlists: [{ type: mongoose.Schema.Types.ObjectId, ref: "playlists" }],
+		limit: { type: Number, min: 1, max: 50, default: 3 },
+		mode: { type: String, enum: ["random", "sequential"], default: "random" }
+	},
 	theme: { type: String, enum: ["blue", "purple", "teal", "orange", "red"], default: "blue" },
-	includedPlaylists: [{ type: String }],
 	blacklist: [{ type: mongoose.Schema.Types.ObjectId, ref: "playlists" }],
 	documentVersion: { type: Number, default: 7, required: true }
 };

+ 44 - 1
backend/logic/migration/migrations/migration20.js

@@ -47,12 +47,55 @@ export default async function migrate(MigrationModule) {
 					});
 					stationModel.updateMany(
 						{ documentVersion: 7 },
-						{ $set: { "requests.enabled": true } },
+						{
+							$set: {
+								requests: {
+									enabled: true,
+									access: "owner",
+									limit: 3
+								}
+							}
+						},
 						(err, res) => {
 							this.log("INFO", `Migration 20. Stations found: ${res.modifiedCount}.`);
 							next(err);
 						}
 					);
+					stationModel.find(
+						{ documentVersion: 7, includedPlaylists: { $exists: true }, playMode: { $exists: true } },
+						(err, stations) => {
+							if (err) next(err);
+							else {
+								async.eachLimit(
+									stations.map(station => station._doc),
+									1,
+									(station, next) => {
+										stationModel.updateOne(
+											{ _id: station._id },
+											{
+												$unset: { includedPlaylists: "", playMode: "" },
+												$set: {
+													autofill: {
+														enabled: true,
+														playlists: station.includedPlaylists.map(playlist =>
+															mongoose.Types.ObjectId(playlist)
+														),
+														limit: 3,
+														mode: station.playMode
+													}
+												}
+											},
+											next
+										);
+									},
+									err => {
+										this.log("INFO", `Migration 20. Stations found: ${stations.length}.`);
+										next(err);
+									}
+								);
+							}
+						}
+					);
 				}
 			],
 			err => {

+ 12 - 12
backend/logic/playlists.js

@@ -486,7 +486,7 @@ class _PlaylistsModule extends CoreClass {
 					},
 
 					(playlistId, next) => {
-						StationsModule.runJob("GET_STATIONS_THAT_INCLUDE_OR_BLACKLIST_PLAYLIST", { playlistId }, this)
+						StationsModule.runJob("GET_STATIONS_THAT_AUTOFILL_OR_BLACKLIST_PLAYLIST", { playlistId }, this)
 							.then(response => {
 								async.eachLimit(
 									response.stationIds,
@@ -538,7 +538,7 @@ class _PlaylistsModule extends CoreClass {
 								.then(response => {
 									if (response.songs.length === 0) {
 										StationsModule.runJob(
-											"GET_STATIONS_THAT_INCLUDE_OR_BLACKLIST_PLAYLIST",
+											"GET_STATIONS_THAT_AUTOFILL_OR_BLACKLIST_PLAYLIST",
 											{ playlistId: playlist._id },
 											this
 										)
@@ -705,25 +705,25 @@ class _PlaylistsModule extends CoreClass {
 					},
 
 					(station, next) => {
-						const includedPlaylists = [];
+						const playlists = [];
 						async.eachLimit(
-							station.includedPlaylists,
+							station.autofill.playlists,
 							1,
 							(playlistId, next) => {
 								PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
 									.then(playlist => {
-										includedPlaylists.push(playlist);
+										playlists.push(playlist);
 										next();
 									})
 									.catch(next);
 							},
 							err => {
-								next(err, station, includedPlaylists);
+								next(err, station, playlists);
 							}
 						);
 					},
 
-					(station, includedPlaylists, next) => {
+					(station, playlists, next) => {
 						const blacklist = [];
 						async.eachLimit(
 							station.blacklist,
@@ -737,12 +737,12 @@ class _PlaylistsModule extends CoreClass {
 									.catch(next);
 							},
 							err => {
-								next(err, station, includedPlaylists, blacklist);
+								next(err, station, playlists, blacklist);
 							}
 						);
 					},
 
-					(station, includedPlaylists, blacklist, next) => {
+					(station, playlists, blacklist, next) => {
 						const blacklistedSongs = blacklist
 							.flatMap(blacklistedPlaylist => blacklistedPlaylist.songs)
 							.reduce(
@@ -750,8 +750,8 @@ class _PlaylistsModule extends CoreClass {
 									items.find(x => x.youtubeId === item.youtubeId) ? [...items] : [...items, item],
 								[]
 							);
-						const includedSongs = includedPlaylists
-							.flatMap(includedPlaylist => includedPlaylist.songs)
+						const includedSongs = playlists
+							.flatMap(playlist => playlist.songs)
 							.reduce(
 								(songs, song) =>
 									songs.find(x => x.youtubeId === song.youtubeId) ? [...songs] : [...songs, song],
@@ -943,7 +943,7 @@ class _PlaylistsModule extends CoreClass {
 
 					next => {
 						StationsModule.runJob(
-							"REMOVE_INCLUDED_OR_BLACKLISTED_PLAYLIST_FROM_STATIONS",
+							"REMOVE_AUTOFILLED_OR_BLACKLISTED_PLAYLIST_FROM_STATIONS",
 							{ playlistId: payload.playlistId },
 							this
 						)

+ 80 - 73
backend/logic/stations.js

@@ -63,23 +63,16 @@ class _StationsModule extends CoreClass {
 							stationId
 						}).then();
 					}
-				});
-			}
-		});
 
-		CacheModule.runJob("SUB", {
-			channel: "station.newOfficialPlaylist",
-			cb: async stationId => {
-				CacheModule.runJob("HGET", {
-					table: "officialPlaylists",
-					key: stationId
-				}).then(playlistObj => {
-					if (playlistObj) {
-						WSModule.runJob("EMIT_TO_ROOM", {
-							room: `station.${stationId}`,
-							args: ["event:newOfficialPlaylist", { data: { playlist: playlistObj.songs } }]
-						});
-					}
+					WSModule.runJob("EMIT_TO_ROOM", {
+						room: `station.${stationId}`,
+						args: ["event:station.queue.updated", { data: { queue: station.queue } }]
+					});
+
+					WSModule.runJob("EMIT_TO_ROOM", {
+						room: `manage-station.${stationId}`,
+						args: ["event:manageStation.queue.updated", { data: { stationId, queue: station.queue } }]
+					});
 				});
 			}
 		});
@@ -449,14 +442,14 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Fills up the official station playlist queue using the songs from the official station playlist
+	 * Autofill station queue from station playlist
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.stationId - the id of the station
 	 * @param {string} payload.ignoreExistingQueue - ignore the existing queue songs, replacing the old queue with a completely fresh one
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST(payload) {
+	AUTOFILL_STATION(payload) {
 		return new Promise((resolve, reject) => {
 			const { stationId, ignoreExistingQueue } = payload;
 			async.waterfall(
@@ -472,6 +465,7 @@ class _StationsModule extends CoreClass {
 					(playlist, next) => {
 						StationsModule.runJob("GET_STATION", { stationId }, this)
 							.then(station => {
+								if (!station.autofill.enabled) next("Autofill is disabled in this station");
 								if (ignoreExistingQueue) station.queue = [];
 								next(null, playlist, station);
 							})
@@ -479,7 +473,7 @@ class _StationsModule extends CoreClass {
 					},
 
 					(playlist, station, next) => {
-						if (station.playMode === "random") {
+						if (station.autofill.mode === "random") {
 							UtilsModule.runJob("SHUFFLE", { array: playlist.songs }, this)
 								.then(response => {
 									next(null, response.array, station);
@@ -490,7 +484,7 @@ class _StationsModule extends CoreClass {
 
 					(_playlistSongs, station, next) => {
 						let playlistSongs = JSON.parse(JSON.stringify(_playlistSongs));
-						if (station.playMode === "sequential") {
+						if (station.autofill.mode === "sequential") {
 							if (station.currentSongIndex <= playlistSongs.length) {
 								const songsToAddToEnd = playlistSongs.splice(0, station.currentSongIndex);
 								playlistSongs = [...playlistSongs, ...songsToAddToEnd];
@@ -520,7 +514,7 @@ class _StationsModule extends CoreClass {
 
 						let { currentSongIndex } = station;
 
-						if (station.playMode === "sequential" && lastSongAdded) {
+						if (station.autofill.mode === "sequential" && lastSongAdded) {
 							const indexOfLastSong = _playlistSongs
 								.map(song => song.youtubeId)
 								.indexOf(lastSongAdded.youtubeId);
@@ -747,34 +741,29 @@ class _StationsModule extends CoreClass {
 							.catch(next);
 					},
 
-					// eslint-disable-next-line consistent-return
 					(station, next) => {
 						if (!station) return next("Station not found.");
 
-						StationsModule.runJob(
-							"FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST",
-							{ stationId: station._id },
-							this
-						)
-							.then(() => {
-								StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
-									.then(response => {
-										StationsModule.runJob(
-											"REMOVE_FIRST_QUEUE_SONG",
-											{ stationId: station._id },
-											this
-										)
-											.then(() => {
-												next(null, response.song, station);
-											})
-											.catch(next);
+						if (station.autofill.enabled)
+							return StationsModule.runJob("AUTOFILL_STATION", { stationId: station._id }, this)
+								.then(() => next(null, station))
+								.catch(next);
+						return next(null, station);
+					},
+
+					(station, next) => {
+						StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
+							.then(response => {
+								StationsModule.runJob("REMOVE_FIRST_QUEUE_SONG", { stationId: station._id }, this)
+									.then(() => {
+										next(null, response.song, station);
 									})
-									.catch(err => {
-										if (err === "No songs available.") next(null, null, station);
-										else next(err);
-									});
+									.catch(next);
 							})
-							.catch(next);
+							.catch(err => {
+								if (err === "No songs available.") next(null, null, station);
+								else next(err);
+							});
 					},
 					(song, station, next) => {
 						const $set = {};
@@ -1140,14 +1129,14 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Adds a playlist to be included in a station
+	 * Adds a playlist to autofill a station
 	 *
 	 * @param {object} payload - object that contains the payload
-	 * @param {object} payload.stationId - the id of the station to include the playlist in
-	 * @param {object} payload.playlistId - the id of the playlist to be included
+	 * @param {object} payload.stationId - the id of the station
+	 * @param {object} payload.playlistId - the id of the playlist
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	INCLUDE_PLAYLIST(payload) {
+	AUTOFILL_PLAYLIST(payload) {
 		return new Promise((resolve, reject) => {
 			async.waterfall(
 				[
@@ -1166,13 +1155,11 @@ class _StationsModule extends CoreClass {
 					},
 
 					(station, next) => {
-						if (station.playlist === payload.playlistId) next("You cannot include the station playlist");
-						else if (station.includedPlaylists.indexOf(payload.playlistId) !== -1)
-							next("This playlist is already included");
+						if (station.playlist === payload.playlistId) next("You cannot autofill the station playlist");
+						else if (station.autofill.playlists.indexOf(payload.playlistId) !== -1)
+							next("This playlist is already autofilling");
 						else if (station.blacklist.indexOf(payload.playlistId) !== -1)
-							next(
-								"This playlist is currently blacklisted, please remove it from there before including it"
-							);
+							next("This playlist is currently blacklisted");
 						else
 							PlaylistsModule.runJob("GET_PLAYLIST", { playlistId: payload.playlistId }, this)
 								.then(() => {
@@ -1191,7 +1178,7 @@ class _StationsModule extends CoreClass {
 						).then(stationModel => {
 							stationModel.updateOne(
 								{ _id: payload.stationId },
-								{ $push: { includedPlaylists: payload.playlistId } },
+								{ $push: { "autofill.playlists": payload.playlistId } },
 								next
 							);
 						});
@@ -1224,14 +1211,14 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Removes a playlist that is included in a station
+	 * Removes a playlist from autofill
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {object} payload.stationId - the id of the station
 	 * @param {object} payload.playlistId - the id of the playlist
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	REMOVE_INCLUDED_PLAYLIST(payload) {
+	REMOVE_AUTOFILL_PLAYLIST(payload) {
 		return new Promise((resolve, reject) => {
 			async.waterfall(
 				[
@@ -1250,8 +1237,8 @@ class _StationsModule extends CoreClass {
 					},
 
 					(station, next) => {
-						if (station.includedPlaylists.indexOf(payload.playlistId) === -1)
-							next("This playlist isn't included");
+						if (station.autofill.playlists.indexOf(payload.playlistId) === -1)
+							next("This playlist isn't autofilling");
 						else next();
 					},
 
@@ -1265,7 +1252,7 @@ class _StationsModule extends CoreClass {
 						).then(stationModel => {
 							stationModel.updateOne(
 								{ _id: payload.stationId },
-								{ $pull: { includedPlaylists: payload.playlistId } },
+								{ $pull: { "autofill.playlists": payload.playlistId } },
 								next
 							);
 						});
@@ -1327,9 +1314,9 @@ class _StationsModule extends CoreClass {
 						if (station.playlist === payload.playlistId) next("You cannot blacklist the station playlist");
 						else if (station.blacklist.indexOf(payload.playlistId) !== -1)
 							next("This playlist is already blacklisted");
-						else if (station.includedPlaylists.indexOf(payload.playlistId) !== -1)
+						else if (station.autofill.playlists.indexOf(payload.playlistId) !== -1)
 							next(
-								"This playlist is currently included, please remove it from there before blacklisting it"
+								"This playlist is currently autofilling, please remove it from there before blacklisting it"
 							);
 						else
 							PlaylistsModule.runJob("GET_PLAYLIST", { playlistId: payload.playlistId }, this)
@@ -1456,13 +1443,13 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Removes included or blacklisted playlist from a station
+	 * Removes autofilled or blacklisted playlist from a station
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.playlistId - the playlist id
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
-	REMOVE_INCLUDED_OR_BLACKLISTED_PLAYLIST_FROM_STATIONS(payload) {
+	REMOVE_AUTOFILLED_OR_BLACKLISTED_PLAYLIST_FROM_STATIONS(payload) {
 		return new Promise((resolve, reject) => {
 			async.waterfall(
 				[
@@ -1474,11 +1461,11 @@ class _StationsModule extends CoreClass {
 					next => {
 						StationsModule.stationModel.updateMany(
 							{
-								$or: [{ includedPlaylists: payload.playlistId }, { blacklist: payload.playlistId }]
+								$or: [{ "autofill.playlists": payload.playlistId }, { blacklist: payload.playlistId }]
 							},
 							{
 								$pull: {
-									includedPlaylists: payload.playlistId,
+									"autofill.playlists": payload.playlistId,
 									blacklist: payload.playlistId
 								}
 							},
@@ -1502,13 +1489,13 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Gets stations that include or blacklist a specific playlist
+	 * Gets stations that autofill or blacklist a specific playlist
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.playlistId - the playlist id
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
-	GET_STATIONS_THAT_INCLUDE_OR_BLACKLIST_PLAYLIST(payload) {
+	GET_STATIONS_THAT_AUTOFILL_OR_BLACKLIST_PLAYLIST(payload) {
 		return new Promise((resolve, reject) => {
 			DBModule.runJob(
 				"GET_MODEL",
@@ -1519,7 +1506,7 @@ class _StationsModule extends CoreClass {
 			).then(stationModel => {
 				stationModel.find(
 					{
-						$or: [{ includedPlaylists: payload.playlistId }, { blacklist: payload.playlistId }]
+						$or: [{ "autofill.playlists": payload.playlistId }, { blacklist: payload.playlistId }]
 					},
 					(err, stations) => {
 						if (err) reject(err);
@@ -1579,19 +1566,19 @@ class _StationsModule extends CoreClass {
 	}
 
 	/**
-	 * Clears and refills a station queue
+	 * Resets a station queue
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.stationId - the station id
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	CLEAR_AND_REFILL_STATION_QUEUE(payload) {
+	RESET_QUEUE(payload) {
 		return new Promise((resolve, reject) => {
 			async.waterfall(
 				[
 					next => {
 						StationsModule.runJob(
-							"FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST",
+							"AUTOFILL_STATION",
 							{ stationId: payload.stationId, ignoreExistingQueue: true },
 							this
 						)
@@ -1605,8 +1592,28 @@ class _StationsModule extends CoreClass {
 								next();
 							})
 							.catch(err => {
-								next(err);
+								if (err === "Autofill is disabled in this station")
+									StationsModule.stationModel
+										.updateOne({ _id: payload.stationId }, { $set: { queue: [] } }, this)
+										.then(() => next())
+										.catch(next);
+								else next(err);
 							});
+					},
+
+					next => {
+						StationsModule.runJob("UPDATE_STATION", { stationId: payload.stationId }, this)
+							.then(() => next())
+							.catch(next);
+					},
+
+					next => {
+						CacheModule.runJob("PUB", {
+							channel: "station.queueUpdate",
+							value: payload.stationId
+						})
+							.then(() => next())
+							.catch(next);
 					}
 				],
 				err => {

+ 9 - 16
frontend/src/components/PlaylistTabBase.vue

@@ -451,7 +451,7 @@
 					</playlist-item>
 				</div>
 				<p v-else class="has-text-centered scrollable-list">
-					No playlists currently included.
+					No playlists currently {{ label("present") }}.
 				</p>
 			</div>
 			<div
@@ -722,7 +722,7 @@ export default {
 		}),
 		...mapState("modals/manageStation", {
 			originalStation: state => state.originalStation,
-			includedPlaylists: state => state.includedPlaylists
+			autofill: state => state.autofill
 		}),
 		...mapState("station", {
 			autoRequest: state => state.autoRequest
@@ -751,12 +751,13 @@ export default {
 
 			if (this.type === "autofill")
 				this.socket.dispatch(
-					`stations.getStationIncludedPlaylistsById`,
+					`stations.getStationAutofillPlaylistsById`,
 					this.station._id,
 					res => {
 						if (res.status === "success") {
-							this.station.includedPlaylists = res.data.playlists;
-							this.originalStation.includedPlaylists =
+							this.station.autofill.playlists =
+								res.data.playlists;
+							this.originalStation.autofill.playlists =
 								res.data.playlists;
 						}
 					}
@@ -808,7 +809,7 @@ export default {
 		selectedPlaylists(typeOverwrite) {
 			const type = typeOverwrite || this.type;
 
-			if (type === "autofill") return this.includedPlaylists;
+			if (type === "autofill") return this.autofill;
 			if (type === "blacklist") return this.blacklist;
 			if (type === "autorequest") return this.autoRequest;
 			return [];
@@ -824,7 +825,7 @@ export default {
 			if (type === "autofill")
 				return new Promise(resolve => {
 					this.socket.dispatch(
-						"stations.includePlaylist",
+						"stations.autofillPlaylist",
 						this.station._id,
 						playlist._id,
 						res => {
@@ -868,7 +869,7 @@ export default {
 			if (type === "autofill")
 				return new Promise(resolve => {
 					this.socket.dispatch(
-						"stations.removeIncludedPlaylist",
+						"stations.removeAutofillPlaylist",
 						this.station._id,
 						playlistId,
 						res => {
@@ -977,14 +978,6 @@ export default {
 	color: var(--dark-red);
 }
 
-.included-icon {
-	color: var(--green);
-}
-
-.selected-icon {
-	color: var(--purple);
-}
-
 .playlist-tab-base {
 	.top-info {
 		font-size: 15px;

+ 1 - 1
frontend/src/components/Queue.vue

@@ -66,7 +66,7 @@
 				</template>
 			</draggable>
 		</div>
-		<p class="nothing-here-text" v-else>
+		<p class="nothing-here-text has-text-centered" v-else>
 			There are no songs currently queued
 		</p>
 	</div>

+ 3 - 8
frontend/src/components/modals/ManageStation/Settings.vue

@@ -41,6 +41,7 @@
 			</div>
 
 			<div
+				v-if="local.requests"
 				class="requests-settings"
 				:class="{ enabled: local.requests.enabled }"
 			>
@@ -93,6 +94,7 @@
 			</div>
 
 			<div
+				v-if="local.autofill"
 				class="autofill-settings"
 				:class="{ enabled: local.autofill.enabled }"
 			>
@@ -190,14 +192,7 @@ export default {
 		})
 	},
 	mounted() {
-		this.local = {
-			...this.station,
-			autofill: {
-				enabled: true,
-				limit: 30,
-				mode: this.station.playMode
-			}
-		};
+		this.local = this.station;
 	},
 	methods: {
 		update() {

+ 24 - 20
frontend/src/components/modals/ManageStation/index.vue

@@ -91,7 +91,9 @@
 								Settings
 							</button>
 							<button
-								v-if="isOwnerOrAdmin()"
+								v-if="
+									isOwnerOrAdmin() && station.autofill.enabled
+								"
 								class="button is-default"
 								:class="{ selected: tab === 'autofill' }"
 								ref="autofill-tab"
@@ -124,7 +126,7 @@
 							v-show="tab === 'settings'"
 						/>
 						<playlist-tab-base
-							v-if="isOwnerOrAdmin()"
+							v-if="isOwnerOrAdmin() && station.autofill.enabled"
 							class="tab"
 							v-show="tab === 'autofill'"
 							:type="'autofill'"
@@ -170,10 +172,8 @@
 		</template>
 		<template #footer>
 			<div v-if="isOwnerOrAdmin()" class="right">
-				<quick-confirm @confirm="clearAndRefillStationQueue()">
-					<a class="button is-danger">
-						Clear and refill station queue
-					</a>
+				<quick-confirm @confirm="resetQueue()">
+					<a class="button is-danger">Reset queue</a>
 				</quick-confirm>
 				<quick-confirm @confirm="removeStation()">
 					<button class="button is-danger">Delete station</button>
@@ -223,7 +223,7 @@ export default {
 			originalStation: state => state.originalStation,
 			songsList: state => state.songsList,
 			stationPlaylist: state => state.stationPlaylist,
-			includedPlaylists: state => state.includedPlaylists,
+			autofill: state => state.autofill,
 			blacklist: state => state.blacklist,
 			stationPaused: state => state.stationPaused,
 			currentSong: state => state.currentSong
@@ -239,6 +239,11 @@ export default {
 				if (this.isOwnerOrAdmin()) this.showTab("settings");
 				else this.closeModal("manageStation");
 			}
+		},
+		// eslint-disable-next-line
+		"station.autofill": function (autofill) {
+			if (this.tab === "autofill" && autofill && !autofill.enabled)
+				this.showTab("settings");
 		}
 	},
 	mounted() {
@@ -258,11 +263,11 @@ export default {
 				this.updateStationPaused(res.data.station.paused);
 
 				this.socket.dispatch(
-					"stations.getStationIncludedPlaylistsById",
+					"stations.getStationAutofillPlaylistsById",
 					this.stationId,
 					res => {
 						if (res.status === "success")
-							this.setIncludedPlaylists(res.data.playlists);
+							this.setAutofillPlaylists(res.data.playlists);
 					}
 				);
 
@@ -311,14 +316,13 @@ export default {
 				);
 
 				this.socket.on(
-					"event:station.includedPlaylist",
+					"event:station.autofillPlaylist",
 					res => {
 						const { playlist } = res.data;
-						const playlistIndex = this.includedPlaylists
-							.map(includedPlaylist => includedPlaylist._id)
+						const playlistIndex = this.autofill
+							.map(autofillPlaylist => autofillPlaylist._id)
 							.indexOf(playlist._id);
-						if (playlistIndex === -1)
-							this.includedPlaylists.push(playlist);
+						if (playlistIndex === -1) this.autofill.push(playlist);
 					},
 					{ modal: "manageStation" }
 				);
@@ -336,14 +340,14 @@ export default {
 				);
 
 				this.socket.on(
-					"event:station.removedIncludedPlaylist",
+					"event:station.removedAutofillPlaylist",
 					res => {
 						const { playlistId } = res.data;
-						const playlistIndex = this.includedPlaylists
+						const playlistIndex = this.autofill
 							.map(playlist => playlist._id)
 							.indexOf(playlistId);
 						if (playlistIndex >= 0)
-							this.includedPlaylists.splice(playlistIndex, 1);
+							this.autofill.splice(playlistIndex, 1);
 					},
 					{ modal: "manageStation" }
 				);
@@ -555,9 +559,9 @@ export default {
 				}
 			);
 		},
-		clearAndRefillStationQueue() {
+		resetQueue() {
 			this.socket.dispatch(
-				"stations.clearAndRefillStationQueue",
+				"stations.resetQueue",
 				this.station._id,
 				res => {
 					if (res.status !== "success")
@@ -571,7 +575,7 @@ export default {
 		},
 		...mapActions("modals/manageStation", [
 			"editStation",
-			"setIncludedPlaylists",
+			"setAutofillPlaylists",
 			"setBlacklist",
 			"clearStation",
 			"updateSongsList",

+ 11 - 12
frontend/src/pages/Station/Sidebar/Playlists.vue

@@ -108,7 +108,7 @@ export default {
 		currentPlaylists() {
 			if (this.station.type === "community") return this.autoRequest;
 
-			return this.includedPlaylists;
+			return this.autofill;
 		},
 		...mapState({
 			role: state => state.user.auth.role,
@@ -117,7 +117,7 @@ export default {
 		}),
 		...mapState("station", {
 			autoRequest: state => state.autoRequest,
-			includedPlaylists: state => state.includedPlaylists,
+			autofill: state => state.autofill,
 			blacklist: state => state.blacklist,
 			songsList: state => state.songsList
 		}),
@@ -128,12 +128,12 @@ export default {
 	mounted() {
 		ws.onConnect(this.init);
 
-		this.socket.on("event:station.includedPlaylist", res => {
+		this.socket.on("event:station.autofillPlaylist", res => {
 			const { playlist } = res.data;
-			const playlistIndex = this.includedPlaylists
-				.map(includedPlaylist => includedPlaylist._id)
+			const playlistIndex = this.autofill
+				.map(autofillPlaylist => autofillPlaylist._id)
 				.indexOf(playlist._id);
-			if (playlistIndex === -1) this.includedPlaylists.push(playlist);
+			if (playlistIndex === -1) this.autofill.push(playlist);
 		});
 
 		this.socket.on("event:station.blacklistedPlaylist", res => {
@@ -144,13 +144,12 @@ export default {
 			if (playlistIndex === -1) this.blacklist.push(playlist);
 		});
 
-		this.socket.on("event:station.removedIncludedPlaylist", res => {
+		this.socket.on("event:station.removedAutofillPlaylist", res => {
 			const { playlistId } = res.data;
-			const playlistIndex = this.includedPlaylists
+			const playlistIndex = this.autofill
 				.map(playlist => playlist._id)
 				.indexOf(playlistId);
-			if (playlistIndex >= 0)
-				this.includedPlaylists.splice(playlistIndex, 1);
+			if (playlistIndex >= 0) this.autofill.splice(playlistIndex, 1);
 		});
 
 		this.socket.on("event:station.removedBlacklistedPlaylist", res => {
@@ -195,7 +194,7 @@ export default {
 				}
 			} else {
 				this.socket.dispatch(
-					"stations.includePlaylist",
+					"stations.autofillPlaylist",
 					this.station._id,
 					playlist._id,
 					res => {
@@ -223,7 +222,7 @@ export default {
 					}
 				} else {
 					this.socket.dispatch(
-						"stations.removeIncludedPlaylist",
+						"stations.removeAutofillPlaylist",
 						this.station._id,
 						id,
 						res => {

+ 6 - 22
frontend/src/pages/Station/index.vue

@@ -898,7 +898,7 @@ export default {
 			localPaused: state => state.localPaused,
 			noSong: state => state.noSong,
 			autoRequest: state => state.autoRequest,
-			includedPlaylists: state => state.includedPlaylists,
+			autofill: state => state.autofill,
 			blacklist: state => state.blacklist
 		}),
 		...mapState({
@@ -1120,16 +1120,6 @@ export default {
 				});
 		});
 
-		this.socket.on("event:privatePlaylist.selected", res => {
-			if (this.station.type === "community")
-				this.station.privatePlaylist = res.data.playlistId;
-		});
-
-		this.socket.on("event:privatePlaylist.deselected", () => {
-			if (this.station.type === "community")
-				this.station.privatePlaylist = null;
-		});
-
 		this.socket.on("event:station.updated", async res => {
 			const { name, theme } = res.data.station;
 
@@ -1888,12 +1878,9 @@ export default {
 							description,
 							privacy,
 							owner,
-							privatePlaylist,
-							includedPlaylists,
+							autofill,
 							blacklist,
 							type,
-							genres,
-							blacklistedGenres,
 							isFavorited,
 							theme,
 							requests
@@ -1912,12 +1899,9 @@ export default {
 							description,
 							privacy,
 							owner,
-							privatePlaylist,
-							includedPlaylists,
+							autofill,
 							blacklist,
 							type,
-							genres,
-							blacklistedGenres,
 							isFavorited,
 							theme,
 							requests
@@ -1939,11 +1923,11 @@ export default {
 						this.updateUsers(res.data.users);
 
 						this.socket.dispatch(
-							"stations.getStationIncludedPlaylistsById",
+							"stations.getStationAutofillPlaylistsById",
 							this.station._id,
 							res => {
 								if (res.status === "success") {
-									this.setIncludedPlaylists(
+									this.setAutofillPlaylists(
 										res.data.playlists
 									);
 								}
@@ -2217,7 +2201,7 @@ export default {
 			"updateLocalPaused",
 			"updateNoSong",
 			"updateIfStationIsFavorited",
-			"setIncludedPlaylists",
+			"setAutofillPlaylists",
 			"setBlacklist",
 			"updateCurrentSongRatings",
 			"updateOwnCurrentSongRatings",

+ 6 - 8
frontend/src/store/modules/modals/manageStation.js

@@ -7,7 +7,7 @@ export default {
 		originalStation: {},
 		station: {},
 		stationPlaylist: { songs: [] },
-		includedPlaylists: [],
+		autofill: [],
 		blacklist: [],
 		songsList: [],
 		stationPaused: true,
@@ -17,8 +17,8 @@ export default {
 	actions: {
 		showTab: ({ commit }, tab) => commit("showTab", tab),
 		editStation: ({ commit }, station) => commit("editStation", station),
-		setIncludedPlaylists: ({ commit }, includedPlaylists) =>
-			commit("setIncludedPlaylists", includedPlaylists),
+		setAutofillPlaylists: ({ commit }, autofillPlaylists) =>
+			commit("setAutofillPlaylists", autofillPlaylists),
 		setBlacklist: ({ commit }, blacklist) =>
 			commit("setBlacklist", blacklist),
 		clearStation: ({ commit }) => commit("clearStation"),
@@ -42,10 +42,8 @@ export default {
 			state.originalStation = JSON.parse(JSON.stringify(station));
 			state.station = JSON.parse(JSON.stringify(station));
 		},
-		setIncludedPlaylists(state, includedPlaylists) {
-			state.includedPlaylists = JSON.parse(
-				JSON.stringify(includedPlaylists)
-			);
+		setAutofillPlaylists(state, autofillPlaylists) {
+			state.autofill = JSON.parse(JSON.stringify(autofillPlaylists));
 		},
 		setBlacklist(state, blacklist) {
 			state.blacklist = JSON.parse(JSON.stringify(blacklist));
@@ -54,7 +52,7 @@ export default {
 			state.originalStation = {};
 			state.station = {};
 			state.stationPlaylist = { songs: [] };
-			state.includedPlaylists = [];
+			state.autofill = [];
 			state.blacklist = [];
 			state.songsList = [];
 			state.stationPaused = true;

+ 6 - 6
frontend/src/store/modules/station.js

@@ -16,7 +16,7 @@ const state = {
 	stationPaused: true,
 	localPaused: false,
 	noSong: true,
-	includedPlaylists: [],
+	autofill: [],
 	blacklist: []
 };
 
@@ -71,8 +71,8 @@ const actions = {
 	updateIfStationIsFavorited: ({ commit }, { isFavorited }) => {
 		commit("updateIfStationIsFavorited", isFavorited);
 	},
-	setIncludedPlaylists: ({ commit }, includedPlaylists) => {
-		commit("setIncludedPlaylists", includedPlaylists);
+	setAutofillPlaylists: ({ commit }, autofillPlaylists) => {
+		commit("setAutofillPlaylists", autofillPlaylists);
 	},
 	setBlacklist: ({ commit }, blacklist) => {
 		commit("setBlacklist", blacklist);
@@ -111,7 +111,7 @@ const mutations = {
 		state.stationPaused = true;
 		state.localPaused = false;
 		state.noSong = true;
-		state.includedPlaylists = [];
+		state.autofill = [];
 		state.blacklist = [];
 	},
 	editStation(state, station) {
@@ -170,8 +170,8 @@ const mutations = {
 	updateIfStationIsFavorited(state, isFavorited) {
 		state.station.isFavorited = isFavorited;
 	},
-	setIncludedPlaylists(state, includedPlaylists) {
-		state.includedPlaylists = JSON.parse(JSON.stringify(includedPlaylists));
+	setAutofillPlaylists(state, autofillPlaylists) {
+		state.autofill = JSON.parse(JSON.stringify(autofillPlaylists));
 	},
 	setBlacklist(state, blacklist) {
 		state.blacklist = JSON.parse(JSON.stringify(blacklist));