StikJIT/Sources/SynchronousDDIDownloader.swift
2026-08-18 11:03:43 -06:00

57 lines
2.5 KiB
Swift

import Foundation
enum SynchronousDDIDownloader {
static func download(to paths: DDIPaths,
progress: @escaping (Double, String) -> Void) throws {
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 120
configuration.timeoutIntervalForResource = 600
let session = URLSession(configuration: configuration)
defer { session.finishTasksAndInvalidate() }
let items = DDIDownloadCatalog.items(for: paths)
let total = Double(items.count)
for (index, item) in items.enumerated() {
progress(Double(index) / total, "Downloading \(item.name)...")
try download(item, session: session)
progress(Double(index + 1) / total, "\(item.name) ready")
}
}
private static func download(_ item: DDIDownloadItem, session: URLSession) throws {
let semaphore = DispatchSemaphore(value: 0)
let result = DownloadResult()
let task = session.downloadTask(with: item.url) { temporaryURL, response, error in
defer { semaphore.signal() }
do {
if let error { throw error }
guard let temporaryURL, let httpResponse = response as? HTTPURLResponse else {
throw StikJITError.ddiDownload("invalid response for \(item.url.absoluteString)")
}
guard (200..<300).contains(httpResponse.statusCode) else {
throw StikJITError.ddiDownload("HTTP \(httpResponse.statusCode) for \(item.url.absoluteString)")
}
let destinationURL = URL(fileURLWithPath: item.destinationPath)
let fileManager = FileManager.default
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL)
}
try fileManager.moveItem(at: temporaryURL, to: destinationURL)
} catch {
result.error = error
}
}
task.resume()
semaphore.wait()
if let error = result.error {
if let stikJITError = error as? StikJITError { throw stikJITError }
throw StikJITError.ddiDownload(error.localizedDescription)
}
}
}
private final class DownloadResult: @unchecked Sendable {
var error: Error?
}