diff --git a/Sources/tart/Fetcher.swift b/Sources/tart/Fetcher.swift index 461ad59..77a4368 100644 --- a/Sources/tart/Fetcher.swift +++ b/Sources/tart/Fetcher.swift @@ -3,19 +3,32 @@ import AsyncAlgorithms fileprivate let urlSession = createURLSession() -class Fetcher { - static func fetch(_ request: URLRequest, viaFile: Bool = false) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { - if viaFile { - return try await fetchViaFile(request) - } - - return try await fetchViaMemory(request) +class DownloadDelegate: NSObject, URLSessionTaskDelegate { + let progress: Progress + init(_ progress: Progress) throws { + self.progress = progress } - private static func fetchViaMemory(_ request: URLRequest) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { + func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { + self.progress.addChild(task.progress, withPendingUnitCount: self.progress.totalUnitCount) + } +} + +class Fetcher { + static func fetch(_ request: URLRequest, viaFile: Bool = false, progress: Progress? = nil) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { + let delegate = progress != nil ? try DownloadDelegate(progress!) : nil + + if viaFile { + return try await fetchViaFile(request, delegate: delegate) + } + + return try await fetchViaMemory(request, delegate: delegate) + } + + private static func fetchViaMemory(_ request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { let dataCh = AsyncThrowingChannel() - let (data, response) = try await urlSession.data(for: request) + let (data, response) = try await urlSession.data(for: request, delegate: delegate) Task { await dataCh.send(data) @@ -26,10 +39,10 @@ class Fetcher { return (dataCh, response as! HTTPURLResponse) } - private static func fetchViaFile(_ request: URLRequest) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { + private static func fetchViaFile(_ request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (AsyncThrowingChannel, HTTPURLResponse) { let dataCh = AsyncThrowingChannel() - let (fileURL, response) = try await urlSession.download(for: request) + let (fileURL, response) = try await urlSession.download(for: request, delegate: delegate) // Acquire a handle to the downloaded file and then remove it. // diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift index 6345588..e825d55 100644 --- a/Sources/tart/VM.swift +++ b/Sources/tart/VM.swift @@ -91,12 +91,17 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { // Download the IPSW defaultLogger.appendNewLine("Fetching \(remoteURL.lastPathComponent)...") - let (channel, response) = try await Fetcher.fetch(URLRequest(url: remoteURL), viaFile: true) + let downloadProgress = Progress(totalUnitCount: 100) + ProgressObserver(downloadProgress).log(defaultLogger) - let progress = Progress(totalUnitCount: response.expectedContentLength) - ProgressObserver(progress).log(defaultLogger) + let request = URLRequest(url: remoteURL) + let (channel, response) = try await Fetcher.fetch(request, viaFile: true, progress: downloadProgress) let temporaryLocation = try Config().tartTmpDir.appendingPathComponent(UUID().uuidString + ".ipsw") + defaultLogger.appendNewLine("Computing digest for \(temporaryLocation.path)...") + let digestProgress = Progress(totalUnitCount: response.expectedContentLength) + ProgressObserver(digestProgress).log(defaultLogger) + FileManager.default.createFile(atPath: temporaryLocation.path, contents: nil) let lock = try FileLock(lockURL: temporaryLocation) try lock.lock() @@ -108,7 +113,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { let chunkAsData = Data(chunk) fileHandle.write(chunkAsData) digest.update(chunkAsData) - progress.completedUnitCount += Int64(chunk.count) + digestProgress.completedUnitCount += Int64(chunk.count) } try fileHandle.close()