Skip to content

Commit

Permalink
downloader: support caching tools download Last-Modified
Browse files Browse the repository at this point in the history
Resolves #5692
  • Loading branch information
osy committed Oct 8, 2023
1 parent 33b52cc commit f083b65
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Platform/UTMData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ struct AlertMessage: Identifiable {

func mountSupportTools(for vm: UTMQemuVirtualMachine) async throws {
let task = UTMDownloadSupportToolsTask(for: vm)
if task.hasExistingSupportTools {
if await task.hasExistingSupportTools {
vm.config.qemu.isGuestToolsInstallRequested = false
_ = try await task.mountTools()
} else {
Expand Down
2 changes: 1 addition & 1 deletion Platform/UTMDownloadIPSWTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class UTMDownloadIPSWTask: UTMDownloadTask {
super.init(for: config.system.boot.macRecoveryIpswURL!, named: config.information.name)
}

override func processCompletedDownload(at location: URL) async throws -> any UTMVirtualMachine {
override func processCompletedDownload(at location: URL, response: URLResponse?) async throws -> any UTMVirtualMachine {
if !fileManager.fileExists(atPath: cacheUrl.path) {
try fileManager.createDirectory(at: cacheUrl, withIntermediateDirectories: false)
}
Expand Down
25 changes: 17 additions & 8 deletions Platform/UTMDownloadSupportToolsTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,24 @@ class UTMDownloadSupportToolsTask: UTMDownloadTask {

private static let supportToolsDownloadUrl = URL(string: "https://getutm.app/downloads/utm-guest-tools-latest.iso")!

private var cacheUrl: URL {
fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
private var toolsUrl: URL {
fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("GuestSupportTools")
}

private var supportToolsLocalUrl: URL {
cacheUrl.appendingPathComponent(Self.supportToolsDownloadUrl.lastPathComponent)
toolsUrl.appendingPathComponent(Self.supportToolsDownloadUrl.lastPathComponent)
}


@Setting("LastDownloadedGuestTools")
private var lastDownloadGuestTools: Int = 0

var hasExistingSupportTools: Bool {
fileManager.fileExists(atPath: supportToolsLocalUrl.path)
get async {
guard fileManager.fileExists(atPath: supportToolsLocalUrl.path) else {
return false
}
return await lastModifiedTimestamp <= lastDownloadGuestTools
}
}

init(for vm: UTMQemuVirtualMachine) {
Expand All @@ -40,14 +48,15 @@ class UTMDownloadSupportToolsTask: UTMDownloadTask {
super.init(for: Self.supportToolsDownloadUrl, named: name)
}

override func processCompletedDownload(at location: URL) async throws -> any UTMVirtualMachine {
if !fileManager.fileExists(atPath: cacheUrl.path) {
try fileManager.createDirectory(at: cacheUrl, withIntermediateDirectories: true)
override func processCompletedDownload(at location: URL, response: URLResponse?) async throws -> any UTMVirtualMachine {
if !fileManager.fileExists(atPath: toolsUrl.path) {
try fileManager.createDirectory(at: toolsUrl, withIntermediateDirectories: true)
}
if fileManager.fileExists(atPath: supportToolsLocalUrl.path) {
try fileManager.removeItem(at: supportToolsLocalUrl)
}
try fileManager.moveItem(at: location, to: supportToolsLocalUrl)
lastDownloadGuestTools = lastModifiedTimestamp(for: response) ?? 0
return try await mountTools()
}

Expand Down
32 changes: 30 additions & 2 deletions Platform/UTMDownloadTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,28 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
FileManager.default
}

/// Find the Last-Modified date as a Unix timestamp
var lastModifiedTimestamp: Int {
get async {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
guard let (_, response) = try? await URLSession.shared.data(for: request) else {
return 0
}
return lastModifiedTimestamp(for: response) ?? 0
}
}

init(for url: URL, named name: String) {
self.url = url
self.name = name
}

/// Called by subclass when download is completed
/// - Parameter location: Downloaded file location
/// - Parameter response: URL response of the download
/// - Returns: Processed UTM virtual machine
func processCompletedDownload(at location: URL) async throws -> any UTMVirtualMachine {
func processCompletedDownload(at location: URL, response: URLResponse?) async throws -> any UTMVirtualMachine {
throw "Not Implemented"
}

Expand All @@ -67,7 +80,7 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
Task {
await pendingVM.setDownloadFinishedNowProcessing()
do {
let vm = try await processCompletedDownload(at: tmpUrl)
let vm = try await processCompletedDownload(at: tmpUrl, response: sessionTask.response)
taskContinuation.resume(returning: vm)
} catch {
taskContinuation.resume(throwing: error)
Expand Down Expand Up @@ -165,4 +178,19 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
func cancel() {
downloadTask?.cancel()
}

/// Get the Last-Modified header as a Unix timestamp
/// - Parameter response: URL response
/// - Returns: Unix timestamp
func lastModifiedTimestamp(for response: URLResponse?) -> Int? {
guard let headers = (response as? HTTPURLResponse)?.allHeaderFields, let lastModified = headers["Last-Modified"] as? String else {
return nil
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
guard let lastModifiedDate = dateFormatter.date(from: lastModified) else {
return nil
}
return Int(lastModifiedDate.timeIntervalSince1970)
}
}
2 changes: 1 addition & 1 deletion Platform/UTMDownloadVMTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class UTMDownloadVMTask: UTMDownloadTask {
return nameWithoutZIP
}

override func processCompletedDownload(at location: URL) async throws -> any UTMVirtualMachine {
override func processCompletedDownload(at location: URL, response: URLResponse?) async throws -> any UTMVirtualMachine {
let tempDir = fileManager.temporaryDirectory
let originalFilename = url.lastPathComponent
let downloadedZip = tempDir.appendingPathComponent(originalFilename)
Expand Down

0 comments on commit f083b65

Please sign in to comment.