mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-16 14:21:41 +00:00
WIP
This commit is contained in:
parent
ebedaeb3b0
commit
51b3f0d465
8 changed files with 157 additions and 200 deletions
|
|
@ -1,174 +0,0 @@
|
|||
<template>
|
||||
<modals-modal v-model="show" name="podcast-episodes-modal" :width="1200" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" id="podcast-wrapper" class="p-4 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
<div ref="episodeContainer" id="episodes-scroll" class="w-full overflow-x-hidden overflow-y-auto">
|
||||
<div
|
||||
v-for="(episode, index) in episodes"
|
||||
:key="index"
|
||||
class="relative"
|
||||
:class="itemEpisodeMap[episode.enclosure.url] ? 'bg-primary bg-opacity-40' : selectedEpisodes[String(index)] ? '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(index, episode)"
|
||||
>
|
||||
<div class="absolute top-0 left-0 h-full flex items-center p-2">
|
||||
<span v-if="itemEpisodeMap[episode.enclosure.url]" class="material-icons text-success text-xl">download_done</span>
|
||||
<ui-checkbox v-else v-model="selectedEpisodes[String(index)]" small checkbox-bg="primary" border-color="gray-600" />
|
||||
</div>
|
||||
<div class="px-8 py-2">
|
||||
<p v-if="episode.episode" class="font-semibold text-gray-200">#{{ episode.episode }}</p>
|
||||
<p class="break-words mb-1">{{ episode.title }}</p>
|
||||
<p v-if="episode.subtitle" class="break-words mb-1 text-sm text-gray-300 episode-subtitle">{{ episode.subtitle }}</p>
|
||||
<p class="text-xs text-gray-300">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end pt-4">
|
||||
<ui-checkbox v-if="!allDownloaded" v-model="selectAll" @input="toggleSelectAll" label="Select all episodes" small checkbox-bg="primary" border-color="gray-600" class="mx-8" />
|
||||
<ui-btn v-if="!allDownloaded" :disabled="!episodesSelected.length" @click="submit">{{ buttonText }}</ui-btn>
|
||||
<p v-else class="text-success text-base px-2 py-4">All episodes are downloaded</p>
|
||||
</div>
|
||||
</div>
|
||||
</modals-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: Boolean,
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
episodes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
selectedEpisodes: {},
|
||||
selectAll: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
title() {
|
||||
if (!this.libraryItem) return ''
|
||||
return this.libraryItem.media.metadata.title || 'Unknown'
|
||||
},
|
||||
allDownloaded() {
|
||||
return !this.episodes.some((episode) => !this.itemEpisodeMap[episode.enclosure.url])
|
||||
},
|
||||
episodesSelected() {
|
||||
return Object.keys(this.selectedEpisodes).filter((key) => !!this.selectedEpisodes[key])
|
||||
},
|
||||
buttonText() {
|
||||
if (!this.episodesSelected.length) return 'No Episodes Selected'
|
||||
return `Download ${this.episodesSelected.length} Episode${this.episodesSelected.length > 1 ? 's' : ''}`
|
||||
},
|
||||
itemEpisodes() {
|
||||
if (!this.libraryItem) return []
|
||||
return this.libraryItem.media.episodes || []
|
||||
},
|
||||
itemEpisodeMap() {
|
||||
var map = {}
|
||||
this.itemEpisodes.forEach((item) => {
|
||||
if (item.enclosure) map[item.enclosure.url] = true
|
||||
})
|
||||
return map
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSelectAll(val) {
|
||||
for (let i = 0; i < this.episodes.length; i++) {
|
||||
const episode = this.episodes[i]
|
||||
if (this.itemEpisodeMap[episode.enclosure.url]) this.selectedEpisodes[String(i)] = false
|
||||
else this.$set(this.selectedEpisodes, String(i), val)
|
||||
}
|
||||
},
|
||||
checkSetIsSelectedAll() {
|
||||
for (let i = 0; i < this.episodes.length; i++) {
|
||||
const episode = this.episodes[i]
|
||||
if (!this.itemEpisodeMap[episode.enclosure.url] && !this.selectedEpisodes[String(i)]) {
|
||||
this.selectAll = false
|
||||
return
|
||||
}
|
||||
}
|
||||
this.selectAll = true
|
||||
},
|
||||
toggleSelectEpisode(index, episode) {
|
||||
if (this.itemEpisodeMap[episode.enclosure.url]) return
|
||||
this.$set(this.selectedEpisodes, String(index), !this.selectedEpisodes[String(index)])
|
||||
this.checkSetIsSelectedAll()
|
||||
},
|
||||
submit() {
|
||||
var episodesToDownload = []
|
||||
if (this.episodesSelected.length) {
|
||||
episodesToDownload = this.episodesSelected.map((episodeIndex) => this.episodes[Number(episodeIndex)])
|
||||
}
|
||||
|
||||
var payloadSize = JSON.stringify(episodesToDownload).length
|
||||
var sizeInMb = payloadSize / 1024 / 1024
|
||||
var sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
|
||||
console.log('Request size', sizeInMb)
|
||||
if (sizeInMb > 4.99) {
|
||||
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
|
||||
}
|
||||
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post(`/api/podcasts/${this.libraryItem.id}/download-episodes`, episodesToDownload)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
this.$toast.success('Started downloading episodes')
|
||||
this.show = false
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to download episodes'
|
||||
console.error('Failed to download episodes', error)
|
||||
this.processing = false
|
||||
this.$toast.error(errorMsg)
|
||||
|
||||
this.selectedEpisodes = {}
|
||||
this.selectAll = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.episodes.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1))
|
||||
this.selectAll = false
|
||||
this.selectedEpisodes = {}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#podcast-wrapper {
|
||||
min-height: 400px;
|
||||
max-height: 80vh;
|
||||
}
|
||||
#episodes-scroll {
|
||||
max-height: calc(80vh - 200px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -15,29 +15,36 @@
|
|||
</div>
|
||||
|
||||
<div class="flex items-center pt-2">
|
||||
<button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick">
|
||||
<button v-if="episode.audioTrack" class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick">
|
||||
<span class="material-icons text-2xl" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
|
||||
<p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p>
|
||||
</button>
|
||||
|
||||
<button v-if="!episode.id && !isDownloading && !isDownloadQueued && userIsAdminOrUp" class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="downloadEpisode">
|
||||
<span class="material-icons text-success">download</span>
|
||||
<p class="pl-2 pr-1 text-sm font-semibold">Download</p>
|
||||
</button>
|
||||
|
||||
<!-- <button v-if="libraryItemIdStreaming && !isStreamingFromDifferentLibrary" class="h-8 w-8 flex justify-center items-center mx-2" :class="isQueued ? 'text-success' : ''" @click.stop="queueBtnClick">
|
||||
<span class="material-icons-outlined">{{ isQueued ? 'playlist_add_check' : 'queue' }}</span>
|
||||
</button> -->
|
||||
|
||||
<ui-tooltip v-if="libraryItemIdStreaming && !isStreamingFromDifferentLibrary" :text="isQueued ? $strings.MessageRemoveFromPlayerQueue : $strings.MessageAddToPlayerQueue" :class="isQueued ? 'text-success' : ''" direction="top">
|
||||
<ui-tooltip v-if="episode.id && libraryItemIdStreaming && !isStreamingFromDifferentLibrary" :text="isQueued ? $strings.MessageRemoveFromPlayerQueue : $strings.MessageAddToPlayerQueue" :class="isQueued ? 'text-success' : ''" direction="top">
|
||||
<ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_play'" borderless @click="queueBtnClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||
<ui-tooltip v-if="episode.id" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip :text="$strings.LabelYourPlaylists" direction="top">
|
||||
<ui-tooltip v-if="episode.id" :text="$strings.LabelYourPlaylists" direction="top">
|
||||
<ui-icon-btn icon="playlist_add" borderless @click="clickAddToPlaylist" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-icon-btn v-if="userCanUpdate" icon="edit" borderless @click="clickEdit" />
|
||||
<ui-icon-btn v-if="userCanDelete" icon="close" borderless @click="removeClick" />
|
||||
<ui-icon-btn v-if="episode.id && userCanUpdate" icon="edit" borderless @click="clickEdit" />
|
||||
<ui-icon-btn v-if="episode.id && userCanDelete" icon="close" borderless @click="removeClick" />
|
||||
<p v-if="isDownloading">Downloading...</p>
|
||||
<p v-if="isDownloadQueued">Queued...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isHovering || isSelected || selectionMode" class="hidden md:block w-12 min-w-12" />
|
||||
|
|
@ -63,14 +70,16 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
selectionMode: Boolean
|
||||
selectionMode: Boolean,
|
||||
isDownloading: Boolean,
|
||||
isDownloadQueued: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isProcessingReadUpdate: false,
|
||||
processingRemove: false,
|
||||
isHovering: false,
|
||||
isSelected: false
|
||||
isSelected: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -80,6 +89,9 @@ export default {
|
|||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
userIsAdminOrUp() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
audioFile() {
|
||||
return this.episode.audioFile
|
||||
},
|
||||
|
|
@ -202,7 +214,33 @@ export default {
|
|||
// Add to queue
|
||||
this.$emit('addToQueue', this.episode)
|
||||
}
|
||||
},
|
||||
downloadEpisode() {
|
||||
var episodesToDownload = [this.episode]
|
||||
var payloadSize = JSON.stringify(episodesToDownload).length
|
||||
var sizeInMb = payloadSize / 1024 / 1024
|
||||
var sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
|
||||
console.log('Request size', sizeInMb)
|
||||
if (sizeInMb > 4.99) {
|
||||
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
|
||||
}
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post(`/api/podcasts/${this.libraryItemId}/download-episodes`, episodesToDownload)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
this.$toast.success('Started downloading episodes')
|
||||
this.show = false
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to download episodes'
|
||||
console.error('Failed to download episodes', error)
|
||||
this.processing = false
|
||||
this.$toast.error(errorMsg)
|
||||
this.selectedEpisodes = {}
|
||||
this.selectAll = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
<template>
|
||||
<div class="w-full py-6">
|
||||
<p class="text-lg mb-2 font-semibold md:hidden">{{ $strings.HeaderEpisodes }}</p>
|
||||
<p v-if="media.podcastFeedDownloadedAt" class="text-sm text-gray-300">Feed last updated {{ $formatDate(media.podcastFeedDownloadedAt, 'MMM do, yyyy HH:mm') }}</p>
|
||||
<div class="flex items-center mb-4">
|
||||
<p class="text-lg mb-0 font-semibold hidden md:block">{{ $strings.HeaderEpisodes }}</p>
|
||||
<ui-icon-btn icon="refresh" class="mx-0.5" outlined @click.stop="refreshPodcastFeed" />
|
||||
<div class="flex-grow hidden md:block" />
|
||||
<template v-if="isSelectionMode">
|
||||
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
||||
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
||||
</ui-tooltip>
|
||||
<ui-btn color="success" :disabled="processing" small class="h-9 mr-1.5" @click="downloadSelectedEpisodes">Download {{ selectedEpisodes.length }} episode(s)</ui-btn>
|
||||
<ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">{{ $getString('MessageRemoveEpisodes', [selectedEpisodes.length]) }}</ui-btn>
|
||||
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
||||
</template>
|
||||
|
|
@ -19,10 +22,9 @@
|
|||
</template>
|
||||
</div>
|
||||
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
||||
<template v-for="episode in episodesSorted">
|
||||
<tables-podcast-episode-table-row ref="episodeRow" :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" :selection-mode="isSelectionMode" class="item" @play="playEpisode" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" @selected="episodeSelected" @addToQueue="addEpisodeToQueue" @addToPlaylist="addToPlaylist" />
|
||||
<template v-for="episode in this.allEpisodes">
|
||||
<tables-podcast-episode-table-row ref="episodeRow" :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" :selection-mode="isSelectionMode" :is-downloading="isDownloading(episode)" :is-download-queued="isDownloadQueued(episode)" class="item" @play="playEpisode" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" @selected="episodeSelected" @addToQueue="addEpisodeToQueue" @addToPlaylist="addToPlaylist" />
|
||||
</template>
|
||||
|
||||
<modals-podcast-remove-episode v-model="showPodcastRemoveModal" @input="removeEpisodeModalToggled" :library-item="libraryItem" :episodes="episodesToRemove" @clearSelected="clearSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -33,6 +35,14 @@ export default {
|
|||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
episodesDownloading: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
episodesQueued: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -54,6 +64,11 @@ export default {
|
|||
handler() {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
episodesDownloading: {
|
||||
handler() {
|
||||
console.log(this.episodesDownloading)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -137,6 +152,17 @@ export default {
|
|||
return String(a[this.sortKey]).localeCompare(String(b[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' })
|
||||
})
|
||||
},
|
||||
allEpisodes() {
|
||||
const added = new Set()
|
||||
const episodes = []
|
||||
for (const episode of [...this.episodes, ...this.media.podcastFeed.episodes]) {
|
||||
const key = `${episode.pubDate}-${episode.title}`
|
||||
if (added.has(key)) continue
|
||||
episodes.push(episode)
|
||||
added.add(key)
|
||||
}
|
||||
return episodes
|
||||
},
|
||||
selectedIsFinished() {
|
||||
// Find an item that is not finished, if none then all items finished
|
||||
return !this.selectedEpisodes.find((episode) => {
|
||||
|
|
@ -290,6 +316,49 @@ export default {
|
|||
this.$store.commit('globals/setSelectedEpisode', episode)
|
||||
this.$store.commit('globals/setShowViewPodcastEpisodeModal', true)
|
||||
},
|
||||
refreshPodcastFeed() {
|
||||
this.$axios
|
||||
.get(`/api/podcasts/${this.libraryItem.id}/refresh-episodes-list`)
|
||||
.then(() => {
|
||||
this.$toast.success('Fetched episodes!')
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.error('Fetching episodes failed')
|
||||
console.error('Fetching episodes failed', error)
|
||||
})
|
||||
},
|
||||
downloadSelectedEpisodes() {
|
||||
var episodesToDownload = this.selectedEpisodes
|
||||
var payloadSize = JSON.stringify(episodesToDownload).length
|
||||
var sizeInMb = payloadSize / 1024 / 1024
|
||||
var sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
|
||||
console.log('Request size', sizeInMb)
|
||||
if (sizeInMb > 4.99) {
|
||||
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
|
||||
}
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post(`/api/podcasts/${this.libraryItem.id}/download-episodes`, episodesToDownload)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
this.$toast.success('Started downloading episodes')
|
||||
this.show = false
|
||||
this.clearSelected()
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to download episodes'
|
||||
console.error('Failed to download episodes', error)
|
||||
this.processing = false
|
||||
this.$toast.error(errorMsg)
|
||||
this.clearSelected()
|
||||
})
|
||||
},
|
||||
isDownloading(episode) {
|
||||
return this.episodesDownloading.map(ep => ep.url).includes(episode?.url || episode?.enclosure?.url)
|
||||
},
|
||||
isDownloadQueued(episode) {
|
||||
return this.episodesQueued.map(ep => ep.url).includes(episode?.url || episode?.enclosure?.url)
|
||||
},
|
||||
init() {
|
||||
this.episodesCopy = this.episodes.map((ep) => ({ ...ep }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,11 +198,6 @@
|
|||
<ui-icon-btn icon="playlist_add" class="mx-0.5" outlined @click="playlistsClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<!-- Only admin or root user can download new episodes -->
|
||||
<ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
|
||||
<ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip v-if="bookmarks.length" :text="$strings.LabelYourBookmarks" direction="top">
|
||||
<ui-icon-btn :icon="bookmarks.length ? 'bookmarks' : 'bookmark_border'" class="mx-0.5" @click="clickBookmarksBtn" />
|
||||
</ui-tooltip>
|
||||
|
|
@ -225,7 +220,7 @@
|
|||
|
||||
<widgets-audiobook-data v-if="tracks.length" :library-item-id="libraryItemId" :is-file="isFile" :media="media" />
|
||||
|
||||
<tables-podcast-episodes-table v-if="isPodcast" :library-item="libraryItem" />
|
||||
<tables-podcast-episodes-table v-if="isPodcast" :library-item="libraryItem" :episodes-downloading="episodesDownloading" :episodes-queued="episodeDownloadsQueued"/>
|
||||
|
||||
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" class="mt-6" />
|
||||
|
||||
|
|
@ -234,7 +229,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" />
|
||||
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :library-item-id="libraryItemId" hide-create @select="selectBookmark" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ class PodcastController {
|
|||
|
||||
var libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath)
|
||||
|
||||
var podcastFeed = await getPodcastFeed(payload.media.metadata.feedUrl)
|
||||
var podcastFeedDownloadedAt = Date.now()
|
||||
|
||||
var relPath = payload.path.replace(folder.fullPath, '')
|
||||
if (relPath.startsWith('/')) relPath = relPath.slice(1)
|
||||
|
||||
|
|
@ -57,7 +60,7 @@ class PodcastController {
|
|||
mtimeMs: libraryItemFolderStats.mtimeMs || 0,
|
||||
ctimeMs: libraryItemFolderStats.ctimeMs || 0,
|
||||
birthtimeMs: libraryItemFolderStats.birthtimeMs || 0,
|
||||
media: payload.media
|
||||
media: { ...payload.media, podcastFeed, podcastFeedDownloadedAt }
|
||||
}
|
||||
|
||||
var libraryItem = new LibraryItem()
|
||||
|
|
@ -106,6 +109,12 @@ class PodcastController {
|
|||
res.json({ podcast })
|
||||
}
|
||||
|
||||
async refreshPodcastFeed(req, res) {
|
||||
var libraryItem = req.libraryItem;
|
||||
await this.podcastManager.refreshPodcastFeed(libraryItem);
|
||||
res.json({ episodes: libraryItem.media.episodes });
|
||||
}
|
||||
|
||||
async getOPMLFeeds(req, res) {
|
||||
if (!req.body.opmlText) {
|
||||
return res.sendStatus(400)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ class PodcastManager {
|
|||
}
|
||||
}
|
||||
|
||||
async refreshPodcastFeed(libraryItem) {
|
||||
var feed = await getPodcastFeed(libraryItem.media.metadata.feedUrl);
|
||||
libraryItem.updatedAt = Date.now();
|
||||
libraryItem.media.update({podcastFeed: feed, podcastFeedDownloadedAt: Date.now()})
|
||||
await this.db.updateLibraryItem(libraryItem);
|
||||
}
|
||||
|
||||
async downloadPodcastEpisodes(libraryItem, episodesToDownload, isAutoDownload) {
|
||||
var index = libraryItem.media.episodes.length + 1
|
||||
episodesToDownload.forEach((ep) => {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ class Podcast {
|
|||
this.lastCoverSearch = null
|
||||
this.lastCoverSearchQuery = null
|
||||
|
||||
this.podcastFeed = null
|
||||
this.podcastFeedDownloadedAt = null
|
||||
|
||||
if (podcast) {
|
||||
this.construct(podcast)
|
||||
}
|
||||
|
|
@ -46,6 +49,8 @@ class Podcast {
|
|||
this.lastEpisodeCheck = podcast.lastEpisodeCheck || 0
|
||||
this.maxEpisodesToKeep = podcast.maxEpisodesToKeep || 0
|
||||
this.maxNewEpisodesToDownload = podcast.maxNewEpisodesToDownload || 3
|
||||
this.podcastFeed = podcast.podcastFeed
|
||||
this.podcastFeedDownloadedAt = podcast.podcastFeedDownloadedAt
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
@ -54,12 +59,14 @@ 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,
|
||||
maxEpisodesToKeep: this.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: this.maxNewEpisodesToDownload
|
||||
maxNewEpisodesToDownload: this.maxNewEpisodesToDownload,
|
||||
podcastFeed: this.podcastFeed,
|
||||
podcastFeedDownloadedAt: this.podcastFeedDownloadedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +81,9 @@ class Podcast {
|
|||
lastEpisodeCheck: this.lastEpisodeCheck,
|
||||
maxEpisodesToKeep: this.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: this.maxNewEpisodesToDownload,
|
||||
size: this.size
|
||||
size: this.size,
|
||||
podcastFeed: this.podcastFeed,
|
||||
podcastFeedDownloadedAt: this.podcastFeedDownloadedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,13 +93,15 @@ 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,
|
||||
maxEpisodesToKeep: this.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: this.maxNewEpisodesToDownload,
|
||||
size: this.size
|
||||
size: this.size,
|
||||
podcastFeed: this.podcastFeed,
|
||||
podcastFeedDownloadedAt: this.podcastFeedDownloadedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +196,9 @@ class Podcast {
|
|||
this.autoDownloadEpisodes = !!mediaData.autoDownloadEpisodes
|
||||
this.autoDownloadSchedule = mediaData.autoDownloadSchedule || global.ServerSettings.podcastEpisodeSchedule
|
||||
this.lastEpisodeCheck = Date.now() // Makes sure new episodes are after this
|
||||
|
||||
this.podcastFeed = mediaData.podcastFeed || null
|
||||
this.podcastFeedDownloadedAt = mediaData.podcastFeedDownloadedAt || null
|
||||
}
|
||||
|
||||
async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ class ApiRouter {
|
|||
this.router.post('/podcasts/:id/match-episodes', PodcastController.middleware.bind(this), PodcastController.quickMatchEpisodes.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))
|
||||
|
||||
this.router.get('/podcasts/:id/refresh-episodes-list', PodcastController.middleware.bind(this), PodcastController.refreshPodcastFeed.bind(this))
|
||||
//
|
||||
// Notification Routes (Admin and up)
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue