Support downloading covers and podcast episodes

This commit is contained in:
ronaldheft 2022-08-07 11:23:50 -04:00
parent b549528e23
commit 5b7fcca800
2 changed files with 71 additions and 15 deletions

View file

@ -123,39 +123,79 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
@objc func downloadLibraryItem(_ call: CAPPluginCall) { @objc func downloadLibraryItem(_ call: CAPPluginCall) {
let libraryItemId = call.getString("libraryItemId") let libraryItemId = call.getString("libraryItemId")
let episodeId = call.getString("episodeId") var episodeId = call.getString("episodeId")
if ( episodeId == "null" ) { episodeId = nil }
NSLog("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "")") NSLog("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "N/A")")
guard let libraryItemId = libraryItemId else { call.resolve(); return; } guard let libraryItemId = libraryItemId else { return call.resolve(["error": "libraryItemId not specified"]) }
// Verify the file isn't already downloading
let downloadItemId = episodeId != nil ? "\(libraryItemId)-\(episodeId!)" : libraryItemId
let downloadItem = Database.shared.getDownloadItem(downloadItemId: downloadItemId)
if ( downloadItem != nil ) {
return call.resolve(["error": "Download already started for this media entity"])
}
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId, episodeId: episodeId) { libraryItem in ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId, episodeId: episodeId) { libraryItem in
if (libraryItem == nil) { if let libraryItem = libraryItem {
NSLog("Library item not found") NSLog("Got library item from server \(libraryItem.id)")
call.resolve(["error": "Library item not found"])
} else {
NSLog("Got library item from server \(libraryItem!.id)")
do { do {
try self.startLibraryItemDownload(libraryItem!) if let episodeId = episodeId {
// Download a podcast episode
guard libraryItem.mediaType == "podcast" else { throw LibraryItemDownloadError.libraryItemNotPodcast }
let episode = libraryItem.media.episodes?.first(where: { $0.id == episodeId })
guard let episode = episode else { throw LibraryItemDownloadError.podcastEpisodeNotFound }
try self.startLibraryItemDownload(libraryItem, episode: episode)
} else {
// Download a book
try self.startLibraryItemDownload(libraryItem)
}
call.resolve() call.resolve()
} catch { } catch {
NSLog("Failed to download \(error)") debugPrint(error)
call.resolve(["error": "Failed to download"]) call.resolve(["error": "Failed to download"])
} }
} else {
call.resolve(["error": "Server request failed"])
} }
} }
} }
private func startLibraryItemDownload(_ item: LibraryItem) throws { private func startLibraryItemDownload(_ item: LibraryItem) throws {
guard let tracks = item.media.tracks else { try startLibraryItemDownload(item, episode: nil)
throw LibraryItemDownloadError.noTracks }
private func startLibraryItemDownload(_ item: LibraryItem, episode: PodcastEpisode?) throws {
var tracks: [AudioTrack]
var episodeId: String?
// Handle the different media type downloads
switch item.mediaType {
case "book":
guard let bookTracks = item.media.tracks else { throw LibraryItemDownloadError.noTracks }
tracks = bookTracks
case "podcast":
guard let episode = episode else { throw LibraryItemDownloadError.podcastEpisodeNotFound }
guard let podcastTrack = episode.audioTrack else { throw LibraryItemDownloadError.noTracks }
episodeId = episode.id
tracks = [podcastTrack]
default:
throw LibraryItemDownloadError.unknownMediaType
} }
// Queue up everything for downloading // Queue up everything for downloading
var downloadItem = DownloadItem(libraryItem: item, server: Store.serverConfig!) var downloadItem = DownloadItem(libraryItem: item, episodeId: episodeId, server: Store.serverConfig!)
downloadItem.downloadItemParts = try tracks.enumerated().map({ i, track in downloadItem.downloadItemParts = try tracks.enumerated().map({ i, track in
try startLibraryItemTrackDownload(item: item, position: i, track: track) try startLibraryItemTrackDownload(item: item, position: i, track: track)
}) })
// Also download the cover
if item.media.coverPath != nil && !item.media.coverPath!.isEmpty {
if let coverDownload = try? startLibraryItemCoverDownload(item: item) {
downloadItem.downloadItemParts.append(coverDownload)
}
}
// Persist in the database before status start coming in // Persist in the database before status start coming in
Database.shared.saveDownloadItem(downloadItem) Database.shared.saveDownloadItem(downloadItem)
@ -187,6 +227,15 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
return downloadItemPart return downloadItemPart
} }
private func startLibraryItemCoverDownload(item: LibraryItem) throws -> DownloadItemPart {
let filename = "cover.jpg"
let serverPath = "/api/items/\(item.id)/cover"
let itemDirectory = try createLibraryItemFileDirectory(item: item)
let localUrl = itemDirectory.appendingPathComponent("\(filename)")
return DownloadItemPart(filename: filename, destination: localUrl, itemTitle: "cover", serverPath: serverPath, audioTrack: nil, episode: nil)
}
private func urlForTrack(item: LibraryItem, track: AudioTrack) -> URL { private func urlForTrack(item: LibraryItem, track: AudioTrack) -> URL {
// filename needs to be encoded otherwise would just use contentUrl // filename needs to be encoded otherwise would just use contentUrl
let filenameEncoded = track.metadata?.filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed) let filenameEncoded = track.metadata?.filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
@ -213,6 +262,9 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
enum LibraryItemDownloadError: String, Error { enum LibraryItemDownloadError: String, Error {
case noTracks = "No tracks on library item" case noTracks = "No tracks on library item"
case noMetadata = "No metadata for track, unable to download" case noMetadata = "No metadata for track, unable to download"
case libraryItemNotPodcast = "Library item is not a podcast but episode was requested"
case podcastEpisodeNotFound = "Invalid podcast episode not found"
case unknownMediaType = "Unknown media type"
case failedDirectory = "Failed to create directory" case failedDirectory = "Failed to create directory"
case failedDownload = "Failed to download item" case failedDownload = "Failed to download item"
case noTaskDescription = "No task description" case noTaskDescription = "No task description"

View file

@ -35,10 +35,9 @@ struct DownloadItem: Realmable, Codable {
} }
extension DownloadItem { extension DownloadItem {
init(libraryItem: LibraryItem, server: ServerConnectionConfig) { init(libraryItem: LibraryItem, episodeId: String?, server: ServerConnectionConfig) {
self.id = libraryItem.id self.id = libraryItem.id
self.libraryItemId = libraryItem.id self.libraryItemId = libraryItem.id
//self.episodeId // TODO
self.userMediaProgress = libraryItem.userMediaProgress self.userMediaProgress = libraryItem.userMediaProgress
self.serverConnectionConfigId = server.id self.serverConnectionConfigId = server.id
self.serverAddress = server.address self.serverAddress = server.address
@ -46,6 +45,11 @@ extension DownloadItem {
self.mediaType = libraryItem.mediaType self.mediaType = libraryItem.mediaType
self.itemTitle = libraryItem.media.metadata.title self.itemTitle = libraryItem.media.metadata.title
self.media = libraryItem.media self.media = libraryItem.media
if let episodeId = episodeId {
self.id! += "-\(episodeId)"
self.episodeId = episodeId
}
} }
func isDoneDownloading() -> Bool { func isDoneDownloading() -> Bool {