Allow changelog URL callback to be set

This commit is contained in:
2024-06-16 18:25:44 +02:00
parent a4f1f0e33d
commit 50da591a6a
3 changed files with 49 additions and 33 deletions

View File

@ -10,7 +10,11 @@ let package = Package(
products: [ products: [
.library(name: "NVAppUpdater", targets: ["NVAppUpdater"]), .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]),
], ],
dependencies: [
.package(name: "NVAlert", path: "/Users/nicoverbruggen/Code/SwiftPM/NVAlert")
// .package(url: "https://github.com/nicoverbruggen/NVAlert", branch: "main")
],
targets: [ targets: [
.target(name: "NVAppUpdater"), .target(name: "NVAppUpdater", dependencies: ["NVAlert"]),
] ]
) )

View File

@ -5,16 +5,17 @@
import Foundation import Foundation
import Cocoa import Cocoa
import NVAlert
open class UpdateCheck open class UpdateCheck
{ {
let caskUrl: URL let caskUrl: URL
let promptOnFailure: Bool
let selfUpdaterName: String let selfUpdaterName: String
let selfUpdaterPath: String let selfUpdaterPath: String
var caskFile: CaskFile! private var releaseNotesUrlCallback: ((CaskFile) -> URL?)? = nil
var newerVersion: AppVersion! private var caskFile: CaskFile!
private var newerVersion: AppVersion!
/** /**
* Create a new update check instance. Once created, you should call `perform` on this instance. * Create a new update check instance. Once created, you should call `perform` on this instance.
@ -28,30 +29,33 @@ open class UpdateCheck
* *
* - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will
* be followed when retrieving and validating the Cask file. * be followed when retrieving and validating the Cask file.
*
* - Parameter promptOnFailure: Whether user interaction is required when failing to check
* or no new update is found. A user usually expects a prompt if they manually searched
* for updates.
*/ */
public init( public init(
selfUpdaterName: String, selfUpdaterName: String,
selfUpdaterPath: String, selfUpdaterPath: String,
caskUrl: URL, caskUrl: URL
promptOnFailure: Bool
) { ) {
self.selfUpdaterName = selfUpdaterName self.selfUpdaterName = selfUpdaterName
self.selfUpdaterPath = selfUpdaterPath self.selfUpdaterPath = selfUpdaterPath
self.caskUrl = caskUrl self.caskUrl = caskUrl
self.promptOnFailure = promptOnFailure }
public func resolvingReleaseNotes(with callback: @escaping (CaskFile) -> URL?) -> Self {
self.releaseNotesUrlCallback = callback
return self
} }
/** /**
Perform the check for a new version. * Perform the check for a new version.
*
* - Parameter promptOnFailure: Whether user interaction is required when failing to check
* or no new update is found. A user usually expects a prompt if they manually searched
* for updates.
*/ */
public func perform() async { public func perform(promptOnFailure: Bool = true) async {
guard let caskFile = await CaskFile.from(url: caskUrl) else { guard let caskFile = CaskFile.from(url: caskUrl) else {
Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.")
return await presentCouldNotRetrieveUpdate() return await presentCouldNotRetrieveUpdate(promptOnFailure)
} }
self.caskFile = caskFile self.caskFile = caskFile
@ -60,7 +64,7 @@ open class UpdateCheck
guard let onlineVersion = AppVersion.from(caskFile.version) else { guard let onlineVersion = AppVersion.from(caskFile.version) else {
Log.text("The version string from the CaskFile could not be read.") Log.text("The version string from the CaskFile could not be read.")
return await presentCouldNotRetrieveUpdate() return await presentCouldNotRetrieveUpdate(promptOnFailure)
} }
self.newerVersion = onlineVersion self.newerVersion = onlineVersion
@ -71,13 +75,13 @@ open class UpdateCheck
if onlineVersion > currentVersion { if onlineVersion > currentVersion {
await presentNewerVersionAvailable() await presentNewerVersionAvailable()
} else if promptOnFailure { } else if promptOnFailure {
await presentVersionIsUpToDate() await presentVersionIsUpToDate(promptOnFailure)
} }
} }
// MARK: - Alerts // MARK: - Alerts
private func presentCouldNotRetrieveUpdate() async { private func presentCouldNotRetrieveUpdate(_ promptOnFailure: Bool) async {
Log.text("Could not retrieve update manifest!") Log.text("Could not retrieve update manifest!")
if promptOnFailure { if promptOnFailure {
@ -88,7 +92,7 @@ open class UpdateCheck
} }
} }
private func presentVersionIsUpToDate() async { private func presentVersionIsUpToDate(_ promptOnFailure: Bool) async {
Log.text("Version is up-to-date!") Log.text("Version is up-to-date!")
if promptOnFailure { if promptOnFailure {
@ -104,22 +108,30 @@ open class UpdateCheck
let current = AppVersion.fromCurrentVersion() let current = AppVersion.fromCurrentVersion()
let outcome = await Alert.choose( let alert = await NVAlert().withInformation(
title: "An updated version of \(Executable.name) is available.", title: "An updated version of \(Executable.name) is available.",
description: """ subtitle: "Version \(newerVersion.version) is available for download.",
Version \(newerVersion.version) is available for download. description: "Do you want to download and install this updated version?"
(This is currently version \(current.version).) )
.withPrimary(
text: "Install",
action: { vc in
vc.close(with: .OK)
self.launchSelfUpdater()
}
)
.withTertiary(text: "Dismiss", action: { vc in
vc.close(with: .OK)
})
Do you want to download and install this updated version? if let callback = self.releaseNotesUrlCallback,
""", let url = callback(self.caskFile) {
options: [ await alert.withSecondary(text: "View Release Notes") { _ in
"Update Now", NSWorkspace.shared.open(url)
"Cancel" }
])
if outcome == .alertFirstButtonReturn {
launchSelfUpdater()
} }
await alert.show()
} }
// MARK: - Functional // MARK: - Functional

View File

@ -5,7 +5,7 @@
import Foundation import Foundation
struct CaskFile { public struct CaskFile {
var properties: [String: String] var properties: [String: String]
var name: String { return self.properties["name"]! } var name: String { return self.properties["name"]! }