mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update:Load playlists only when needed & remove podcast episode from playlist when deleted
This commit is contained in:
		
							parent
							
								
									5a9eed0a5a
								
							
						
					
					
						commit
						710a62c2af
					
				| @ -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) { | ||||
|  | ||||
| @ -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: [], | ||||
|  | ||||
| @ -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') | ||||
|       } | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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) | ||||
|     } | ||||
|  | ||||
| @ -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({ | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user