mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #4041 from nichwall/podcast_queue_no_duplicates
Prevent duplicate episodes from being added to queue
This commit is contained in:
		
						commit
						e4a34b0145
					
				| @ -16,11 +16,12 @@ | |||||||
|           v-for="(episode, index) in episodesList" |           v-for="(episode, index) in episodesList" | ||||||
|           :key="index" |           :key="index" | ||||||
|           class="relative" |           class="relative" | ||||||
|           :class="getIsEpisodeDownloaded(episode) ? 'bg-primary bg-opacity-40' : selectedEpisodes[episode.cleanUrl] ? 'cursor-pointer bg-success bg-opacity-10' : index % 2 == 0 ? 'cursor-pointer bg-primary bg-opacity-25 hover:bg-opacity-40' : 'cursor-pointer bg-primary bg-opacity-5 hover:bg-opacity-25'" |           :class="episode.isDownloaded || episode.isDownloading ? 'bg-primary bg-opacity-40' : selectedEpisodes[episode.cleanUrl] ? 'cursor-pointer bg-success bg-opacity-10' : index % 2 == 0 ? 'cursor-pointer bg-primary bg-opacity-25 hover:bg-opacity-40' : 'cursor-pointer bg-primary bg-opacity-5 hover:bg-opacity-25'" | ||||||
|           @click="toggleSelectEpisode(episode)" |           @click="toggleSelectEpisode(episode)" | ||||||
|         > |         > | ||||||
|           <div class="absolute top-0 left-0 h-full flex items-center p-2"> |           <div class="absolute top-0 left-0 h-full flex items-center p-2"> | ||||||
|             <span v-if="getIsEpisodeDownloaded(episode)" class="material-symbols text-success text-xl">download_done</span> |             <span v-if="episode.isDownloaded" class="material-symbols text-success text-xl">download_done</span> | ||||||
|  |             <span v-else-if="episode.isDownloading" class="material-symbols text-warning text-xl">download</span> | ||||||
|             <ui-checkbox v-else v-model="selectedEpisodes[episode.cleanUrl]" small checkbox-bg="primary" border-color="gray-600" /> |             <ui-checkbox v-else v-model="selectedEpisodes[episode.cleanUrl]" small checkbox-bg="primary" border-color="gray-600" /> | ||||||
|           </div> |           </div> | ||||||
|           <div class="px-8 py-2"> |           <div class="px-8 py-2"> | ||||||
| @ -58,6 +59,14 @@ export default { | |||||||
|     episodes: { |     episodes: { | ||||||
|       type: Array, |       type: Array, | ||||||
|       default: () => [] |       default: () => [] | ||||||
|  |     }, | ||||||
|  |     downloadQueue: { | ||||||
|  |       type: Array, | ||||||
|  |       default: () => [] | ||||||
|  |     }, | ||||||
|  |     episodesDownloading: { | ||||||
|  |       type: Array, | ||||||
|  |       default: () => [] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
| @ -79,6 +88,21 @@ export default { | |||||||
|       handler(newVal) { |       handler(newVal) { | ||||||
|         if (newVal) this.init() |         if (newVal) this.init() | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     episodes: { | ||||||
|  |       handler(newVal) { | ||||||
|  |         if (newVal) this.updateEpisodeDownloadStatuses() | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     episodesDownloading: { | ||||||
|  |       handler(newVal) { | ||||||
|  |         if (newVal) this.updateEpisodeDownloadStatuses() | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     downloadQueue: { | ||||||
|  |       handler(newVal) { | ||||||
|  |         if (newVal) this.updateEpisodeDownloadStatuses() | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @ -132,6 +156,13 @@ export default { | |||||||
|       } |       } | ||||||
|       return false |       return false | ||||||
|     }, |     }, | ||||||
|  |     getIsEpisodeDownloadingOrQueued(episode) { | ||||||
|  |       const episodesToCheck = [...this.episodesDownloading, ...this.downloadQueue] | ||||||
|  |       if (episode.guid) { | ||||||
|  |         return episodesToCheck.some((download) => download.guid === episode.guid) | ||||||
|  |       } | ||||||
|  |       return episodesToCheck.some((download) => this.getCleanEpisodeUrl(download.url) === episode.cleanUrl) | ||||||
|  |     }, | ||||||
|     /** |     /** | ||||||
|      * UPDATE: As of v2.4.5 guid is used for matching existing downloaded episodes if it is found on the RSS feed. |      * UPDATE: As of v2.4.5 guid is used for matching existing downloaded episodes if it is found on the RSS feed. | ||||||
|      * Fallback to checking the clean url |      * Fallback to checking the clean url | ||||||
| @ -187,7 +218,7 @@ export default { | |||||||
|       this.selectAll = true |       this.selectAll = true | ||||||
|     }, |     }, | ||||||
|     toggleSelectEpisode(episode) { |     toggleSelectEpisode(episode) { | ||||||
|       if (this.getIsEpisodeDownloaded(episode)) return |       if (episode.isDownloaded || episode.isDownloading) return | ||||||
|       this.$set(this.selectedEpisodes, episode.cleanUrl, !this.selectedEpisodes[episode.cleanUrl]) |       this.$set(this.selectedEpisodes, episode.cleanUrl, !this.selectedEpisodes[episode.cleanUrl]) | ||||||
|       this.checkSetIsSelectedAll() |       this.checkSetIsSelectedAll() | ||||||
|     }, |     }, | ||||||
| @ -223,6 +254,23 @@ export default { | |||||||
|         }) |         }) | ||||||
|     }, |     }, | ||||||
|     init() { |     init() { | ||||||
|  |       this.updateDownloadedEpisodeMaps() | ||||||
|  | 
 | ||||||
|  |       this.episodesCleaned = this.episodes | ||||||
|  |         .filter((ep) => ep.enclosure?.url) | ||||||
|  |         .map((_ep) => { | ||||||
|  |           return { | ||||||
|  |             ..._ep, | ||||||
|  |             cleanUrl: this.getCleanEpisodeUrl(_ep.enclosure.url), | ||||||
|  |             isDownloading: this.getIsEpisodeDownloadingOrQueued(_ep), | ||||||
|  |             isDownloaded: this.getIsEpisodeDownloaded(_ep) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       this.episodesCleaned.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1)) | ||||||
|  |       this.selectAll = false | ||||||
|  |       this.selectedEpisodes = {} | ||||||
|  |     }, | ||||||
|  |     updateDownloadedEpisodeMaps() { | ||||||
|       this.downloadedEpisodeGuidMap = {} |       this.downloadedEpisodeGuidMap = {} | ||||||
|       this.downloadedEpisodeUrlMap = {} |       this.downloadedEpisodeUrlMap = {} | ||||||
| 
 | 
 | ||||||
| @ -230,18 +278,16 @@ export default { | |||||||
|         if (episode.guid) this.downloadedEpisodeGuidMap[episode.guid] = episode.id |         if (episode.guid) this.downloadedEpisodeGuidMap[episode.guid] = episode.id | ||||||
|         if (episode.enclosure?.url) this.downloadedEpisodeUrlMap[this.getCleanEpisodeUrl(episode.enclosure.url)] = episode.id |         if (episode.enclosure?.url) this.downloadedEpisodeUrlMap[this.getCleanEpisodeUrl(episode.enclosure.url)] = episode.id | ||||||
|       }) |       }) | ||||||
| 
 |     }, | ||||||
|       this.episodesCleaned = this.episodes |     updateEpisodeDownloadStatuses() { | ||||||
|         .filter((ep) => ep.enclosure?.url) |       this.updateDownloadedEpisodeMaps() | ||||||
|         .map((_ep) => { |       this.episodesCleaned = this.episodesCleaned.map((ep) => { | ||||||
|         return { |         return { | ||||||
|             ..._ep, |           ...ep, | ||||||
|             cleanUrl: this.getCleanEpisodeUrl(_ep.enclosure.url) |           isDownloading: this.getIsEpisodeDownloadingOrQueued(ep), | ||||||
|  |           isDownloaded: this.getIsEpisodeDownloaded(ep) | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       this.episodesCleaned.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1)) |  | ||||||
|       this.selectAll = false |  | ||||||
|       this.selectedEpisodes = {} |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() {} |   mounted() {} | ||||||
|  | |||||||
| @ -141,7 +141,7 @@ | |||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" /> |     <modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" :download-queue="episodeDownloadsQueued" :episodes-downloading="episodesDownloading" /> | ||||||
|     <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :playback-rate="1" :library-item-id="libraryItemId" hide-create @select="selectBookmark" /> |     <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :playback-rate="1" :library-item-id="libraryItemId" hide-create @select="selectBookmark" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @ -660,13 +660,11 @@ export default { | |||||||
|     }, |     }, | ||||||
|     rssFeedOpen(data) { |     rssFeedOpen(data) { | ||||||
|       if (data.entityId === this.libraryItemId) { |       if (data.entityId === this.libraryItemId) { | ||||||
|         console.log('RSS Feed Opened', data) |  | ||||||
|         this.rssFeed = data |         this.rssFeed = data | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     rssFeedClosed(data) { |     rssFeedClosed(data) { | ||||||
|       if (data.entityId === this.libraryItemId) { |       if (data.entityId === this.libraryItemId) { | ||||||
|         console.log('RSS Feed Closed', data) |  | ||||||
|         this.rssFeed = null |         this.rssFeed = null | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -72,6 +72,15 @@ class PodcastManager { | |||||||
|    */ |    */ | ||||||
|   async startPodcastEpisodeDownload(podcastEpisodeDownload) { |   async startPodcastEpisodeDownload(podcastEpisodeDownload) { | ||||||
|     if (this.currentDownload) { |     if (this.currentDownload) { | ||||||
|  |       // Prevent downloading episodes from the same URL for the same library item.
 | ||||||
|  |       // Allow downloading for different library items in case of the same podcast existing in multiple libraries (e.g. different folders)
 | ||||||
|  |       if (this.downloadQueue.some((d) => d.url === podcastEpisodeDownload.url && d.libraryItem.id === podcastEpisodeDownload.libraryItem.id)) { | ||||||
|  |         Logger.warn(`[PodcastManager] Episode already in queue: "${this.currentDownload.episodeTitle}"`) | ||||||
|  |         return | ||||||
|  |       } else if (this.currentDownload.url === podcastEpisodeDownload.url && this.currentDownload.libraryItem.id === podcastEpisodeDownload.libraryItem.id) { | ||||||
|  |         Logger.warn(`[PodcastManager] Episode download already in progress for "${podcastEpisodeDownload.episodeTitle}"`) | ||||||
|  |         return | ||||||
|  |       } | ||||||
|       this.downloadQueue.push(podcastEpisodeDownload) |       this.downloadQueue.push(podcastEpisodeDownload) | ||||||
|       SocketAuthority.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient()) |       SocketAuthority.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient()) | ||||||
|       return |       return | ||||||
|  | |||||||
| @ -43,7 +43,8 @@ class PodcastEpisodeDownload { | |||||||
|       season: this.rssPodcastEpisode?.season ?? null, |       season: this.rssPodcastEpisode?.season ?? null, | ||||||
|       episode: this.rssPodcastEpisode?.episode ?? null, |       episode: this.rssPodcastEpisode?.episode ?? null, | ||||||
|       episodeType: this.rssPodcastEpisode?.episodeType ?? 'full', |       episodeType: this.rssPodcastEpisode?.episodeType ?? 'full', | ||||||
|       publishedAt: this.rssPodcastEpisode?.publishedAt ?? null |       publishedAt: this.rssPodcastEpisode?.publishedAt ?? null, | ||||||
|  |       guid: this.rssPodcastEpisode?.guid ?? null | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user