update progress
This commit is contained in:
@@ -1,12 +1,59 @@
|
||||
import AppKit
|
||||
import Sparkle
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class UpdateProgress {
|
||||
var title = ""
|
||||
var done = 0.0
|
||||
var total: Double?
|
||||
var detail = ""
|
||||
var cancel: (() -> Void)?
|
||||
}
|
||||
|
||||
private struct UpdateProgressView: View {
|
||||
let progress: UpdateProgress
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(progress.title)
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
.foregroundStyle(Palette.text)
|
||||
if let total = progress.total {
|
||||
ProgressView(value: min(progress.done, total), total: total)
|
||||
.tint(Palette.accent)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(.linear)
|
||||
.tint(Palette.accent)
|
||||
}
|
||||
HStack {
|
||||
Text(progress.detail)
|
||||
.font(Fonts.font(12))
|
||||
.foregroundStyle(Palette.muted)
|
||||
Spacer()
|
||||
if let cancel = progress.cancel {
|
||||
Button("Cancel", action: cancel)
|
||||
.buttonStyle(WebButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(width: 360)
|
||||
.background(Palette.bg.ignoresSafeArea())
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate {
|
||||
static let shared = Updater()
|
||||
private var updater: SPUUpdater?
|
||||
private var now = false
|
||||
private var version = ""
|
||||
private var staged: (item: SUAppcastItem, install: () -> Void)?
|
||||
private let progress = UpdateProgress()
|
||||
private var panel: NSPanel?
|
||||
|
||||
func start() {
|
||||
let updater = SPUUpdater(hostBundle: .main, applicationBundle: .main, userDriver: self, delegate: self)
|
||||
@@ -20,7 +67,8 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate {
|
||||
|
||||
func check() {
|
||||
if let staged {
|
||||
if ask(staged.item) {
|
||||
now = ask(staged.item)
|
||||
if now {
|
||||
staged.install()
|
||||
}
|
||||
return
|
||||
@@ -29,6 +77,7 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate {
|
||||
}
|
||||
|
||||
private func ask(_ item: SUAppcastItem) -> Bool {
|
||||
version = item.displayVersionString
|
||||
let current = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Byre \(item.displayVersionString) is available"
|
||||
@@ -47,9 +96,38 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate {
|
||||
alert.runModal()
|
||||
}
|
||||
|
||||
private func showProgress(_ title: String, total: Double?, cancel: (() -> Void)? = nil) {
|
||||
progress.title = title
|
||||
progress.done = 0
|
||||
progress.total = total
|
||||
progress.detail = ""
|
||||
progress.cancel = cancel
|
||||
if panel == nil {
|
||||
let p = NSPanel(contentRect: .zero, styleMask: [.titled, .fullSizeContentView], backing: .buffered, defer: false)
|
||||
p.titleVisibility = .hidden
|
||||
p.titlebarAppearsTransparent = true
|
||||
p.isReleasedWhenClosed = false
|
||||
p.contentViewController = NSHostingController(rootView: UpdateProgressView(progress: progress))
|
||||
p.center()
|
||||
panel = p
|
||||
}
|
||||
NSApp.activate()
|
||||
panel?.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
private func hideProgress() {
|
||||
panel?.orderOut(nil)
|
||||
panel = nil
|
||||
}
|
||||
|
||||
private func bytes(_ n: Double) -> String {
|
||||
Int64(n).formatted(.byteCount(style: .file))
|
||||
}
|
||||
|
||||
func updater(_ updater: SPUUpdater, willInstallUpdateOnQuit item: SUAppcastItem, immediateInstallationBlock immediateInstallHandler: @escaping () -> Void) -> Bool {
|
||||
staged = (item, immediateInstallHandler)
|
||||
if ask(item) {
|
||||
now = ask(item)
|
||||
if now {
|
||||
immediateInstallHandler()
|
||||
}
|
||||
return true
|
||||
@@ -80,29 +158,66 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate {
|
||||
}
|
||||
|
||||
func showUpdaterError(_ error: any Error, acknowledgement: @escaping () -> Void) {
|
||||
hideProgress()
|
||||
tell("Update failed", error.localizedDescription)
|
||||
acknowledgement()
|
||||
}
|
||||
|
||||
func showDownloadInitiated(cancellation: @escaping () -> Void) {}
|
||||
func showDownloadInitiated(cancellation: @escaping () -> Void) {
|
||||
guard now else {
|
||||
return
|
||||
}
|
||||
showProgress("Downloading Byre \(version)", total: nil) { [weak self] in
|
||||
self?.now = false
|
||||
self?.hideProgress()
|
||||
cancellation()
|
||||
}
|
||||
}
|
||||
|
||||
func showDownloadDidReceiveExpectedContentLength(_ expectedContentLength: UInt64) {}
|
||||
func showDownloadDidReceiveExpectedContentLength(_ expectedContentLength: UInt64) {
|
||||
guard expectedContentLength > 0 else {
|
||||
return
|
||||
}
|
||||
progress.total = Double(expectedContentLength)
|
||||
}
|
||||
|
||||
func showDownloadDidReceiveData(ofLength length: UInt64) {}
|
||||
func showDownloadDidReceiveData(ofLength length: UInt64) {
|
||||
progress.done += Double(length)
|
||||
if let total = progress.total {
|
||||
progress.detail = "\(bytes(progress.done)) of \(bytes(total))"
|
||||
} else {
|
||||
progress.detail = bytes(progress.done)
|
||||
}
|
||||
}
|
||||
|
||||
func showDownloadDidStartExtractingUpdate() {}
|
||||
func showDownloadDidStartExtractingUpdate() {
|
||||
guard now else {
|
||||
return
|
||||
}
|
||||
showProgress("Extracting Byre \(version)", total: 1)
|
||||
}
|
||||
|
||||
func showExtractionReceivedProgress(_ progress: Double) {}
|
||||
func showExtractionReceivedProgress(_ progress: Double) {
|
||||
self.progress.done = progress
|
||||
}
|
||||
|
||||
func showReady(toInstallAndRelaunch reply: @escaping (SPUUserUpdateChoice) -> Void) {
|
||||
reply(now ? .install : .dismiss)
|
||||
}
|
||||
|
||||
func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) {}
|
||||
func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) {
|
||||
guard now else {
|
||||
return
|
||||
}
|
||||
showProgress("Installing Byre \(version)", total: nil)
|
||||
}
|
||||
|
||||
func showUpdateInstalledAndRelaunched(_ relaunched: Bool, acknowledgement: @escaping () -> Void) {
|
||||
hideProgress()
|
||||
acknowledgement()
|
||||
}
|
||||
|
||||
func dismissUpdateInstallation() {}
|
||||
func dismissUpdateInstallation() {
|
||||
hideProgress()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user