mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-28 22:27:14 +02:00
Switch to async logic
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
C4B265F5285A5A6600E1B5C3 /* LocalLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */; };
|
C4B265F5285A5A6600E1B5C3 /* LocalLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */; };
|
||||||
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D0677428106D0C00B8F875 /* DataClasses.swift */; };
|
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D0677428106D0C00B8F875 /* DataClasses.swift */; };
|
||||||
E99C8C932883A00F00E3279A /* LocalLibraryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99C8C922883A00F00E3279A /* LocalLibraryExtensions.swift */; };
|
E99C8C932883A00F00E3279A /* LocalLibraryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99C8C922883A00F00E3279A /* LocalLibraryExtensions.swift */; };
|
||||||
|
E9D38158289A0A6F0019EEED /* LibraryItemDownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D38157289A0A6F0019EEED /* LibraryItemDownloadSession.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalLibrary.swift; sourceTree = "<group>"; };
|
C4B265F4285A5A6600E1B5C3 /* LocalLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalLibrary.swift; sourceTree = "<group>"; };
|
||||||
C4D0677428106D0C00B8F875 /* DataClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataClasses.swift; sourceTree = "<group>"; };
|
C4D0677428106D0C00B8F875 /* DataClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataClasses.swift; sourceTree = "<group>"; };
|
||||||
E99C8C922883A00F00E3279A /* LocalLibraryExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalLibraryExtensions.swift; sourceTree = "<group>"; };
|
E99C8C922883A00F00E3279A /* LocalLibraryExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalLibraryExtensions.swift; sourceTree = "<group>"; };
|
||||||
|
E9D38157289A0A6F0019EEED /* LibraryItemDownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItemDownloadSession.swift; sourceTree = "<group>"; };
|
||||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@
|
|||||||
3AF1970B2806E2590096F747 /* ApiClient.swift */,
|
3AF1970B2806E2590096F747 /* ApiClient.swift */,
|
||||||
3AB34052280829BF0039308B /* Extensions.swift */,
|
3AB34052280829BF0039308B /* Extensions.swift */,
|
||||||
3AB34054280832720039308B /* PlayerEvents.swift */,
|
3AB34054280832720039308B /* PlayerEvents.swift */,
|
||||||
|
E9D38157289A0A6F0019EEED /* LibraryItemDownloadSession.swift */,
|
||||||
);
|
);
|
||||||
path = util;
|
path = util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -341,6 +344,7 @@
|
|||||||
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */,
|
C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */,
|
||||||
4D66B954282EE87C008272D4 /* AbsDownloader.swift in Sources */,
|
4D66B954282EE87C008272D4 /* AbsDownloader.swift in Sources */,
|
||||||
3AB34055280832720039308B /* PlayerEvents.swift in Sources */,
|
3AB34055280832720039308B /* PlayerEvents.swift in Sources */,
|
||||||
|
E9D38158289A0A6F0019EEED /* LibraryItemDownloadSession.swift in Sources */,
|
||||||
E99C8C932883A00F00E3279A /* LocalLibraryExtensions.swift in Sources */,
|
E99C8C932883A00F00E3279A /* LocalLibraryExtensions.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import Capacitor
|
|||||||
|
|
||||||
@objc(AbsDownloader)
|
@objc(AbsDownloader)
|
||||||
public class AbsDownloader: CAPPlugin {
|
public class AbsDownloader: CAPPlugin {
|
||||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
||||||
|
|
||||||
@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")
|
let episodeId = call.getString("episodeId")
|
||||||
@@ -24,90 +22,19 @@ public class AbsDownloader: CAPPlugin {
|
|||||||
call.resolve()
|
call.resolve()
|
||||||
} else {
|
} else {
|
||||||
NSLog("Got library item from server \(libraryItem!.id)")
|
NSLog("Got library item from server \(libraryItem!.id)")
|
||||||
self.startLibraryItemDownload(item: libraryItem!)
|
Task {
|
||||||
call.resolve()
|
do {
|
||||||
|
let downloadSession = LibraryItemDownloadSession(libraryItem!)
|
||||||
|
let localLibraryItem = try await downloadSession.startDownload()
|
||||||
|
Database.shared.saveLocalLibraryItem(localLibraryItem: localLibraryItem)
|
||||||
|
} catch {
|
||||||
|
NSLog("Failed to download \(error)")
|
||||||
|
}
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startLibraryItemDownload(item: LibraryItem) {
|
|
||||||
let length = item.media.tracks?.count ?? 0
|
|
||||||
if length > 0 {
|
|
||||||
let downloadDispatch = DispatchGroup()
|
|
||||||
let files = item.media.tracks!.enumerated().map {
|
|
||||||
position, track -> LocalFile in startLibraryItemTrackDownload(item: item, position: position, track: track, dispatch: downloadDispatch)
|
|
||||||
}
|
|
||||||
downloadDispatch.notify(queue: .main) {
|
|
||||||
let localLibraryItem = LocalLibraryItem(item, localUrl: self.documentsDirectory, server: Store.serverConfig!, files: files)
|
|
||||||
Database.shared.saveLocalLibraryItem(localLibraryItem: localLibraryItem)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NSLog("No audio tracks for the supplied library item")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startLibraryItemTrackDownload(item: LibraryItem, position: Int, track: AudioTrack, dispatch: DispatchGroup) -> LocalFile {
|
|
||||||
NSLog("TRACK \(track.contentUrl!)")
|
|
||||||
|
|
||||||
// If we don't name metadata, then we can't proceed
|
|
||||||
guard let filename = track.metadata?.filename else {
|
|
||||||
NSLog("No metadata for track, unable to download")
|
|
||||||
return LocalFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
let serverUrl = urlForTrack(item: item, track: track)
|
|
||||||
let itemDirectory = createLibraryItemFileDirectory(item: item)
|
|
||||||
let localUrl = itemDirectory.appendingPathComponent("\(filename)")
|
|
||||||
|
|
||||||
downloadTrack(serverUrl: serverUrl, localUrl: localUrl, dispatch: dispatch)
|
|
||||||
return LocalFile(item.id, filename, track.mimeType, localUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func urlForTrack(item: LibraryItem, track: AudioTrack) -> URL {
|
|
||||||
// filename needs to be encoded otherwise would just use contentUrl
|
|
||||||
let filenameEncoded = track.metadata?.filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
|
|
||||||
let urlstr = "\(Store.serverConfig!.address)/s/item/\(item.id)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
|
||||||
return URL(string: urlstr)!
|
|
||||||
}
|
|
||||||
|
|
||||||
private func createLibraryItemFileDirectory(item: LibraryItem) -> URL {
|
|
||||||
let itemDirectory = documentsDirectory.appendingPathComponent("\(item.id)")
|
|
||||||
|
|
||||||
NSLog("ITEM DIR \(itemDirectory)")
|
|
||||||
|
|
||||||
// Create library item directory
|
|
||||||
do {
|
|
||||||
try FileManager.default.createDirectory(at: itemDirectory, withIntermediateDirectories: true)
|
|
||||||
} catch {
|
|
||||||
NSLog("Failed to CREATE LI DIRECTORY \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
private func downloadTrack(serverUrl: URL, localUrl: URL, dispatch: DispatchGroup) {
|
|
||||||
dispatch.enter()
|
|
||||||
|
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: serverUrl) { urlOrNil, responseOrNil, errorOrNil in
|
|
||||||
defer { dispatch.leave() }
|
|
||||||
|
|
||||||
guard let fileURL = urlOrNil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
NSLog("Download TMP file URL \(fileURL)")
|
|
||||||
let audioData = try Data(contentsOf:fileURL)
|
|
||||||
try audioData.write(to: localUrl)
|
|
||||||
NSLog("Download written to \(localUrl)")
|
|
||||||
} catch {
|
|
||||||
NSLog("FILE ERROR: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the download
|
|
||||||
downloadTask.resume()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DownloadItem: Codable {
|
struct DownloadItem: Codable {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// LibraryItemDownloadSession.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Created by Ron Heft on 8/2/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum LibraryItemDownloadError: String, Error {
|
||||||
|
case noTracks = "No tracks on library item"
|
||||||
|
case noMetadata = "No metadata for track, unable to download"
|
||||||
|
case failedDownload = "Failed to download item"
|
||||||
|
}
|
||||||
|
|
||||||
|
class LibraryItemDownloadSession {
|
||||||
|
|
||||||
|
let item: LibraryItem
|
||||||
|
|
||||||
|
private let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
|
||||||
|
init(_ item: LibraryItem) {
|
||||||
|
self.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
public func startDownload() async throws -> LocalLibraryItem {
|
||||||
|
guard let tracks = item.media.tracks else {
|
||||||
|
throw LibraryItemDownloadError.noTracks
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await withThrowingTaskGroup(of: LocalFile.self, returning: LocalLibraryItem.self) { group in
|
||||||
|
for (position, track) in tracks.enumerated() {
|
||||||
|
group.addTask { try await self.startLibraryItemTrackDownload(item: self.item, position: position, track: track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = [LocalFile]()
|
||||||
|
for try await file in group {
|
||||||
|
files.append(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalLibraryItem(self.item, localUrl: self.documentsDirectory, server: Store.serverConfig!, files: files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLibraryItemTrackDownload(item: LibraryItem, position: Int, track: AudioTrack) async throws -> LocalFile {
|
||||||
|
NSLog("TRACK \(track.contentUrl!)")
|
||||||
|
|
||||||
|
// If we don't name metadata, then we can't proceed
|
||||||
|
guard let filename = track.metadata?.filename else {
|
||||||
|
throw LibraryItemDownloadError.noMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverUrl = urlForTrack(item: item, track: track)
|
||||||
|
let itemDirectory = createLibraryItemFileDirectory(item: item)
|
||||||
|
let localUrl = itemDirectory.appendingPathComponent("\(filename)")
|
||||||
|
|
||||||
|
try await downloadFile(serverUrl: serverUrl, localUrl: localUrl)
|
||||||
|
return LocalFile(item.id, filename, track.mimeType, localUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createLibraryItemFileDirectory(item: LibraryItem) -> URL {
|
||||||
|
let itemDirectory = documentsDirectory.appendingPathComponent("\(item.id)")
|
||||||
|
|
||||||
|
NSLog("ITEM DIR \(itemDirectory)")
|
||||||
|
|
||||||
|
// Create library item directory
|
||||||
|
do {
|
||||||
|
try FileManager.default.createDirectory(at: itemDirectory, withIntermediateDirectories: true)
|
||||||
|
} catch {
|
||||||
|
NSLog("Failed to CREATE LI DIRECTORY \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
private func urlForTrack(item: LibraryItem, track: AudioTrack) -> URL {
|
||||||
|
// filename needs to be encoded otherwise would just use contentUrl
|
||||||
|
let filenameEncoded = track.metadata?.filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
|
||||||
|
let urlstr = "\(Store.serverConfig!.address)/s/item/\(item.id)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
|
||||||
|
return URL(string: urlstr)!
|
||||||
|
}
|
||||||
|
|
||||||
|
private func downloadFile(serverUrl: URL, localUrl: URL) async throws {
|
||||||
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
|
let downloadTask = URLSession.shared.downloadTask(with: serverUrl) { urlOrNil, responseOrNil, errorOrNil in
|
||||||
|
guard let tempUrl = urlOrNil else {
|
||||||
|
continuation.resume(throwing: errorOrNil!)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try FileManager.default.moveItem(at: tempUrl, to: localUrl)
|
||||||
|
continuation.resume()
|
||||||
|
} catch {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadTask.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user