mirror of https://github.com/cirruslabs/tart.git
Report progress when downloading IPSW files (#768)
The URLSession async/await functions do not report progress through the normal URLSessionTaskDelegate callbacks, as reported in: https://developer.apple.com/forums/thread/723015 We don't want to use URLSession.bytes, as that results in a much slower download speed compared to URLSession.download, but we can work around the lack of progress callbacks by observing the progress on the URLSessionTask itself. Fixes #767
This commit is contained in:
parent
d8b010c79c
commit
2b33b8f9e6
|
|
@ -3,19 +3,32 @@ import AsyncAlgorithms
|
|||
|
||||
fileprivate let urlSession = createURLSession()
|
||||
|
||||
class Fetcher {
|
||||
static func fetch(_ request: URLRequest, viaFile: Bool = false) async throws -> (AsyncThrowingChannel<Data, Error>, 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<Data, Error>, 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<Data, Error>, 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<Data, Error>, HTTPURLResponse) {
|
||||
let dataCh = AsyncThrowingChannel<Data, Error>()
|
||||
|
||||
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<Data, Error>, HTTPURLResponse) {
|
||||
private static func fetchViaFile(_ request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (AsyncThrowingChannel<Data, Error>, HTTPURLResponse) {
|
||||
let dataCh = AsyncThrowingChannel<Data, Error>()
|
||||
|
||||
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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue