1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
import Cocoa
import Sparkle
/// Implement the SPUUserDriver to modify our UpdateViewModel for custom presentation.
class UpdateDriver: NSObject, SPUUserDriver {
let viewModel: UpdateViewModel
init(viewModel: UpdateViewModel) {
self.viewModel = viewModel
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: {
guard let delegate = NSApp.delegate as? AppDelegate else {
return
}
// TODO fill this in
},
dismiss: { [weak viewModel] in
viewModel?.state = .idle
}))
}
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
}
}
|