mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 12:08:27 +00:00
feat(updater): upgrade sparkle to 2.9.3 and enhance integration
- Update Sparkle dependency to 2.9.3 - Replace brittle HTML parsing with native NSAttributedString parsing - Fix dangling updater callbacks by handling view disappear events - Add 'Remind Me Later' button in update prompt - Add toggle for automatic background update checks
This commit is contained in:
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"originHash" : "048cca0a42e966dd91de6a4753f25d908574338fda8bf9b8bcae473cf159ebf4",
|
||||
"originHash" : "c1cb50a392a5949f6fb77fb4800ef2ea6c811268af19d41dd83b3be29f0321a8",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "sparkle",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "6276ba2b404829d139c45ff98427cf90e2efc59b",
|
||||
"version" : "2.9.2"
|
||||
"revision" : "d46d456107feacc80711b21847b82b07bd9fb46e",
|
||||
"version" : "2.9.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ let package = Package(
|
||||
.executable(name: "Firelink", targets: ["Firelink"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.6.4")
|
||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.3")
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
|
||||
@@ -14,7 +14,12 @@ final class SparkleUpdater: NSObject, ObservableObject, SPUUpdaterDelegate {
|
||||
|
||||
@Published var updateStatus: String?
|
||||
@Published var foundUpdateItem: SUAppcastItem?
|
||||
@Published var releaseNotes: String?
|
||||
@Published var releaseNotes: AttributedString?
|
||||
@Published var automaticallyChecksForUpdates: Bool = true {
|
||||
didSet {
|
||||
_updater?.automaticallyChecksForUpdates = automaticallyChecksForUpdates
|
||||
}
|
||||
}
|
||||
|
||||
var expectedContentLength: UInt64 = 0
|
||||
var receivedContentLength: UInt64 = 0
|
||||
@@ -28,6 +33,7 @@ final class SparkleUpdater: NSObject, ObservableObject, SPUUpdaterDelegate {
|
||||
self._updater = SPUUpdater(hostBundle: hostBundle, applicationBundle: hostBundle, userDriver: driver, delegate: self)
|
||||
do {
|
||||
try self._updater?.start()
|
||||
self.automaticallyChecksForUpdates = self._updater?.automaticallyChecksForUpdates ?? true
|
||||
} catch {
|
||||
print("Failed to start Sparkle updater: \(error)")
|
||||
}
|
||||
|
||||
@@ -85,7 +85,9 @@ struct AboutSettingsPane: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
sparkleUpdater.updateChoiceReply?(.install)
|
||||
let reply = sparkleUpdater.updateChoiceReply
|
||||
sparkleUpdater.updateChoiceReply = nil
|
||||
reply?(.install)
|
||||
} label: {
|
||||
Label("Install and Relaunch", systemImage: "sparkles")
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -108,7 +110,7 @@ struct AboutSettingsPane: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let notes = sparkleUpdater.releaseNotes, !notes.isEmpty {
|
||||
if let notes = sparkleUpdater.releaseNotes {
|
||||
DisclosureGroup("What's New") {
|
||||
ScrollView {
|
||||
Text(notes)
|
||||
@@ -125,14 +127,24 @@ struct AboutSettingsPane: View {
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
sparkleUpdater.updateChoiceReply?(.install)
|
||||
let reply = sparkleUpdater.updateChoiceReply
|
||||
sparkleUpdater.updateChoiceReply = nil
|
||||
reply?(.install)
|
||||
} label: {
|
||||
Text("Download & Install")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
Button("Remind Me Later") {
|
||||
let reply = sparkleUpdater.updateChoiceReply
|
||||
sparkleUpdater.updateChoiceReply = nil
|
||||
reply?(.dismiss)
|
||||
}
|
||||
|
||||
Button("Skip This Version") {
|
||||
sparkleUpdater.updateChoiceReply?(.skip)
|
||||
let reply = sparkleUpdater.updateChoiceReply
|
||||
sparkleUpdater.updateChoiceReply = nil
|
||||
reply?(.skip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +216,11 @@ struct AboutSettingsPane: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 4)
|
||||
|
||||
Toggle("Automatically check for updates", isOn: $sparkleUpdater.automaticallyChecksForUpdates)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.animation(.easeInOut, value: sparkleUpdater.isChecking)
|
||||
@@ -250,5 +267,11 @@ struct AboutSettingsPane: View {
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.onDisappear {
|
||||
if let reply = sparkleUpdater.updateChoiceReply {
|
||||
sparkleUpdater.updateChoiceReply = nil
|
||||
reply(.dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,55 +32,23 @@ class InlineUpdateUserDriver: NSObject, SPUUserDriver {
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
if let htmlString = String(data: downloadData.data, encoding: .utf8) {
|
||||
let parsedText = self.fastHTMLToMarkdown(htmlString)
|
||||
DispatchQueue.main.async {
|
||||
self.updater?.releaseNotes = parsedText
|
||||
DispatchQueue.main.async {
|
||||
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
|
||||
.documentType: NSAttributedString.DocumentType.html,
|
||||
.characterEncoding: String.Encoding.utf8.rawValue
|
||||
]
|
||||
if let nsAttrString = try? NSMutableAttributedString(data: downloadData.data, options: options, documentAttributes: nil) {
|
||||
let range = NSRange(location: 0, length: nsAttrString.length)
|
||||
nsAttrString.removeAttribute(.foregroundColor, range: range)
|
||||
nsAttrString.removeAttribute(.font, range: range)
|
||||
|
||||
if let attrString = try? AttributedString(nsAttrString, including: \.appKit) {
|
||||
self.updater?.releaseNotes = attrString
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated private func fastHTMLToMarkdown(_ html: String) -> String {
|
||||
var text = html
|
||||
text = text.replacingOccurrences(of: "<br>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br/>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br />", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</p>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<li>", with: "- ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</li>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h1>", with: "# ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h1>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h2>", with: "## ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h2>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h3>", with: "### ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h3>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<strong>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</strong>", with: "**", options: .caseInsensitive)
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "<[^>]+>", options: .caseInsensitive) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "")
|
||||
}
|
||||
|
||||
text = text.replacingOccurrences(of: " ", with: " ")
|
||||
text = text.replacingOccurrences(of: "&", with: "&")
|
||||
text = text.replacingOccurrences(of: "<", with: "<")
|
||||
text = text.replacingOccurrences(of: ">", with: ">")
|
||||
text = text.replacingOccurrences(of: """, with: "\"")
|
||||
text = text.replacingOccurrences(of: "'", with: "'")
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "\\n{3,}", options: []) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "\n\n")
|
||||
}
|
||||
|
||||
return text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotesFailedToDownloadWithError(_ error: Error) {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user