summaryrefslogtreecommitdiff
path: root/macos/Sources/Features/Update/UpdateDriver.swift
diff options
context:
space:
mode:
Diffstat (limited to 'macos/Sources/Features/Update/UpdateDriver.swift')
-rw-r--r--macos/Sources/Features/Update/UpdateDriver.swift103
1 files changed, 103 insertions, 0 deletions
diff --git a/macos/Sources/Features/Update/UpdateDriver.swift b/macos/Sources/Features/Update/UpdateDriver.swift
new file mode 100644
index 000000000..00f74e9ed
--- /dev/null
+++ b/macos/Sources/Features/Update/UpdateDriver.swift
@@ -0,0 +1,103 @@
+import Sparkle
+
+/// Implement the SPUUserDriver to modify our UpdateViewModel for custom presentation.
+class UpdateDriver: NSObject, SPUUserDriver {
+ let viewModel: UpdateViewModel
+ let retryHandler: () -> Void
+
+ init(viewModel: UpdateViewModel, retryHandler: @escaping () -> Void) {
+ self.viewModel = viewModel
+ self.retryHandler = retryHandler
+ super.init()
+ }
+
+ func show(_ request: SPUUpdatePermissionRequest, reply: @escaping @Sendable (SUUpdatePermissionResponse) -> Void) {
+ viewModel.state = .permissionRequest(.init(request: request, reply: reply))
+ }
+
+ func showUserInitiatedUpdateCheck(cancellation: @escaping () -> Void) {
+ viewModel.state = .checking(.init(cancel: cancellation))
+ }
+
+ func showUpdateFound(with appcastItem: SUAppcastItem, state: SPUUserUpdateState, reply: @escaping @Sendable (SPUUserUpdateChoice) -> Void) {
+ viewModel.state = .updateAvailable(.init(appcastItem: appcastItem, reply: reply))
+ }
+
+ func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
+ // We don't do anything with the release notes here because Ghostty
+ // doesn't use the release notes feature of Sparkle currently.
+ }
+
+ func showUpdateReleaseNotesFailedToDownloadWithError(_ error: any Error) {
+ // We don't do anything with release notes. See `showUpdateReleaseNotes`
+ }
+
+ func showUpdateNotFoundWithError(_ error: any Error, acknowledgement: @escaping () -> Void) {
+ viewModel.state = .notFound
+ // TODO: Do we need to acknowledge?
+ }
+
+ func showUpdaterError(_ error: any Error, acknowledgement: @escaping () -> Void) {
+ viewModel.state = .error(.init(error: error, retry: retryHandler))
+ }
+
+ func showDownloadInitiated(cancellation: @escaping () -> Void) {
+ viewModel.state = .downloading(.init(
+ cancel: cancellation,
+ expectedLength: nil,
+ progress: 0))
+ }
+
+ func showDownloadDidReceiveExpectedContentLength(_ expectedContentLength: UInt64) {
+ guard case let .downloading(downloading) = viewModel.state else {
+ return
+ }
+
+ viewModel.state = .downloading(.init(
+ cancel: downloading.cancel,
+ expectedLength: expectedContentLength,
+ progress: 0))
+ }
+
+ func showDownloadDidReceiveData(ofLength length: UInt64) {
+ guard case let .downloading(downloading) = viewModel.state else {
+ return
+ }
+
+ viewModel.state = .downloading(.init(
+ cancel: downloading.cancel,
+ expectedLength: downloading.expectedLength,
+ progress: downloading.progress + length))
+ }
+
+ func showDownloadDidStartExtractingUpdate() {
+ viewModel.state = .extracting(.init(progress: 0))
+ }
+
+ func showExtractionReceivedProgress(_ progress: Double) {
+ viewModel.state = .extracting(.init(progress: progress))
+ }
+
+ func showReady(toInstallAndRelaunch reply: @escaping @Sendable (SPUUserUpdateChoice) -> Void) {
+ viewModel.state = .readyToInstall(.init(reply: reply))
+ }
+
+ func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) {
+ viewModel.state = .installing
+ }
+
+ func showUpdateInstalledAndRelaunched(_ relaunched: Bool, acknowledgement: @escaping () -> Void) {
+ // We don't do anything here.
+ viewModel.state = .idle
+ }
+
+ func showUpdateInFocus() {
+ // We don't currently implement this because our update state is
+ // shown in a terminal window. We may want to implement this at some
+ // point to handle the case that no windows are open, though.
+ }
+
+ func dismissUpdateInstallation() {
+ viewModel.state = .idle
+ }
+}