From 5187d0e55f47385c6967de635a7ab301a73f6352 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 24 May 2022 18:38:25 -0500 Subject: [PATCH 1/2] Add:Option to hard delete podcast episode from file system #488 --- .../modals/podcast/RemoveEpisode.vue | 90 +++++++++++++++++++ .../tables/podcast/EpisodeTableRow.vue | 27 +----- .../tables/podcast/EpisodesTable.vue | 40 +++++++-- server/controllers/LibraryItemController.js | 18 ---- server/controllers/PodcastController.js | 29 ++++++ server/routers/ApiRouter.js | 2 +- 6 files changed, 153 insertions(+), 53 deletions(-) create mode 100644 client/components/modals/podcast/RemoveEpisode.vue diff --git a/client/components/modals/podcast/RemoveEpisode.vue b/client/components/modals/podcast/RemoveEpisode.vue new file mode 100644 index 00000000..b7839dae --- /dev/null +++ b/client/components/modals/podcast/RemoveEpisode.vue @@ -0,0 +1,90 @@ + + + diff --git a/client/components/tables/podcast/EpisodeTableRow.vue b/client/components/tables/podcast/EpisodeTableRow.vue index b4adeb49..0e318858 100644 --- a/client/components/tables/podcast/EpisodeTableRow.vue +++ b/client/components/tables/podcast/EpisodeTableRow.vue @@ -45,7 +45,6 @@ export default { type: Object, default: () => {} } - // isDragging: Boolean }, data() { return { @@ -54,15 +53,6 @@ export default { isHovering: false } }, - // watch: { - // isDragging: { - // handler(newVal) { - // if (newVal) { - // this.isHovering = false - // } - // } - // } - // }, computed: { userCanUpdate() { return this.$store.getters['user/getUserCanUpdate'] @@ -149,22 +139,7 @@ export default { }) }, removeClick() { - if (confirm(`Are you sure you want to remove episode ${this.title}?\nNote: Does not delete from file system`)) { - this.processingRemove = true - - this.$axios - .$delete(`/api/items/${this.libraryItemId}/episode/${this.episode.id}`) - .then((updatedPodcast) => { - console.log(`Episode removed from podcast`, updatedPodcast) - this.$toast.success('Episode removed from podcast') - this.processingRemove = false - }) - .catch((error) => { - console.error('Failed to remove episode from podcast', error) - this.$toast.error('Failed to remove episode from podcast') - this.processingRemove = false - }) - } + this.$emit('remove', this.episode) } } } diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue index 56546f69..6401c893 100644 --- a/client/components/tables/podcast/EpisodesTable.vue +++ b/client/components/tables/podcast/EpisodesTable.vue @@ -3,15 +3,14 @@

Episodes

- -
- -
+

No Episodes

- @@ -25,8 +24,16 @@ export default { }, data() { return { + episodesCopy: [], sortKey: 'publishedAt', - sortDesc: true + sortDesc: true, + selectedEpisode: null, + showPodcastRemoveModal: false + } + }, + watch: { + libraryItem() { + this.init() } }, computed: { @@ -41,16 +48,33 @@ export default { }, episodes() { return this.media.episodes || [] + }, + episodesSorted() { + return this.episodesCopy.sort((a, b) => { + if (this.sortDesc) { + return String(b[this.sortKey]).localeCompare(String(a[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) + } + return String(a[this.sortKey]).localeCompare(String(b[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) + }) } }, methods: { + removeEpisode(episode) { + this.selectedEpisode = episode + this.showPodcastRemoveModal = true + }, editEpisode(episode) { this.$store.commit('setSelectedLibraryItem', this.libraryItem) this.$store.commit('globals/setSelectedEpisode', episode) this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) + }, + init() { + this.episodesCopy = this.episodes.map((ep) => ({ ...ep })) } }, - mounted() {} + mounted() { + this.init() + } } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index b920de46..070ce416 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -224,24 +224,6 @@ class LibraryItemController { res.json(libraryItem.toJSON()) } - // DELETE: api/items/:id/episode/:episodeId - async removeEpisode(req, res) { - var episodeId = req.params.episodeId - var libraryItem = req.libraryItem - if (libraryItem.mediaType !== 'podcast') { - Logger.error(`[LibraryItemController] removeEpisode invalid media type ${libraryItem.id}`) - return res.sendStatus(500) - } - if (!libraryItem.media.episodes.find(ep => ep.id === episodeId)) { - Logger.error(`[LibraryItemController] removeEpisode episode ${episodeId} not found for item ${libraryItem.id}`) - return res.sendStatus(404) - } - libraryItem.media.removeEpisode(episodeId) - await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) - res.json(libraryItem.toJSON()) - } - // POST api/items/:id/match async match(req, res) { var libraryItem = req.libraryItem diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index e83e108f..a0f14eda 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -190,6 +190,35 @@ class PodcastController { res.json(libraryItem.toJSONExpanded()) } + // 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' + + var 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 + // 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}"`) + }).catch((error) => { + Logger.error(`[PodcastController] Failed to hard delete episode file at "${audioFile.metadata.path}"`, error) + }) + } + + libraryItem.media.removeEpisode(episodeId) + + await this.db.updateLibraryItem(libraryItem) + this.emitter('item_updated', libraryItem.toJSONExpanded()) + res.json(libraryItem.toJSON()) + } + middleware(req, res, next) { var item = this.db.libraryItems.find(li => li.id === req.params.id) if (!item || !item.media) return res.sendStatus(404) diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 8af4d9f6..3c3e5333 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -90,7 +90,6 @@ class ApiRouter { this.router.post('/items/:id/play', LibraryItemController.middleware.bind(this), LibraryItemController.startPlaybackSession.bind(this)) this.router.post('/items/:id/play/:episodeId', LibraryItemController.middleware.bind(this), LibraryItemController.startEpisodePlaybackSession.bind(this)) this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this)) - this.router.delete('/items/:id/episode/:episodeId', LibraryItemController.middleware.bind(this), LibraryItemController.removeEpisode.bind(this)) this.router.get('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this)) this.router.get('/items/:id/audio-metadata', LibraryItemController.middleware.bind(this), LibraryItemController.updateAudioFileMetadata.bind(this)) this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this)) @@ -188,6 +187,7 @@ class ApiRouter { this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this)) this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this)) this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this)) + this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this)) // // Misc Routes From d2e012d7b1d8d2f0680d5c7933096415937f13fe Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 24 May 2022 19:19:16 -0500 Subject: [PATCH 2/2] Version bump 2.0.16 --- client/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index 35541e86..d212526a 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.0.15", + "version": "2.0.16", "description": "Self-hosted audiobook and podcast client", "main": "index.js", "scripts": { diff --git a/package.json b/package.json index babba376..32946801 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.0.15", + "version": "2.0.16", "description": "Self-hosted audiobook and podcast server", "main": "index.js", "scripts": {