diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js index 6e91a0aa..d8db6492 100644 --- a/server/managers/PodcastManager.js +++ b/server/managers/PodcastManager.js @@ -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,10 +104,12 @@ class PodcastManager { }) } else { // Download episode only - success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath).then(() => true).catch((error) => { - Logger.error(`[PodcastManager] Podcast Episode download failed`, error) - return false - }) + success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath) + .then(() => true) + .catch((error) => { + Logger.error(`[PodcastManager] Podcast Episode download failed`, error) + return false + }) } if (success) { @@ -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,12 +194,14 @@ class PodcastManager { async removeOldestEpisode(libraryItem, episodeIdJustDownloaded) { var smallestPublishedAt = 0 var oldestEpisode = null - libraryItem.media.episodesWithPubDate.filter(ep => ep.id !== episodeIdJustDownloaded).forEach((ep) => { - if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) { - smallestPublishedAt = ep.publishedAt - oldestEpisode = ep - } - }) + libraryItem.media.episodesWithPubDate + .filter((ep) => ep.id !== episodeIdJustDownloaded) + .forEach((ep) => { + if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) { + smallestPublishedAt = ep.publishedAt + oldestEpisode = ep + } + }) // TODO: Should we check for open playback sessions for this episode? // TODO: remove all user progress for this episode if (oldestEpisode?.audioFile) { @@ -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() @@ -370,7 +376,7 @@ class PodcastManager { /** * OPML file string for podcasts in a library - * @param {import('../models/Podcast')[]} podcasts + * @param {import('../models/Podcast')[]} podcasts * @returns {string} XML string */ generateOPMLFileText(podcasts) { @@ -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()) } } } diff --git a/server/objects/entities/PodcastEpisode.js b/server/objects/entities/PodcastEpisode.js index cc8b8d9b..69a9b2f0 100644 --- a/server/objects/entities/PodcastEpisode.js +++ b/server/objects/entities/PodcastEpisode.js @@ -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 } } diff --git a/server/objects/mediaTypes/Podcast.js b/server/objects/mediaTypes/Podcast.js index 6f7ca337..bca741a2 100644 --- a/server/objects/mediaTypes/Podcast.js +++ b/server/objects/mediaTypes/Podcast.js @@ -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