Update:Load playlists only when needed & remove podcast episode from playlist when deleted

This commit is contained in:
advplyr 2023-07-23 09:42:57 -05:00
parent 5a9eed0a5a
commit 710a62c2af
7 changed files with 178 additions and 23 deletions

View File

@ -19,7 +19,6 @@ class Database {
// TODO: below data should be loaded from the DB as needed
this.libraryItems = []
this.settings = []
this.playlists = []
this.authors = []
this.series = []
@ -160,9 +159,6 @@ class Database {
this.libraryItems = await this.models.libraryItem.loadAllLibraryItems()
Logger.info(`[Database] Loaded ${this.libraryItems.length} library items`)
this.playlists = await this.models.playlist.getOldPlaylists()
Logger.info(`[Database] Loaded ${this.playlists.length} playlists`)
this.authors = await this.models.author.getOldAuthors()
Logger.info(`[Database] Loaded ${this.authors.length} authors`)
@ -341,7 +337,6 @@ class Database {
await this.createBulkPlaylistMediaItems(playlistMediaItems)
}
}
this.playlists.push(oldPlaylist)
}
updatePlaylist(oldPlaylist) {
@ -364,7 +359,6 @@ class Database {
async removePlaylist(playlistId) {
if (!this.sequelize) return false
await this.models.playlist.removeById(playlistId)
this.playlists = this.playlists.filter(p => p.id !== playlistId)
}
createPlaylistMediaItem(playlistMediaItem) {

View File

@ -83,7 +83,7 @@ class LibraryController {
return res.json({
filterdata: libraryHelpers.getDistinctFilterDataNew(req.libraryItems),
issues: req.libraryItems.filter(li => li.hasIssues).length,
numUserPlaylists: Database.playlists.filter(p => p.userId === req.user.id && p.libraryId === req.library.id).length,
numUserPlaylists: await Database.models.playlist.getNumPlaylistsForUserAndLibrary(req.user.id, req.library.id),
library: req.library
})
}
@ -557,7 +557,8 @@ class LibraryController {
// api/libraries/:id/playlists
async getUserPlaylistsForLibrary(req, res) {
let playlistsForUser = Database.playlists.filter(p => p.userId === req.user.id && p.libraryId === req.library.id).map(p => p.toJSONExpanded(Database.libraryItems))
let playlistsForUser = await Database.models.playlist.getPlaylistsForUserAndLibrary(req.user.id, req.library.id)
playlistsForUser = playlistsForUser.map(p => p.toJSONExpanded(Database.libraryItems))
const payload = {
results: [],

View File

@ -22,9 +22,10 @@ class PlaylistController {
}
// GET: api/playlists
findAllForUser(req, res) {
async findAllForUser(req, res) {
const playlistsForUser = await Database.models.playlist.getPlaylistsForUserAndLibrary(req.user.id)
res.json({
playlists: Database.playlists.filter(p => p.userId === req.user.id).map(p => p.toJSONExpanded(Database.libraryItems))
playlists: playlistsForUser.map(p => p.toJSONExpanded(Database.libraryItems))
})
}
@ -231,9 +232,9 @@ class PlaylistController {
res.json(jsonExpanded)
}
middleware(req, res, next) {
async middleware(req, res, next) {
if (req.params.id) {
const playlist = Database.playlists.find(p => p.id === req.params.id)
const playlist = await Database.models.playlist.getById(req.params.id)
if (!playlist) {
return res.status(404).send('Playlist not found')
}

View File

@ -241,18 +241,18 @@ class PodcastController {
// DELETE: api/podcasts/:id/episode/:episodeId
async removeEpisode(req, res) {
var episodeId = req.params.episodeId
var libraryItem = req.libraryItem
var hardDelete = req.query.hard === '1'
const episodeId = req.params.episodeId
const libraryItem = req.libraryItem
const hardDelete = req.query.hard === '1'
var episode = libraryItem.media.episodes.find(ep => ep.id === episodeId)
const episode = libraryItem.media.episodes.find(ep => ep.id === episodeId)
if (!episode) {
Logger.error(`[PodcastController] removeEpisode episode ${episodeId} not found for item ${libraryItem.id}`)
return res.sendStatus(404)
}
if (hardDelete) {
var audioFile = episode.audioFile
const audioFile = episode.audioFile
// TODO: this will trigger the watcher. should maybe handle this gracefully
await fs.remove(audioFile.metadata.path).then(() => {
Logger.info(`[PodcastController] Hard deleted episode file at "${audioFile.metadata.path}"`)
@ -267,6 +267,22 @@ class PodcastController {
libraryItem.removeLibraryFile(episodeRemoved.audioFile.ino)
}
// Update/remove playlists that had this podcast episode
const playlistsWithEpisode = await Database.models.playlist.getPlaylistsForMediaItemIds([episodeId])
for (const playlist of playlistsWithEpisode) {
playlist.removeItem(libraryItem.id, episodeId)
// If playlist is now empty then remove it
if (!playlist.items.length) {
Logger.info(`[PodcastController] Playlist "${playlist.name}" has no more items - removing it`)
await Database.removePlaylist(playlist.id)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', playlist.toJSONExpanded(Database.libraryItems))
} else {
await Database.updatePlaylist(playlist)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', playlist.toJSONExpanded(Database.libraryItems))
}
}
await Database.updateLibraryItem(libraryItem)
SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
res.json(libraryItem.toJSON())

View File

@ -122,7 +122,7 @@ class UserController {
// Todo: check if user is logged in and cancel streams
// Remove user playlists
const userPlaylists = Database.playlists.filter(p => p.userId === user.id)
const userPlaylists = await Database.models.playlist.getPlaylistsForUserAndLibrary(user.id)
for (const playlist of userPlaylists) {
await Database.removePlaylist(playlist.id)
}

View File

@ -1,4 +1,4 @@
const { DataTypes, Model } = require('sequelize')
const { DataTypes, Model, Op } = require('sequelize')
const Logger = require('../Logger')
const oldPlaylist = require('../objects/Playlist')
@ -119,6 +119,146 @@ module.exports = (sequelize) => {
}
})
}
/**
* Get playlist by id
* @param {string} playlistId
* @returns {Promise<oldPlaylist|null>} returns null if not found
*/
static async getById(playlistId) {
if (!playlistId) return null
const playlist = await this.findByPk(playlistId, {
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
if (!playlist) return null
return this.getOldPlaylist(playlist)
}
/**
* Get playlists for user and optionally for library
* @param {string} userId
* @param {[string]} libraryId optional
* @returns {Promise<oldPlaylist[]>}
*/
static async getPlaylistsForUserAndLibrary(userId, libraryId = null) {
if (!userId && !libraryId) return []
const whereQuery = {}
if (userId) {
whereQuery.userId = userId
}
if (libraryId) {
whereQuery.libraryId = libraryId
}
const playlists = await this.findAll({
where: whereQuery,
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
return playlists.map(p => this.getOldPlaylist(p))
}
/**
* Get number of playlists for a user and library
* @param {string} userId
* @param {string} libraryId
* @returns
*/
static async getNumPlaylistsForUserAndLibrary(userId, libraryId) {
return this.count({
where: {
userId,
libraryId
}
})
}
/**
* Get all playlists for mediaItemIds
* @param {string[]} mediaItemIds
* @returns {Promise<oldPlaylist[]>}
*/
static async getPlaylistsForMediaItemIds(mediaItemIds) {
if (!mediaItemIds?.length) return []
const playlistMediaItemsExpanded = await sequelize.models.playlistMediaItem.findAll({
where: {
mediaItemId: {
[Op.in]: mediaItemIds
}
},
include: [
{
model: sequelize.models.playlist,
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
}
}
],
order: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
})
return playlistMediaItemsExpanded.map(pmie => {
pmie.playlist.playlistMediaItems = pmie.playlist.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
return this.getOldPlaylist(pmie.playlist)
})
}
}
Playlist.init({

View File

@ -389,7 +389,7 @@ class ApiRouter {
}
// TODO: Remove open sessions for library item
let mediaItemIds = []
if (libraryItem.isBook) {
// remove book from collections
const collectionsWithBook = await Database.models.collection.getAllForBook(libraryItem.media.id)
@ -401,12 +401,15 @@ class ApiRouter {
// Check remove empty series
await this.checkRemoveEmptySeries(libraryItem.media.metadata.series, libraryItem.id)
mediaItemIds.push(libraryItem.media.id)
} else if (libraryItem.isPodcast) {
mediaItemIds.push(...libraryItem.media.episodes.map(ep => ep.id))
}
// remove item from playlists
const playlistsWithItem = Database.playlists.filter(p => p.hasItemsForLibraryItem(libraryItem.id))
for (let i = 0; i < playlistsWithItem.length; i++) {
const playlist = playlistsWithItem[i]
const playlistsWithItem = await Database.models.playlist.getPlaylistsForMediaItemIds(mediaItemIds)
for (const playlist of playlistsWithItem) {
playlist.removeItemsForLibraryItem(libraryItem.id)
// If playlist is now empty then remove it