diff --git a/ios/App/App/plugins/AbsDownloader.swift b/ios/App/App/plugins/AbsDownloader.swift index 099bfbbb..c624da05 100644 --- a/ios/App/App/plugins/AbsDownloader.swift +++ b/ios/App/App/plugins/AbsDownloader.swift @@ -171,7 +171,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate { statusNotification["localLibraryItem"] = try? localLibraryItem.asDictionary() if let progress = libraryItem.userMediaProgress { - let episode = downloadItem.media?.episodes?.first(where: { $0.id == downloadItem.episodeId }) + let episode = downloadItem.media?.episodes.first(where: { $0.id == downloadItem.episodeId }) let localMediaProgress = LocalMediaProgress(localLibraryItem: localLibraryItem!, episode: episode, progress: progress) Database.shared.saveLocalMediaProgress(localMediaProgress) statusNotification["localMediaProgress"] = try? localMediaProgress.asDictionary() diff --git a/ios/App/App/plugins/AbsFileSystem.swift b/ios/App/App/plugins/AbsFileSystem.swift index b64906b5..d5b4e4f6 100644 --- a/ios/App/App/plugins/AbsFileSystem.swift +++ b/ios/App/App/plugins/AbsFileSystem.swift @@ -89,12 +89,14 @@ public class AbsFileSystem: CAPPlugin { var success = false do { - if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, var item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) { + if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) { if let fileIndex = item.localFiles.firstIndex(where: { $0.id == trackLocalFileId }) { try FileManager.default.removeItem(at: item.localFiles[fileIndex].contentPath) item.localFiles.remove(at: fileIndex) - if item.isPodcast, var media = item.media { - media.episodes = media.episodes?.filter { $0.audioTrack?.localFileId != trackLocalFileId } + if item.isPodcast, let media = item.media { + if let episodeIndex = media.episodes.firstIndex(where: { $0.audioTrack?.localFileId == trackLocalFileId }) { + media.episodes.remove(at: episodeIndex) + } item.media = media } Database.shared.saveLocalLibraryItem(localLibraryItem: item) diff --git a/ios/App/Shared/models/DataClasses.swift b/ios/App/Shared/models/DataClasses.swift index 6f5ceea4..64eea432 100644 --- a/ios/App/Shared/models/DataClasses.swift +++ b/ios/App/Shared/models/DataClasses.swift @@ -175,6 +175,8 @@ class Metadata: Object, Codable { @Persisted var seriesName: String? @Persisted var feedUrl: String? + var authorDisplayName: String { self.authorName ?? "Unknown" } + private enum CodingKeys : String, CodingKey { case title, subtitle, @@ -254,7 +256,7 @@ class Metadata: Object, Codable { class PodcastEpisode: Object, Codable { @Persisted var id: String = "" - @Persisted var index: Int = 0 + @Persisted var index: Int? @Persisted var episode: String? @Persisted var episodeType: String? @Persisted var title: String = "Unknown" @@ -262,8 +264,8 @@ class PodcastEpisode: Object, Codable { @Persisted var desc: String? @Persisted var audioFile: AudioFile? @Persisted var audioTrack: AudioTrack? - @Persisted var duration: Double = 0 - @Persisted var size: Int = 0 + @Persisted var duration: Double? + @Persisted var size: Int? var serverEpisodeId: String { self.id } private enum CodingKeys : String, CodingKey { @@ -281,7 +283,9 @@ class PodcastEpisode: Object, Codable { serverEpisodeId } - init(from decoder: Decoder) throws { + override init() {} + + required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decode(String.self, forKey: .id) index = try? values.decode(Int.self, forKey: .index) @@ -369,9 +373,6 @@ class Author: Object, Codable { try container.encode(name, forKey: .name) try container.encode(coverPath, forKey: .coverPath) } - - override init() { - super.init() } class Chapter: Object, Codable { @@ -451,7 +452,7 @@ class AudioTrack: Object, Codable { try container.encode(serverIndex, forKey: .serverIndex) } - mutating func setLocalInfo(filenameIdMap: [String: String], serverIndex: Int) -> Bool { + func setLocalInfo(filenameIdMap: [String: String], serverIndex: Int) -> Bool { if let localFileId = filenameIdMap[self.metadata?.filename ?? ""] { self.localFileId = localFileId self.serverIndex = serverIndex diff --git a/ios/App/Shared/models/DownloadItem.swift b/ios/App/Shared/models/DownloadItem.swift index aee00561..e8bc5f13 100644 --- a/ios/App/Shared/models/DownloadItem.swift +++ b/ios/App/Shared/models/DownloadItem.swift @@ -74,35 +74,6 @@ class DownloadItemPart: Object, Codable { @Persisted var destinationUri: String? @Persisted var progress: Double = 0 var task: URLSessionDownloadTask? - -struct DownloadItemPart: Realmable, Codable { - var id: String = UUID().uuidString - var filename: String? - var itemTitle: String? - var serverPath: String? - var audioTrack: AudioTrack? - var episode: PodcastEpisode? - var completed: Bool = false - var moved: Bool = false - var failed: Bool = false - var uri: String? - var downloadURL: URL? { - if let uri = self.uri { - return URL(string: uri) - } else { - return nil - } - } - var destinationUri: String? - var destinationURL: URL? { - if let destinationUri = self.destinationUri { - return AbsDownloader.itemDownloadFolder(path: destinationUri)! - } else { - return nil - } - } - var progress: Double = 0 - var task: URLSessionDownloadTask! private enum CodingKeys : String, CodingKey { case id, filename, itemTitle, completed, moved, failed, progress @@ -134,36 +105,3 @@ struct DownloadItemPart: Realmable, Codable { try container.encode(progress, forKey: .progress) } } - -extension DownloadItemPart { - init(filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?) { - self.filename = filename - self.itemTitle = itemTitle - self.serverPath = serverPath - self.audioTrack = audioTrack - self.episode = episode - - let config = Store.serverConfig! - var downloadUrl = "" - if (serverPath.hasSuffix("/cover")) { - downloadUrl += "\(config.address)\(serverPath)?token=\(config.token)" - downloadUrl += "&format=jpeg" // For cover images force to jpeg - } else { - downloadUrl = destination - } - self.uri = downloadUrl - self.destinationUri = destination - } - - func mimeType() -> String? { - if let track = audioTrack { - return track.mimeType - } else if let podcastTrack = episode?.audioTrack { - return podcastTrack.mimeType - } else if serverPath?.hasSuffix("/cover") ?? false { - return "image/jpg" - } else { - return nil - } - } -} diff --git a/ios/App/Shared/models/DownloadItemExtensions.swift b/ios/App/Shared/models/DownloadItemExtensions.swift index d875bb72..fa71bbd7 100644 --- a/ios/App/Shared/models/DownloadItemExtensions.swift +++ b/ios/App/Shared/models/DownloadItemExtensions.swift @@ -65,7 +65,7 @@ extension DownloadItemPart { var destinationURL: URL? { if let destinationUri = self.destinationUri { - return AbsDownloader.downloadsDirectory.appendingPathComponent(destinationUri) + return AbsDownloader.itemDownloadFolder(path: destinationUri) } else { return nil } diff --git a/ios/App/Shared/models/LocalLibrary.swift b/ios/App/Shared/models/LocalLibrary.swift index a2e6acf1..cea39dd0 100644 --- a/ios/App/Shared/models/LocalLibrary.swift +++ b/ios/App/Shared/models/LocalLibrary.swift @@ -133,7 +133,7 @@ class LocalPodcastEpisode: Object, Codable { class LocalFile: Object, Codable { @Persisted(primaryKey: true) var id: String = UUID().uuidString @Persisted var filename: String? - @Persisted var contentUrl: String = "" + @Persisted var _contentUrl: String = "" @Persisted var mimeType: String? @Persisted var size: Int = 0 diff --git a/ios/App/Shared/models/LocalLibraryExtensions.swift b/ios/App/Shared/models/LocalLibraryExtensions.swift index e5bfed64..cacfa09e 100644 --- a/ios/App/Shared/models/LocalLibraryExtensions.swift +++ b/ios/App/Shared/models/LocalLibraryExtensions.swift @@ -6,12 +6,13 @@ // import Foundation +import RealmSwift extension LocalLibraryItem { convenience init(_ item: LibraryItem, localUrl: String, server: ServerConnectionConfig, files: [LocalFile], coverPath: String?) { self.init() - self.contentUrl = localUrl + self._contentUrl = localUrl self.mediaType = item.mediaType self.localFiles.append(objectsIn: files) self._coverContentUrl = coverPath @@ -21,34 +22,33 @@ extension LocalLibraryItem { self.serverUserId = server.userId // Link the audio tracks and files - linkLocalFiles(files, fromMedia: item.media) - } - - mutating func addFiles(_ files: [LocalFile], item: LibraryItem) throws { - guard self.isPodcast else { throw LibraryItemDownloadError.podcastOnlySupported } - self.localFiles.append(contentsOf: files.filter({ $0.isAudioFile() })) linkLocalFiles(self.localFiles, fromMedia: item.media) } - mutating private func linkLocalFiles(_ files: [LocalFile], fromMedia: MediaType) { - var fromMedia = fromMedia + func addFiles(_ files: [LocalFile], item: LibraryItem) throws { + guard self.isPodcast else { throw LibraryItemDownloadError.podcastOnlySupported } + self.localFiles.append(objectsIn: files.filter({ $0.isAudioFile() })) + linkLocalFiles(self.localFiles, fromMedia: item.media) + } + + private func linkLocalFiles(_ files: List, fromMedia: MediaType?) { + guard let fromMedia = fromMedia else { return } let fileMap = files.map { ($0.filename ?? "", $0.id) } let fileIdByFilename = Dictionary(fileMap, uniquingKeysWith: { (_, last) in last }) if ( self.isBook ) { - if let tracks = fromMedia.tracks { - for i in tracks.indices { - _ = fromMedia.tracks?[i].setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: i) - } + for i in fromMedia.tracks.indices { + _ = fromMedia.tracks[i].setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: i) } } else if ( self.isPodcast ) { - if let episodes = fromMedia.episodes { - fromMedia.episodes = episodes.compactMap { episode in - // Filter out episodes not downloaded - var episode = episode - let episodeIsDownloaded = episode.audioTrack?.setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: 0) ?? false - return episodeIsDownloaded ? episode : nil + let episodes = List() + for episode in fromMedia.episodes { + // Filter out episodes not downloaded + let episodeIsDownloaded = episode.audioTrack?.setLocalInfo(filenameIdMap: fileIdByFilename, serverIndex: 0) ?? false + if episodeIsDownloaded { + episodes.append(episode) } } + fromMedia.episodes = episodes } self.media = fromMedia } @@ -74,12 +74,14 @@ extension LocalLibraryItem { let mediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: mediaProgressId) let mediaMetadata = self.media?.metadata - let chapters = self.media?.chapters - var audioTracks = self.media?.tracks + let chapters = Array(self.media?.chapters ?? List()) let authorName = mediaMetadata?.authorDisplayName + var audioTracks = [AudioTrack]() if let episode = episode, let track = episode.audioTrack { - audioTracks = [track] + audioTracks.append(track) + } else if let tracks = self.media?.tracks { + audioTracks.append(contentsOf: tracks) } let dateNow = Date().timeIntervalSince1970 @@ -89,7 +91,7 @@ extension LocalLibraryItem { libraryItemId: self.libraryItemId, episodeId: episode?.serverEpisodeId, mediaType: self.mediaType, - chapters: chapters ?? [], + chapters: chapters, displayTitle: mediaMetadata?.title, displayAuthor: authorName, coverPath: self.coverContentUrl, @@ -98,7 +100,7 @@ extension LocalLibraryItem { startedAt: dateNow, updatedAt: 0, timeListening: 0.0, - audioTracks: audioTracks ?? [], + audioTracks: audioTracks, currentTime: mediaProgress?.currentTime ?? 0.0, libraryItem: nil, localLibraryItem: self, @@ -120,7 +122,7 @@ extension LocalFile { } var absolutePath: String { - return AbsDownloader.downloadsDirectory.appendingPathComponent(self.contentUrl).absoluteString + return AbsDownloader.itemDownloadFolder(path: self._contentUrl)?.absoluteString ?? "" } func isAudioFile() -> Bool { @@ -161,7 +163,7 @@ extension LocalMediaProgress { } } - init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?, progress: MediaProgress) { + convenience init(localLibraryItem: LocalLibraryItem, episode: PodcastEpisode?, progress: MediaProgress) { self.init(localLibraryItem: localLibraryItem, episode: episode) self.duration = progress.duration self.progress = progress.progress @@ -172,7 +174,7 @@ extension LocalMediaProgress { self.finishedAt = progress.finishedAt } - mutating func updateIsFinished(_ finished: Bool) { + func updateIsFinished(_ finished: Bool) { if self.isFinished != finished { self.progress = finished ? 1.0 : 0.0 } @@ -186,7 +188,7 @@ extension LocalMediaProgress { self.finishedAt = finished ? lastUpdate : nil } - mutating func updateFromPlaybackSession(_ playbackSession: PlaybackSession) { + func updateFromPlaybackSession(_ playbackSession: PlaybackSession) { self.currentTime = playbackSession.currentTime self.progress = playbackSession.progress self.lastUpdate = Int(Date().timeIntervalSince1970) @@ -194,7 +196,7 @@ extension LocalMediaProgress { self.finishedAt = self.isFinished ? self.lastUpdate : nil } - mutating func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) { + func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) { self.isFinished = serverMediaProgress.isFinished self.progress = serverMediaProgress.progress self.currentTime = serverMediaProgress.currentTime