Fix:iOS downloads #537

This commit is contained in:
advplyr 2023-02-17 13:07:35 -06:00
parent 6cd04e7098
commit 4eeb7f24d8
7 changed files with 40 additions and 16 deletions

View file

@ -44,7 +44,6 @@ export default {
}, },
methods: { methods: {
clickedIt() { clickedIt() {
if (this.isIos) return // TODO: Implement on iOS
this.$router.push('/downloading') this.$router.push('/downloading')
}, },
onItemDownloadComplete(data) { onItemDownloadComplete(data) {

View file

@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Override point for customization after application launch. // Override point for customization after application launch.
let configuration = Realm.Configuration( let configuration = Realm.Configuration(
schemaVersion: 6, schemaVersion: 7,
migrationBlock: { [weak self] migration, oldSchemaVersion in migrationBlock: { [weak self] migration, oldSchemaVersion in
if (oldSchemaVersion < 1) { if (oldSchemaVersion < 1) {
self?.logger.log("Realm schema version was \(oldSchemaVersion)") self?.logger.log("Realm schema version was \(oldSchemaVersion)")

View file

@ -33,6 +33,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in
let realm = try Realm() let realm = try Realm()
try realm.write { try realm.write {
downloadItemPart.bytesDownloaded = downloadItemPart.fileSize
downloadItemPart.progress = 100 downloadItemPart.progress = 100
downloadItemPart.completed = true downloadItemPart.completed = true
} }
@ -72,9 +73,11 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in
// Calculate the download percentage // Calculate the download percentage
let percentDownloaded = (Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) * 100 let percentDownloaded = (Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) * 100
// Only update the progress if we received accurate progress data // Only update the progress if we received accurate progress data
if percentDownloaded >= 0.0 && percentDownloaded <= 100.0 { if percentDownloaded >= 0.0 && percentDownloaded <= 100.0 {
try Realm().write { try Realm().write {
downloadItemPart.bytesDownloaded = Double(totalBytesWritten)
downloadItemPart.progress = percentDownloaded downloadItemPart.progress = percentDownloaded
} }
} }
@ -109,6 +112,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
// Call the progress handler // Call the progress handler
do { do {
try progressHandler(downloadItem, part) try progressHandler(downloadItem, part)
try? self.notifyListeners("onDownloadItemPartUpdate", data: part.asDictionary())
} catch { } catch {
logger.error("Error while processing progress") logger.error("Error while processing progress")
debugPrint(error) debugPrint(error)
@ -158,10 +162,9 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
} }
} }
// Emit status for active downloads // Check for items done downloading
if let activeDownloads = fetchActiveDownloads() { if let activeDownloads = fetchActiveDownloads() {
for item in activeDownloads.values { for item in activeDownloads.values {
try? self.notifyListeners("onItemDownloadUpdate", data: item.asDictionary())
if item.isDoneDownloading() { handleDoneDownloadItem(item) } if item.isDoneDownloading() { handleDoneDownloadItem(item) }
} }
} }
@ -277,19 +280,22 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
let downloadItem = DownloadItem(libraryItem: item, episodeId: episodeId, server: Store.serverConfig!) let downloadItem = DownloadItem(libraryItem: item, episodeId: episodeId, server: Store.serverConfig!)
var tasks = [DownloadItemPartTask]() var tasks = [DownloadItemPartTask]()
for (i, track) in tracks.enumerated() { for (i, track) in tracks.enumerated() {
let task = try startLibraryItemTrackDownload(item: item, position: i, track: track, episode: episode) let task = try startLibraryItemTrackDownload(downloadItemId: downloadItem.id!, item: item, position: i, track: track, episode: episode)
downloadItem.downloadItemParts.append(task.part) downloadItem.downloadItemParts.append(task.part)
tasks.append(task) tasks.append(task)
} }
// Also download the cover // Also download the cover
if item.media?.coverPath != nil && !(item.media?.coverPath!.isEmpty ?? true) { if item.media?.coverPath != nil && !(item.media?.coverPath!.isEmpty ?? true) {
if let task = try? startLibraryItemCoverDownload(item: item) { if let task = try? startLibraryItemCoverDownload(downloadItemId: downloadItem.id!, item: item) {
downloadItem.downloadItemParts.append(task.part) downloadItem.downloadItemParts.append(task.part)
tasks.append(task) tasks.append(task)
} }
} }
// Notify client of download item
try? self.notifyListeners("onDownloadItem", data: downloadItem.asDictionary())
// Persist in the database before status start coming in // Persist in the database before status start coming in
try Database.shared.saveDownloadItem(downloadItem) try Database.shared.saveDownloadItem(downloadItem)
@ -299,7 +305,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
} }
} }
private func startLibraryItemTrackDownload(item: LibraryItem, position: Int, track: AudioTrack, episode: PodcastEpisode?) throws -> DownloadItemPartTask { private func startLibraryItemTrackDownload(downloadItemId: String, item: LibraryItem, position: Int, track: AudioTrack, episode: PodcastEpisode?) throws -> DownloadItemPartTask {
logger.log("TRACK \(track.contentUrl!)") logger.log("TRACK \(track.contentUrl!)")
// If we don't name metadata, then we can't proceed // If we don't name metadata, then we can't proceed
@ -312,7 +318,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
let localUrl = "\(itemDirectory)/\(filename)" let localUrl = "\(itemDirectory)/\(filename)"
let task = session.downloadTask(with: serverUrl) let task = session.downloadTask(with: serverUrl)
let part = DownloadItemPart(filename: filename, destination: localUrl, itemTitle: track.title ?? "Unknown", serverPath: Store.serverConfig!.address, audioTrack: track, episode: episode) let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: track.title ?? "Unknown", serverPath: Store.serverConfig!.address, audioTrack: track, episode: episode, size: track.metadata?.size ?? 0)
// Store the id on the task so the download item can be pulled from the database later // Store the id on the task so the download item can be pulled from the database later
task.taskDescription = part.id task.taskDescription = part.id
@ -320,13 +326,18 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
return DownloadItemPartTask(part: part, task: task) return DownloadItemPartTask(part: part, task: task)
} }
private func startLibraryItemCoverDownload(item: LibraryItem) throws -> DownloadItemPartTask { private func startLibraryItemCoverDownload(downloadItemId: String, item: LibraryItem) throws -> DownloadItemPartTask {
let filename = "cover.jpg" let filename = "cover.jpg"
let serverPath = "/api/items/\(item.id)/cover" let serverPath = "/api/items/\(item.id)/cover"
let itemDirectory = try createLibraryItemFileDirectory(item: item) let itemDirectory = try createLibraryItemFileDirectory(item: item)
let localUrl = "\(itemDirectory)/\(filename)" let localUrl = "\(itemDirectory)/\(filename)"
let part = DownloadItemPart(filename: filename, destination: localUrl, itemTitle: "cover", serverPath: serverPath, audioTrack: nil, episode: nil) // Find library file to get cover size
let coverLibraryFile = item.libraryFiles.first(where: {
$0.metadata?.path == item.media?.coverPath
})
let part = DownloadItemPart(downloadItemId: downloadItemId, filename: filename, destination: localUrl, itemTitle: "cover", serverPath: serverPath, audioTrack: nil, episode: nil, size: coverLibraryFile?.metadata?.size ?? 0)
let task = session.downloadTask(with: part.downloadURL!) let task = session.downloadTask(with: part.downloadURL!)
// Store the id on the task so the download item can be pulled from the database later // Store the id on the task so the download item can be pulled from the database later

View file

@ -9,7 +9,7 @@ import Foundation
import RealmSwift import RealmSwift
class DownloadItem: Object, Codable { class DownloadItem: Object, Codable {
@Persisted(primaryKey: true) var id: String? @Persisted(primaryKey: true) var id:String?
@Persisted(indexed: true) var libraryItemId: String? @Persisted(indexed: true) var libraryItemId: String?
@Persisted var episodeId: String? @Persisted var episodeId: String?
@Persisted var userMediaProgress: MediaProgress? @Persisted var userMediaProgress: MediaProgress?

View file

@ -10,7 +10,9 @@ import RealmSwift
class DownloadItemPart: Object, Codable { class DownloadItemPart: Object, Codable {
@Persisted(primaryKey: true) var id = "" @Persisted(primaryKey: true) var id = ""
@Persisted var downloadItemId: String?
@Persisted var filename: String? @Persisted var filename: String?
@Persisted var fileSize: Double = 0
@Persisted var itemTitle: String? @Persisted var itemTitle: String?
@Persisted var serverPath: String? @Persisted var serverPath: String?
@Persisted var audioTrack: AudioTrack? @Persisted var audioTrack: AudioTrack?
@ -21,9 +23,10 @@ class DownloadItemPart: Object, Codable {
@Persisted var uri: String? @Persisted var uri: String?
@Persisted var destinationUri: String? @Persisted var destinationUri: String?
@Persisted var progress: Double = 0 @Persisted var progress: Double = 0
@Persisted var bytesDownloaded: Double = 0
private enum CodingKeys : String, CodingKey { private enum CodingKeys : String, CodingKey {
case id, filename, itemTitle, completed, moved, failed, progress case id, downloadItemId, filename, fileSize, itemTitle, completed, moved, failed, progress, bytesDownloaded
} }
override init() { override init() {
@ -33,32 +36,40 @@ class DownloadItemPart: Object, Codable {
required init(from decoder: Decoder) throws { required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self) let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id) id = try values.decode(String.self, forKey: .id)
downloadItemId = try? values.decode(String.self, forKey: .downloadItemId)
filename = try? values.decode(String.self, forKey: .filename) filename = try? values.decode(String.self, forKey: .filename)
fileSize = try values.decode(Double.self, forKey: .fileSize)
itemTitle = try? values.decode(String.self, forKey: .itemTitle) itemTitle = try? values.decode(String.self, forKey: .itemTitle)
completed = try values.decode(Bool.self, forKey: .completed) completed = try values.decode(Bool.self, forKey: .completed)
moved = try values.decode(Bool.self, forKey: .moved) moved = try values.decode(Bool.self, forKey: .moved)
failed = try values.decode(Bool.self, forKey: .failed) failed = try values.decode(Bool.self, forKey: .failed)
progress = try values.decode(Double.self, forKey: .progress) progress = try values.decode(Double.self, forKey: .progress)
bytesDownloaded = try values.decode(Double.self, forKey: .bytesDownloaded)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(downloadItemId, forKey: .downloadItemId)
try container.encode(filename, forKey: .filename) try container.encode(filename, forKey: .filename)
try container.encode(fileSize, forKey: .fileSize)
try container.encode(itemTitle, forKey: .itemTitle) try container.encode(itemTitle, forKey: .itemTitle)
try container.encode(completed, forKey: .completed) try container.encode(completed, forKey: .completed)
try container.encode(moved, forKey: .moved) try container.encode(moved, forKey: .moved)
try container.encode(failed, forKey: .failed) try container.encode(failed, forKey: .failed)
try container.encode(progress, forKey: .progress) try container.encode(progress, forKey: .progress)
try container.encode(bytesDownloaded, forKey: .bytesDownloaded)
} }
} }
extension DownloadItemPart { extension DownloadItemPart {
convenience init(filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?) { convenience init(downloadItemId: String, filename: String, destination: String, itemTitle: String, serverPath: String, audioTrack: AudioTrack?, episode: PodcastEpisode?, size: Double) {
self.init() self.init()
self.id = destination.toBase64() self.id = destination.toBase64()
self.downloadItemId = downloadItemId
self.filename = filename self.filename = filename
self.fileSize = size
self.itemTitle = itemTitle self.itemTitle = itemTitle
self.serverPath = serverPath self.serverPath = serverPath
self.audioTrack = AudioTrack.detachCopy(of: audioTrack) self.audioTrack = AudioTrack.detachCopy(of: audioTrack)

View file

@ -13,9 +13,10 @@ class FileMetadata: EmbeddedObject, Codable {
@Persisted var ext: String = "" @Persisted var ext: String = ""
@Persisted var path: String = "" @Persisted var path: String = ""
@Persisted var relPath: String = "" @Persisted var relPath: String = ""
@Persisted var size:Double = 0
private enum CodingKeys : String, CodingKey { private enum CodingKeys : String, CodingKey {
case filename, ext, path, relPath case filename, ext, path, relPath, size
} }
override init() { override init() {
@ -29,6 +30,7 @@ class FileMetadata: EmbeddedObject, Codable {
ext = try values.decode(String.self, forKey: .ext) ext = try values.decode(String.self, forKey: .ext)
path = try values.decode(String.self, forKey: .path) path = try values.decode(String.self, forKey: .path)
relPath = try values.decode(String.self, forKey: .relPath) relPath = try values.decode(String.self, forKey: .relPath)
size = try values.decode(Double.self, forKey: .size)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
@ -37,5 +39,6 @@ class FileMetadata: EmbeddedObject, Codable {
try container.encode(ext, forKey: .ext) try container.encode(ext, forKey: .ext)
try container.encode(path, forKey: .path) try container.encode(path, forKey: .path)
try container.encode(relPath, forKey: .relPath) try container.encode(relPath, forKey: .relPath)
try container.encode(size, forKey: .size)
} }
} }

View file

@ -130,8 +130,8 @@ export const mutations = {
downloadItem.downloadItemParts = downloadItem.downloadItemParts.map(dip => { downloadItem.downloadItemParts = downloadItem.downloadItemParts.map(dip => {
let newDip = dip.id == downloadItemPart.id ? downloadItemPart : dip let newDip = dip.id == downloadItemPart.id ? downloadItemPart : dip
totalBytes += newDip.fileSize totalBytes += Number(newDip.fileSize)
totalBytesDownloaded += newDip.bytesDownloaded totalBytesDownloaded += Number(newDip.bytesDownloaded)
return newDip return newDip
}) })