mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Fix:Podcast download new episode check to compare both GUID and enclosure URL for existing episodes #2986
This commit is contained in:
		
							parent
							
								
									ab3a137db9
								
							
						
					
					
						commit
						6d89721371
					
				| @ -32,7 +32,7 @@ class PodcastManager { | ||||
|   } | ||||
| 
 | ||||
|   getEpisodeDownloadsInQueue(libraryItemId) { | ||||
|     return this.downloadQueue.filter(d => d.libraryItemId === libraryItemId) | ||||
|     return this.downloadQueue.filter((d) => d.libraryItemId === libraryItemId) | ||||
|   } | ||||
| 
 | ||||
|   clearDownloadQueue(libraryItemId = null) { | ||||
| @ -44,12 +44,12 @@ class PodcastManager { | ||||
|     } else { | ||||
|       var itemDownloads = this.getEpisodeDownloadsInQueue(libraryItemId) | ||||
|       Logger.info(`[PodcastManager] Clearing downloads in queue for item "${libraryItemId}" (${itemDownloads.length})`) | ||||
|       this.downloadQueue = this.downloadQueue.filter(d => d.libraryItemId !== libraryItemId) | ||||
|       this.downloadQueue = this.downloadQueue.filter((d) => d.libraryItemId !== libraryItemId) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async downloadPodcastEpisodes(libraryItem, episodesToDownload, isAutoDownload) { | ||||
|     let index = Math.max(...libraryItem.media.episodes.filter(ep => ep.index == null || isNaN(ep.index)).map(ep => Number(ep.index))) + 1 | ||||
|     let index = Math.max(...libraryItem.media.episodes.filter((ep) => ep.index == null || isNaN(ep.index)).map((ep) => Number(ep.index))) + 1 | ||||
|     for (const ep of episodesToDownload) { | ||||
|       const newPe = new PodcastEpisode() | ||||
|       newPe.setData(ep, index++) | ||||
| @ -72,7 +72,7 @@ class PodcastManager { | ||||
|     const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".` | ||||
|     const taskData = { | ||||
|       libraryId: podcastEpisodeDownload.libraryId, | ||||
|       libraryItemId: podcastEpisodeDownload.libraryItemId, | ||||
|       libraryItemId: podcastEpisodeDownload.libraryItemId | ||||
|     } | ||||
|     const task = TaskManager.createAndAddTask('download-podcast-episode', 'Downloading Episode', taskDescription, false, taskData) | ||||
| 
 | ||||
| @ -104,7 +104,9 @@ class PodcastManager { | ||||
|       }) | ||||
|     } else { | ||||
|       // Download episode only
 | ||||
|       success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath).then(() => true).catch((error) => { | ||||
|       success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath) | ||||
|         .then(() => true) | ||||
|         .catch((error) => { | ||||
|           Logger.error(`[PodcastManager] Podcast Episode download failed`, error) | ||||
|           return false | ||||
|         }) | ||||
| @ -156,7 +158,7 @@ class PodcastManager { | ||||
|     podcastEpisode.audioFile = audioFile | ||||
| 
 | ||||
|     if (audioFile.chapters?.length) { | ||||
|       podcastEpisode.chapters = audioFile.chapters.map(ch => ({ ...ch })) | ||||
|       podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch })) | ||||
|     } | ||||
| 
 | ||||
|     libraryItem.media.addPodcastEpisode(podcastEpisode) | ||||
| @ -181,7 +183,8 @@ class PodcastManager { | ||||
|     podcastEpisodeExpanded.libraryItem = libraryItem.toJSONExpanded() | ||||
|     SocketAuthority.emitter('episode_added', podcastEpisodeExpanded) | ||||
| 
 | ||||
|     if (this.currentDownload.isAutoDownload) { // Notifications only for auto downloaded episodes
 | ||||
|     if (this.currentDownload.isAutoDownload) { | ||||
|       // Notifications only for auto downloaded episodes
 | ||||
|       this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode) | ||||
|     } | ||||
| 
 | ||||
| @ -191,7 +194,9 @@ class PodcastManager { | ||||
|   async removeOldestEpisode(libraryItem, episodeIdJustDownloaded) { | ||||
|     var smallestPublishedAt = 0 | ||||
|     var oldestEpisode = null | ||||
|     libraryItem.media.episodesWithPubDate.filter(ep => ep.id !== episodeIdJustDownloaded).forEach((ep) => { | ||||
|     libraryItem.media.episodesWithPubDate | ||||
|       .filter((ep) => ep.id !== episodeIdJustDownloaded) | ||||
|       .forEach((ep) => { | ||||
|         if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) { | ||||
|           smallestPublishedAt = ep.publishedAt | ||||
|           oldestEpisode = ep | ||||
| @ -246,7 +251,8 @@ class PodcastManager { | ||||
|     var newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, dateToCheckForEpisodesAfter, libraryItem.media.maxNewEpisodesToDownload) | ||||
|     Logger.debug(`[PodcastManager] runEpisodeCheck: ${newEpisodes?.length || 'N/A'} episodes found`) | ||||
| 
 | ||||
|     if (!newEpisodes) { // Failed
 | ||||
|     if (!newEpisodes) { | ||||
|       // Failed
 | ||||
|       // Allow up to MaxFailedEpisodeChecks failed attempts before disabling auto download
 | ||||
|       if (!this.failedCheckMap[libraryItem.id]) this.failedCheckMap[libraryItem.id] = 0 | ||||
|       this.failedCheckMap[libraryItem.id]++ | ||||
| @ -285,7 +291,7 @@ class PodcastManager { | ||||
|     } | ||||
| 
 | ||||
|     // Filter new and not already has
 | ||||
|     let newEpisodes = feed.episodes.filter(ep => ep.publishedAt > dateToCheckForEpisodesAfter && !podcastLibraryItem.media.checkHasEpisodeByFeedUrl(ep.enclosure.url)) | ||||
|     let newEpisodes = feed.episodes.filter((ep) => ep.publishedAt > dateToCheckForEpisodesAfter && !podcastLibraryItem.media.checkHasEpisodeByFeedEpisode(ep)) | ||||
| 
 | ||||
|     if (maxNewEpisodes > 0) { | ||||
|       newEpisodes = newEpisodes.slice(0, maxNewEpisodes) | ||||
| @ -322,7 +328,7 @@ class PodcastManager { | ||||
|     } | ||||
| 
 | ||||
|     const matches = [] | ||||
|     feed.episodes.forEach(ep => { | ||||
|     feed.episodes.forEach((ep) => { | ||||
|       if (!ep.title) return | ||||
| 
 | ||||
|       const epTitle = ep.title.toLowerCase().trim() | ||||
| @ -383,7 +389,7 @@ class PodcastManager { | ||||
| 
 | ||||
|     return { | ||||
|       currentDownload: _currentDownload?.toJSONForClient(), | ||||
|       queue: this.downloadQueue.filter(item => !libraryId || item.libraryId === libraryId).map(item => item.toJSONForClient()) | ||||
|       queue: this.downloadQueue.filter((item) => !libraryId || item.libraryId === libraryId).map((item) => item.toJSONForClient()) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| const uuidv4 = require("uuid").v4 | ||||
| const uuidv4 = require('uuid').v4 | ||||
| const { areEquivalent, copyValue } = require('../../utils/index') | ||||
| const AudioFile = require('../files/AudioFile') | ||||
| const AudioTrack = require('../files/AudioTrack') | ||||
| @ -47,7 +47,7 @@ class PodcastEpisode { | ||||
|     this.enclosure = episode.enclosure ? { ...episode.enclosure } : null | ||||
|     this.guid = episode.guid || null | ||||
|     this.pubDate = episode.pubDate | ||||
|     this.chapters = episode.chapters?.map(ch => ({ ...ch })) || [] | ||||
|     this.chapters = episode.chapters?.map((ch) => ({ ...ch })) || [] | ||||
|     this.audioFile = episode.audioFile ? new AudioFile(episode.audioFile) : null | ||||
|     this.publishedAt = episode.publishedAt | ||||
|     this.addedAt = episode.addedAt | ||||
| @ -74,7 +74,7 @@ class PodcastEpisode { | ||||
|       enclosure: this.enclosure ? { ...this.enclosure } : null, | ||||
|       guid: this.guid, | ||||
|       pubDate: this.pubDate, | ||||
|       chapters: this.chapters.map(ch => ({ ...ch })), | ||||
|       chapters: this.chapters.map((ch) => ({ ...ch })), | ||||
|       audioFile: this.audioFile?.toJSON() || null, | ||||
|       publishedAt: this.publishedAt, | ||||
|       addedAt: this.addedAt, | ||||
| @ -98,7 +98,7 @@ class PodcastEpisode { | ||||
|       enclosure: this.enclosure ? { ...this.enclosure } : null, | ||||
|       guid: this.guid, | ||||
|       pubDate: this.pubDate, | ||||
|       chapters: this.chapters.map(ch => ({ ...ch })), | ||||
|       chapters: this.chapters.map((ch) => ({ ...ch })), | ||||
|       audioFile: this.audioFile?.toJSON() || null, | ||||
|       audioTrack: this.audioTrack?.toJSON() || null, | ||||
|       publishedAt: this.publishedAt, | ||||
| @ -121,7 +121,9 @@ class PodcastEpisode { | ||||
|   get duration() { | ||||
|     return this.audioFile?.duration || 0 | ||||
|   } | ||||
|   get size() { return this.audioFile?.metadata.size || 0 } | ||||
|   get size() { | ||||
|     return this.audioFile?.metadata.size || 0 | ||||
|   } | ||||
|   get enclosureUrl() { | ||||
|     return this.enclosure?.url || null | ||||
|   } | ||||
| @ -151,9 +153,9 @@ class PodcastEpisode { | ||||
|     let hasUpdates = false | ||||
|     for (const key in this.toJSON()) { | ||||
|       let newValue = payload[key] | ||||
|       if (newValue === "") newValue = null | ||||
|       if (newValue === '') newValue = null | ||||
|       let existingValue = this[key] | ||||
|       if (existingValue === "") existingValue = null | ||||
|       if (existingValue === '') existingValue = null | ||||
| 
 | ||||
|       if (newValue != undefined && !areEquivalent(newValue, existingValue)) { | ||||
|         this[key] = copyValue(newValue) | ||||
| @ -177,7 +179,7 @@ class PodcastEpisode { | ||||
|   } | ||||
| 
 | ||||
|   checkEqualsEnclosureUrl(url) { | ||||
|     if (!this.enclosure || !this.enclosure.url) return false | ||||
|     if (!this.enclosure?.url) return false | ||||
|     return this.enclosure.url == url | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -58,7 +58,7 @@ class Podcast { | ||||
|       metadata: this.metadata.toJSON(), | ||||
|       coverPath: this.coverPath, | ||||
|       tags: [...this.tags], | ||||
|       episodes: this.episodes.map(e => e.toJSON()), | ||||
|       episodes: this.episodes.map((e) => e.toJSON()), | ||||
|       autoDownloadEpisodes: this.autoDownloadEpisodes, | ||||
|       autoDownloadSchedule: this.autoDownloadSchedule, | ||||
|       lastEpisodeCheck: this.lastEpisodeCheck, | ||||
| @ -90,7 +90,7 @@ class Podcast { | ||||
|       metadata: this.metadata.toJSONExpanded(), | ||||
|       coverPath: this.coverPath, | ||||
|       tags: [...this.tags], | ||||
|       episodes: this.episodes.map(e => e.toJSONExpanded()), | ||||
|       episodes: this.episodes.map((e) => e.toJSONExpanded()), | ||||
|       autoDownloadEpisodes: this.autoDownloadEpisodes, | ||||
|       autoDownloadSchedule: this.autoDownloadSchedule, | ||||
|       lastEpisodeCheck: this.lastEpisodeCheck, | ||||
| @ -121,7 +121,7 @@ class Podcast { | ||||
| 
 | ||||
|   get size() { | ||||
|     var total = 0 | ||||
|     this.episodes.forEach((ep) => total += ep.size) | ||||
|     this.episodes.forEach((ep) => (total += ep.size)) | ||||
|     return total | ||||
|   } | ||||
|   get hasMediaEntities() { | ||||
| @ -129,7 +129,7 @@ class Podcast { | ||||
|   } | ||||
|   get duration() { | ||||
|     let total = 0 | ||||
|     this.episodes.forEach((ep) => total += ep.duration) | ||||
|     this.episodes.forEach((ep) => (total += ep.duration)) | ||||
|     return total | ||||
|   } | ||||
|   get numTracks() { | ||||
| @ -145,7 +145,7 @@ class Podcast { | ||||
|     return largestPublishedAt | ||||
|   } | ||||
|   get episodesWithPubDate() { | ||||
|     return this.episodes.filter(ep => !!ep.publishedAt) | ||||
|     return this.episodes.filter((ep) => !!ep.publishedAt) | ||||
|   } | ||||
| 
 | ||||
|   update(payload) { | ||||
| @ -169,7 +169,7 @@ class Podcast { | ||||
|   } | ||||
| 
 | ||||
|   updateEpisode(id, payload) { | ||||
|     var episode = this.episodes.find(ep => ep.id == id) | ||||
|     var episode = this.episodes.find((ep) => ep.id == id) | ||||
|     if (!episode) return false | ||||
|     return episode.update(payload) | ||||
|   } | ||||
| @ -182,15 +182,15 @@ class Podcast { | ||||
|   } | ||||
| 
 | ||||
|   removeFileWithInode(inode) { | ||||
|     const hasEpisode = this.episodes.some(ep => ep.audioFile.ino === inode) | ||||
|     const hasEpisode = this.episodes.some((ep) => ep.audioFile.ino === inode) | ||||
|     if (hasEpisode) { | ||||
|       this.episodes = this.episodes.filter(ep => ep.audioFile.ino !== inode) | ||||
|       this.episodes = this.episodes.filter((ep) => ep.audioFile.ino !== inode) | ||||
|     } | ||||
|     return hasEpisode | ||||
|   } | ||||
| 
 | ||||
|   findFileWithInode(inode) { | ||||
|     var episode = this.episodes.find(ep => ep.audioFile.ino === inode) | ||||
|     var episode = this.episodes.find((ep) => ep.audioFile.ino === inode) | ||||
|     if (episode) return episode.audioFile | ||||
|     return null | ||||
|   } | ||||
| @ -208,21 +208,23 @@ class Podcast { | ||||
|   } | ||||
| 
 | ||||
|   checkHasEpisode(episodeId) { | ||||
|     return this.episodes.some(ep => ep.id === episodeId) | ||||
|     return this.episodes.some((ep) => ep.id === episodeId) | ||||
|   } | ||||
|   checkHasEpisodeByFeedUrl(url) { | ||||
|     return this.episodes.some(ep => ep.checkEqualsEnclosureUrl(url)) | ||||
|   checkHasEpisodeByFeedEpisode(feedEpisode) { | ||||
|     const guid = feedEpisode.guid | ||||
|     const url = feedEpisode.enclosure.url | ||||
|     return this.episodes.some((ep) => (ep.guid && ep.guid === guid) || ep.checkEqualsEnclosureUrl(url)) | ||||
|   } | ||||
| 
 | ||||
|   // Only checks container format
 | ||||
|   checkCanDirectPlay(payload, episodeId) { | ||||
|     var episode = this.episodes.find(ep => ep.id === episodeId) | ||||
|     var episode = this.episodes.find((ep) => ep.id === episodeId) | ||||
|     if (!episode) return false | ||||
|     return episode.checkCanDirectPlay(payload) | ||||
|   } | ||||
| 
 | ||||
|   getDirectPlayTracklist(episodeId) { | ||||
|     var episode = this.episodes.find(ep => ep.id === episodeId) | ||||
|     var episode = this.episodes.find((ep) => ep.id === episodeId) | ||||
|     if (!episode) return false | ||||
|     return episode.getDirectPlayTracklist() | ||||
|   } | ||||
| @ -241,15 +243,15 @@ class Podcast { | ||||
|   } | ||||
| 
 | ||||
|   removeEpisode(episodeId) { | ||||
|     const episode = this.episodes.find(ep => ep.id === episodeId) | ||||
|     const episode = this.episodes.find((ep) => ep.id === episodeId) | ||||
|     if (episode) { | ||||
|       this.episodes = this.episodes.filter(ep => ep.id !== episodeId) | ||||
|       this.episodes = this.episodes.filter((ep) => ep.id !== episodeId) | ||||
|     } | ||||
|     return episode | ||||
|   } | ||||
| 
 | ||||
|   getPlaybackTitle(episodeId) { | ||||
|     var episode = this.episodes.find(ep => ep.id == episodeId) | ||||
|     var episode = this.episodes.find((ep) => ep.id == episodeId) | ||||
|     if (!episode) return this.metadata.title | ||||
|     return episode.title | ||||
|   } | ||||
| @ -259,7 +261,7 @@ class Podcast { | ||||
|   } | ||||
| 
 | ||||
|   getEpisodeDuration(episodeId) { | ||||
|     var episode = this.episodes.find(ep => ep.id == episodeId) | ||||
|     var episode = this.episodes.find((ep) => ep.id == episodeId) | ||||
|     if (!episode) return 0 | ||||
|     return episode.duration | ||||
|   } | ||||
| @ -268,13 +270,13 @@ class Podcast { | ||||
|     if (!episodeId) return null | ||||
| 
 | ||||
|     // Support old episode ids for mobile downloads
 | ||||
|     if (episodeId.startsWith('ep_')) return this.episodes.find(ep => ep.oldEpisodeId == episodeId) | ||||
|     if (episodeId.startsWith('ep_')) return this.episodes.find((ep) => ep.oldEpisodeId == episodeId) | ||||
| 
 | ||||
|     return this.episodes.find(ep => ep.id == episodeId) | ||||
|     return this.episodes.find((ep) => ep.id == episodeId) | ||||
|   } | ||||
| 
 | ||||
|   getChapters(episodeId) { | ||||
|     return this.getEpisode(episodeId)?.chapters?.map(ch => ({ ...ch })) || [] | ||||
|     return this.getEpisode(episodeId)?.chapters?.map((ch) => ({ ...ch })) || [] | ||||
|   } | ||||
| } | ||||
| module.exports = Podcast | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user