Add download queue

This commit is contained in:
mfcar 2023-02-27 02:56:07 +00:00
parent f0edea5d52
commit 34ac972130
No known key found for this signature in database
21 changed files with 359 additions and 22 deletions

View File

@ -86,6 +86,14 @@
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">file_download</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonDownloadQueue }}</p>
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'">
<span class="material-icons text-2xl">warning</span>
@ -149,6 +157,9 @@ export default {
isMusicLibrary() {
return this.currentLibraryMediaType === 'music'
},
isPodcastDownloadQueuePage() {
return this.$route.name === 'library-library-podcast-download-queue'
},
isPodcastSearchPage() {
return this.$route.name === 'library-library-podcast-search'
},

View File

@ -11,12 +11,15 @@
</nuxt-link>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center">
<span class="material-icons text-sm">person</span>
<p v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</p>
<p v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</p>
<p v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base">
<div class="flex items-center">
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base">
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link>
</p>
<p v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</p>
</div>
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
<widgets-explicit-indicator :explicit="isExplicit"></widgets-explicit-indicator>
</div>
</div>
<div class="text-gray-400 flex items-center">
@ -129,6 +132,9 @@ export default {
isMusic() {
return this.streamLibraryItem ? this.streamLibraryItem.mediaType === 'music' : false
},
isExplicit() {
return this.mediaMetadata.explicit || false
},
mediaMetadata() {
return this.media.metadata || {}
},

View File

@ -19,8 +19,15 @@
<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>
<div class="flex items-center font-semibold text-gray-200">
<div v-if="episode.season || episode.episode">#</div>
<div v-if="episode.season">{{ episode.season }}x</div>
<div v-if="episode.episode">{{ episode.episode }}</div>
</div>
<div class="flex items-center mb-1">
<div class="break-words">{{ episode.title }}</div>
<widgets-podcast-type-indicator :type="episode.episodeType" />
</div>
<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>

View File

@ -0,0 +1,68 @@
<template>
<div class="w-full my-2">
<div class="w-full bg-primary px-4 md:px-6 py-2 flex items-center">
<p class="pr-2 md:pr-4">{{ $strings.HeaderDownloadQueue }}</p>
<div class="h-5 md:h-7 w-5 md:w-7 rounded-full bg-white bg-opacity-10 flex items-center justify-center">
<span class="text-sm font-mono">{{ queue.length }}</span>
</div>
</div>
<transition name="slide">
<div class="w-full">
<table class="text-sm tracksTable">
<tr>
<th class="text-left px-4">{{ $strings.LabelPodcast }}</th>
<th class="text-left w-32 min-w-32">{{ $strings.LabelEpisode }}</th>
<th class="text-left px-4">{{ $strings.LabelEpisodeTitle }}</th>
<th class="text-left px-4 w-48">{{ $strings.LabelPubDate }}</th>
</tr>
<template v-for="downloadQueued in queue">
<tr :key="downloadQueued.id">
<td class="px-4">
<div class="flex items-center">
<nuxt-link :to="`/item/${downloadQueued.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ downloadQueued.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="downloadQueued.podcastExplicit" />
</div>
</td>
<td>
<div class="flex items-center">
<div v-if="downloadQueued.season">{{ downloadQueued.season }}x</div>
<div v-if="downloadQueued.episode">{{ downloadQueued.episode }}</div>
<widgets-podcast-type-indicator :type="downloadQueued.episodeType" />
</div>
</td>
<td class="px-4">
{{ downloadQueued.episodeDisplayTitle }}
</td>
<td class="text-xs">
<div class="flex items-center">
<p>{{ $dateDistanceFromNow(downloadQueued.publishedAt) }}</p>
</div>
</td>
</tr>
</template>
</table>
</div>
</transition>
</div>
</template>
<script>
export default {
props: {
queue: {
type: Array,
default: () => []
},
libraryItemId: String
},
data() {
return {}
},
computed: {
},
methods: {
},
mounted() {
}
}
</script>

View File

@ -2,9 +2,10 @@
<div class="w-full px-2 py-3 overflow-hidden relative border-b border-white border-opacity-10" @mouseover="mouseover" @mouseleave="mouseleave">
<div v-if="episode" class="flex items-center cursor-pointer" :class="{ 'opacity-70': isSelected || selectionMode }" @click="clickedEpisode">
<div class="flex-grow px-2">
<p class="text-sm font-semibold">
{{ title }}
</p>
<div class="flex items-center">
<span class="text-sm font-semibold">{{ title }}</span>
<widgets-podcast-type-indicator :type="episode.episodeType" />
</div>
<p class="text-sm text-gray-200 episode-subtitle mt-1.5 mb-0.5">{{ subtitle }}</p>

View File

@ -0,0 +1,31 @@
<template>
<div>
<template v-if="type == 'bonus'">
<ui-tooltip text="Bonus" direction="top">
<span class="material-icons ml-1" style="font-size: 0.8rem">local_play</span>
</ui-tooltip>
</template>
<template v-if="type == 'trailer'">
<ui-tooltip text="Trailer" direction="top">
<span class="material-icons ml-1" style="font-size: 0.8rem">local_movies</span>
</ui-tooltip>
</template>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'full'
}
},
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
<app-book-shelf-toolbar page="podcast-search" />
<div id="bookshelf" class="w-full overflow-y-auto px-2 py-6 sm:px-4 md:p-12 relative">
<div class="w-full max-w-3xl mx-auto py-4">
<p class="text-xl mb-2 font-semibold px-4 md:px-0">{{ $strings.HeaderCurrentDownloads }}</p>
<p v-if="!episodesDownloading.length" class="text-center text-xl">{{ $strings.MessageNoDownloadsInProgress }}</p>
<template v-for="episode in episodesDownloading">
<div :key="episode.id" class="flex py-5 relative">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](episode.libraryItemId)" :width="96" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" class="hidden md:block" />
<div class="flex-grow pl-4 max-w-2xl">
<!-- mobile -->
<div class="flex md:hidden mb-2">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](episode.libraryItemId)" :width="48" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" class="md:hidden" />
<div class="flex-grow px-2">
<div class="flex items-center">
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>
</div>
<!-- desktop -->
<div class="hidden md:block">
<div class="flex items-center">
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>
<div class="flex items-center font-semibold text-gray-200">
<div v-if="episode.season || episode.episode">#</div>
<div v-if="episode.season">{{ episode.season }}x</div>
<div v-if="episode.episode">{{ episode.episode }}</div>
</div>
<div class="flex items-center mb-2">
<span class="font-semibold text-sm md:text-base">{{ episode.episodeDisplayTitle }}</span>
<widgets-podcast-type-indicator :type="episode.episodeType" />
</div>
<p class="text-sm text-gray-200 mb-4">{{ episode.subtitle }}</p>
</div>
</div>
</template>
</div>
<tables-podcast-download-queue-table :queue="episodeDownloadsQueued"></tables-podcast-download-queue-table>
</div>
</div>
</template>
<script>
import DownloadQueueTable from "~/components/tables/podcast/DownloadQueueTable.vue";
export default {
components: {DownloadQueueTable},
data() {
return {
episodesDownloading: [],
episodeDownloadsQueued: [],
processing: false,
}
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
}
},
methods: {
episodeDownloadQueued(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
this.episodeDownloadsQueued.push(episodeDownload)
}
},
episodeDownloadStarted(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
this.episodeDownloadsQueued = this.episodeDownloadsQueued.filter((d) => d.id !== episodeDownload.id)
this.episodesDownloading.push(episodeDownload)
}
},
episodeDownloadFinished(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
this.episodeDownloadsQueued = this.episodeDownloadsQueued.filter((d) => d.id !== episodeDownload.id)
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
}
},
downloadQueueUpdated(downloadQueue) {
this.episodeDownloadsQueued = downloadQueue.filter((q) => q.libraryId == this.currentLibraryId)
},
async loadInitialDownloadQueue() {
this.processing = true
const queuePayload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/downloads`).catch((error) => {
console.error('Failed to get download queue', error)
this.$toast.error('Failed to get download queue')
return null
})
this.processing = false
console.log('Episodes', queuePayload)
this.episodeDownloadsQueued = queuePayload || []
}
},
mounted() {
this.loadInitialDownloadQueue()
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
this.$root.socket.on('download_queue_updated', this.downloadQueueUpdated)
},
beforeDestroy() {
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
this.$root.socket.off('download_queue_updated', this.downloadQueueUpdated)
}
}
</script>

View File

@ -30,7 +30,16 @@
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>
<p class="font-semibold mb-2 text-sm md:text-base">{{ episode.title }}</p>
<div class="flex items-center font-semibold text-gray-200">
<div v-if="episode.season || episode.episode">#</div>
<div v-if="episode.season">{{ episode.season }}x</div>
<div v-if="episode.episode">{{ episode.episode }}</div>
</div>
<div class="flex items-center mb-2">
<div class="font-semibold text-sm md:text-base">{{ episode.title }}</div>
<widgets-podcast-type-indicator :type="episode.episodeType" />
</div>
<p class="text-sm text-gray-200 mb-4">{{ episode.subtitle }}</p>

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Erstellen",
"ButtonCreateBackup": "Sicherung erstellen",
"ButtonDelete": "Löschen",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Bearbeiten",
"ButtonEditChapters": "Kapitel bearbeiten",
"ButtonEditPodcast": "Podcast bearbeiten",
@ -92,7 +93,9 @@
"HeaderCollection": "Sammlungen",
"HeaderCollectionItems": "Sammlungseinträge",
"HeaderCover": "Titelbild",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Episoden",
"HeaderFiles": "Dateien",
"HeaderFindChapters": "Kapitel suchen",
@ -493,6 +496,8 @@
"MessageNoCollections": "Keine Sammlungen",
"MessageNoCoversFound": "Keine Titelbilder gefunden",
"MessageNoDescription": "Keine Beschreibung",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Keine Episodenübereinstimmungen gefunden",
"MessageNoEpisodes": "Keine Episoden",
"MessageNoFoldersAvailable": "Keine Ordner verfügbar",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Create",
"ButtonCreateBackup": "Create Backup",
"ButtonDelete": "Delete",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Edit Chapters",
"ButtonEditPodcast": "Edit Podcast",
@ -92,7 +93,9 @@
"HeaderCollection": "Collection",
"HeaderCollectionItems": "Collection Items",
"HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Episodes",
"HeaderFiles": "Files",
"HeaderFindChapters": "Find Chapters",
@ -493,6 +496,8 @@
"MessageNoCollections": "No Collections",
"MessageNoCoversFound": "No Covers Found",
"MessageNoDescription": "No description",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "No episode matches found",
"MessageNoEpisodes": "No Episodes",
"MessageNoFoldersAvailable": "No Folders Available",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Create",
"ButtonCreateBackup": "Create Backup",
"ButtonDelete": "Delete",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Edit Chapters",
"ButtonEditPodcast": "Edit Podcast",
@ -92,7 +93,9 @@
"HeaderCollection": "Collection",
"HeaderCollectionItems": "Collection Items",
"HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Episodes",
"HeaderFiles": "Files",
"HeaderFindChapters": "Find Chapters",
@ -493,6 +496,8 @@
"MessageNoCollections": "No Collections",
"MessageNoCoversFound": "No Covers Found",
"MessageNoDescription": "No description",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "No episode matches found",
"MessageNoEpisodes": "No Episodes",
"MessageNoFoldersAvailable": "No Folders Available",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Créer",
"ButtonCreateBackup": "Créer une sauvegarde",
"ButtonDelete": "Effacer",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Modifier",
"ButtonEditChapters": "Modifier les chapitres",
"ButtonEditPodcast": "Modifier les podcasts",
@ -92,7 +93,9 @@
"HeaderCollection": "Collection",
"HeaderCollectionItems": "Entrées de la Collection",
"HeaderCover": "Couverture",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Détails",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Épisodes",
"HeaderFiles": "Fichiers",
"HeaderFindChapters": "Trouver les chapitres",
@ -493,6 +496,8 @@
"MessageNoCollections": "Pas de collections",
"MessageNoCoversFound": "Aucune couverture trouvée",
"MessageNoDescription": "Pas de description",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Pas de correspondance d'épisode trouvée",
"MessageNoEpisodes": "Aucun épisode",
"MessageNoFoldersAvailable": "Aucun dossier disponible",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Napravi",
"ButtonCreateBackup": "Napravi backup",
"ButtonDelete": "Obriši",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Uredi poglavlja",
"ButtonEditPodcast": "Uredi podcast",
@ -92,7 +93,9 @@
"HeaderCollection": "Kolekcija",
"HeaderCollectionItems": "Stvari u kolekciji",
"HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Detalji",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Epizode",
"HeaderFiles": "Datoteke",
"HeaderFindChapters": "Pronađi poglavlja",
@ -493,6 +496,8 @@
"MessageNoCollections": "Nema kolekcija",
"MessageNoCoversFound": "Covers nisu pronađeni",
"MessageNoDescription": "Nema opisa",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Nijedna epizoda pronađena",
"MessageNoEpisodes": "Nema epizoda",
"MessageNoFoldersAvailable": "Nema dostupnih foldera",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Crea",
"ButtonCreateBackup": "Crea un Backup",
"ButtonDelete": "Elimina",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Modifica Capitoli",
"ButtonEditPodcast": "Modifica Podcast",
@ -92,7 +93,9 @@
"HeaderCollection": "Raccolta",
"HeaderCollectionItems": "Elementi della Raccolta",
"HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Dettagli",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Episodi",
"HeaderFiles": "File",
"HeaderFindChapters": "Trova Capitoli",
@ -493,6 +496,8 @@
"MessageNoCollections": "Nessuna Raccolta",
"MessageNoCoversFound": "Nessuna Cover Trovata",
"MessageNoDescription": "Nessuna descrizione",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Nessun episodio corrispondente trovato",
"MessageNoEpisodes": "Nessun Episodio",
"MessageNoFoldersAvailable": "Nessuna Cartella disponibile",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Utwórz",
"ButtonCreateBackup": "Utwórz kopię zapasową",
"ButtonDelete": "Usuń",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Edytuj rozdziały",
"ButtonEditPodcast": "Edytuj podcast",
@ -92,7 +93,9 @@
"HeaderCollection": "Kolekcja",
"HeaderCollectionItems": "Elementy kolekcji",
"HeaderCover": "Okładka",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Szczegóły",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Rozdziały",
"HeaderFiles": "Pliki",
"HeaderFindChapters": "Wyszukaj rozdziały",
@ -493,6 +496,8 @@
"MessageNoCollections": "Brak kolekcji",
"MessageNoCoversFound": "Okładki nieznalezione",
"MessageNoDescription": "Brak opisu",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Nie znaleziono pasujących odcinków",
"MessageNoEpisodes": "Brak odcinków",
"MessageNoFoldersAvailable": "Brak dostępnych folderów",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "Создать",
"ButtonCreateBackup": "Создать бэкап",
"ButtonDelete": "Удалить",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "Редактировать",
"ButtonEditChapters": "Редактировать Главы",
"ButtonEditPodcast": "Редактировать Подкаст",
@ -92,7 +93,9 @@
"HeaderCollection": "Коллекция",
"HeaderCollectionItems": "Элементы Коллекции",
"HeaderCover": "Обложка",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "Подробности",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "Эпизоды",
"HeaderFiles": "Файлы",
"HeaderFindChapters": "Найти Главы",
@ -493,6 +496,8 @@
"MessageNoCollections": "Нет Коллекций",
"MessageNoCoversFound": "Обложек не найдено",
"MessageNoDescription": "Нет описания",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "Совпадения эпизодов не найдены",
"MessageNoEpisodes": "Нет Эпизодов",
"MessageNoFoldersAvailable": "Нет доступных папок",

View File

@ -20,6 +20,7 @@
"ButtonCreate": "创建",
"ButtonCreateBackup": "创建备份",
"ButtonDelete": "删除",
"ButtonDownloadQueue": "Queue",
"ButtonEdit": "编辑",
"ButtonEditChapters": "编辑章节",
"ButtonEditPodcast": "编辑播客",
@ -92,7 +93,9 @@
"HeaderCollection": "收藏",
"HeaderCollectionItems": "收藏项目",
"HeaderCover": "封面",
"HeaderCurrentDownloads": "Current Downloads",
"HeaderDetails": "详情",
"HeaderDownloadQueue": "Download Queue",
"HeaderEpisodes": "剧集",
"HeaderFiles": "文件",
"HeaderFindChapters": "查找章节",
@ -493,6 +496,8 @@
"MessageNoCollections": "没有收藏",
"MessageNoCoversFound": "没有找到封面",
"MessageNoDescription": "没有描述",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoEpisodeMatchesFound": "没有找到任何剧集匹配项",
"MessageNoEpisodes": "没有剧集",
"MessageNoFoldersAvailable": "没有可用文件夹",

View File

@ -82,6 +82,13 @@ class LibraryController {
return res.json(req.library)
}
async getDownloadQueue(req, res) {
const library = req.library
let queue = this.podcastManager.getDownloadQueueDetails().filter(q => q.libraryId === library.id)
return res.json(queue)
}
async update(req, res) {
const library = req.library

View File

@ -56,12 +56,13 @@ class PodcastManager {
newPe.setData(ep, index++)
newPe.libraryItemId = libraryItem.id
var newPeDl = new PodcastEpisodeDownload()
newPeDl.setData(newPe, libraryItem, isAutoDownload)
newPeDl.setData(newPe, libraryItem, isAutoDownload, libraryItem.libraryId)
this.startPodcastEpisodeDownload(newPeDl)
})
}
async startPodcastEpisodeDownload(podcastEpisodeDownload) {
SocketAuthority.emitter('download_queue_updated', this.getDownloadQueueDetails())
if (this.currentDownload) {
this.downloadQueue.push(podcastEpisodeDownload)
SocketAuthority.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient())
@ -99,6 +100,7 @@ class PodcastManager {
}
SocketAuthority.emitter('episode_download_finished', this.currentDownload.toJSONForClient())
SocketAuthority.emitter('download_queue_updated', this.getDownloadQueueDetails())
this.watcher.removeIgnoreDir(this.currentDownload.libraryItem.path)
this.currentDownload = null
@ -329,5 +331,22 @@ class PodcastManager {
feeds: rssFeedData
}
}
getDownloadQueueDetails() {
return this.downloadQueue.map(item => {
return {
id: item.id,
libraryId: item.libraryId || null,
libraryItemId: item.libraryItemId || null,
podcastTitle: item.libraryItem.media.metadata.title || null,
podcastExplicit: item.libraryItem.media.metadata.explicit || false,
episodeDisplayTitle: item.podcastEpisode.title || null,
season: item.podcastEpisode.season || null,
episode: item.podcastEpisode.episode || null,
episodeType: item.podcastEpisode.episodeType || 'full',
publishedAt: item.podcastEpisode.publishedAt || null
}
})
}
}
module.exports = PodcastManager

View File

@ -8,6 +8,7 @@ class PodcastEpisodeDownload {
this.podcastEpisode = null
this.url = null
this.libraryItem = null
this.libraryId = null
this.isAutoDownload = false
this.isDownloading = false
@ -25,12 +26,17 @@ class PodcastEpisodeDownload {
episodeDisplayTitle: this.podcastEpisode ? this.podcastEpisode.title : null,
url: this.url,
libraryItemId: this.libraryItem ? this.libraryItem.id : null,
libraryId: this.libraryId || null,
isDownloading: this.isDownloading,
isFinished: this.isFinished,
failed: this.failed,
startedAt: this.startedAt,
createdAt: this.createdAt,
finishedAt: this.finishedAt
finishedAt: this.finishedAt,
season: this.podcastEpisode ? this.podcastEpisode.season : null,
episode: this.podcastEpisode ? this.podcastEpisode.episode : null,
episodeType: this.podcastEpisode ? this.podcastEpisode.episodeType : 'full',
publishedAt: this.podcastEpisode ? this.podcastEpisode.publishedAt : null
}
}
@ -47,13 +53,14 @@ class PodcastEpisodeDownload {
return this.libraryItem ? this.libraryItem.id : null
}
setData(podcastEpisode, libraryItem, isAutoDownload) {
setData(podcastEpisode, libraryItem, isAutoDownload, libraryId) {
this.id = getId('epdl')
this.podcastEpisode = podcastEpisode
this.url = encodeURI(podcastEpisode.enclosure.url)
this.libraryItem = libraryItem
this.isAutoDownload = isAutoDownload
this.createdAt = Date.now()
this.libraryId = libraryId
}
setFinished(success) {

View File

@ -76,6 +76,7 @@ class ApiRouter {
this.router.get('/libraries/:id/items', LibraryController.middleware.bind(this), LibraryController.getLibraryItems.bind(this))
this.router.delete('/libraries/:id/issues', LibraryController.middleware.bind(this), LibraryController.removeLibraryItemsWithIssues.bind(this))
this.router.get('/libraries/:id/downloads', LibraryController.middleware.bind(this), LibraryController.getDownloadQueue.bind(this))
this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), LibraryController.getAllSeriesForLibrary.bind(this))
this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), LibraryController.getCollectionsForLibrary.bind(this))
this.router.get('/libraries/:id/playlists', LibraryController.middleware.bind(this), LibraryController.getUserPlaylistsForLibrary.bind(this))