91 lines
2.6 KiB
Swift
91 lines
2.6 KiB
Swift
import Sparkle
|
|
import Cocoa
|
|
import Combine
|
|
|
|
/// Controller for managing Sparkle updates in cmux.
|
|
class UpdateController {
|
|
private(set) var updater: SPUUpdater
|
|
private let userDriver: UpdateDriver
|
|
private var installCancellable: AnyCancellable?
|
|
|
|
var viewModel: UpdateViewModel {
|
|
userDriver.viewModel
|
|
}
|
|
|
|
/// True if we're force-installing an update.
|
|
var isInstalling: Bool {
|
|
installCancellable != nil
|
|
}
|
|
|
|
init() {
|
|
let hostBundle = Bundle.main
|
|
self.userDriver = UpdateDriver(viewModel: .init(), hostBundle: hostBundle)
|
|
self.updater = SPUUpdater(
|
|
hostBundle: hostBundle,
|
|
applicationBundle: hostBundle,
|
|
userDriver: userDriver,
|
|
delegate: userDriver
|
|
)
|
|
}
|
|
|
|
deinit {
|
|
installCancellable?.cancel()
|
|
}
|
|
|
|
/// Start the updater. If startup fails, the error is shown via the custom UI.
|
|
func startUpdater() {
|
|
do {
|
|
try updater.start()
|
|
} catch {
|
|
userDriver.viewModel.state = .error(.init(
|
|
error: error,
|
|
retry: { [weak self] in
|
|
self?.userDriver.viewModel.state = .idle
|
|
self?.startUpdater()
|
|
},
|
|
dismiss: { [weak self] in
|
|
self?.userDriver.viewModel.state = .idle
|
|
}
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Force install the current update by auto-confirming all installable states.
|
|
func installUpdate() {
|
|
guard viewModel.state.isInstallable else { return }
|
|
guard installCancellable == nil else { return }
|
|
|
|
installCancellable = viewModel.$state.sink { [weak self] state in
|
|
guard let self else { return }
|
|
guard state.isInstallable else {
|
|
self.installCancellable = nil
|
|
return
|
|
}
|
|
state.confirm()
|
|
}
|
|
}
|
|
|
|
/// Check for updates (used by the menu item).
|
|
@objc func checkForUpdates() {
|
|
UpdateLogStore.shared.append("checkForUpdates invoked (state=\(viewModel.state.isIdle ? "idle" : "busy"))")
|
|
if viewModel.state == .idle {
|
|
updater.checkForUpdates()
|
|
return
|
|
}
|
|
|
|
installCancellable?.cancel()
|
|
viewModel.state.cancel()
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
|
|
self?.updater.checkForUpdates()
|
|
}
|
|
}
|
|
|
|
/// Validate the check for updates menu item.
|
|
func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
|
if item.action == #selector(checkForUpdates) {
|
|
return updater.canCheckForUpdates
|
|
}
|
|
return true
|
|
}
|
|
}
|