mirror of
https://github.com/nicoverbruggen/NVAppUpdater.git
synced 2025-08-07 17:50:07 +02:00
Add UpdateCheck and some supporting code
This commit is contained in:
@ -9,21 +9,55 @@ import Cocoa
|
||||
class Alert {
|
||||
public static var appName: String = ""
|
||||
|
||||
public static func show(description: String, shouldExit: Bool = true) async {
|
||||
await withUnsafeContinuation { continuation in
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "\(Alert.appName) could not be updated."
|
||||
alert.informativeText = description
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.alertStyle = .critical
|
||||
alert.runModal()
|
||||
if shouldExit {
|
||||
exit(0)
|
||||
}
|
||||
continuation.resume()
|
||||
// MARK: - Specific Cases
|
||||
|
||||
@MainActor
|
||||
public static func upgradeFailure(description: String, shouldExit: Bool = true) async {
|
||||
await confirm(
|
||||
title: "\(Alert.appName) could not be updated.",
|
||||
description: description,
|
||||
alertStyle: .critical,
|
||||
callback: {
|
||||
exit(0)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
|
||||
@MainActor
|
||||
public static func confirm(
|
||||
title: String,
|
||||
description: String,
|
||||
alertStyle: NSAlert.Style = .informational,
|
||||
callback: (() -> Void)? = nil
|
||||
) async {
|
||||
let alert = await NSAlert()
|
||||
alert.messageText = title
|
||||
alert.informativeText = description
|
||||
await alert.addButton(withTitle: "OK")
|
||||
alert.alertStyle = alertStyle
|
||||
await alert.runModal()
|
||||
if callback != nil {
|
||||
callback!()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public static func choose(
|
||||
title: String,
|
||||
description: String,
|
||||
options: [String],
|
||||
cancel: Bool = false
|
||||
) async -> NSApplication.ModalResponse {
|
||||
let alert = await NSAlert()
|
||||
alert.messageText = title
|
||||
alert.informativeText = description
|
||||
for option in options {
|
||||
await alert.addButton(withTitle: option)
|
||||
}
|
||||
alert.alertStyle = .informational
|
||||
return await alert.runModal()
|
||||
}
|
||||
}
|
||||
|
||||
|
120
Sources/AppUpdater/Support/AppVersion.swift
Normal file
120
Sources/AppUpdater/Support/AppVersion.swift
Normal file
@ -0,0 +1,120 @@
|
||||
//
|
||||
// Created by Nico Verbruggen on 26/05/2024.
|
||||
// Copyright © 2024 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AppVersion: Comparable {
|
||||
var version: String
|
||||
var build: Int?
|
||||
var suffix: String?
|
||||
|
||||
init(version: String, build: String?, suffix: String? = nil) {
|
||||
self.version = version
|
||||
self.build = build == nil ? nil : Int(build!)
|
||||
self.suffix = suffix
|
||||
}
|
||||
|
||||
public static func from(_ string: String) -> AppVersion? {
|
||||
do {
|
||||
let regex = try NSRegularExpression(
|
||||
pattern: #"(?<version>(\d+)[.](\d+)([.](\d+))?)(-(?<suffix>[a-z]+)){0,1}((,|_)(?<build>\d+)){0,1}"#,
|
||||
options: []
|
||||
)
|
||||
|
||||
let match = regex.matches(
|
||||
in: string,
|
||||
options: [],
|
||||
range: NSRange(location: 0, length: string.count)
|
||||
).first
|
||||
|
||||
guard let match = match else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var version: String = ""
|
||||
var build: String?
|
||||
var suffix: String?
|
||||
|
||||
if let versionRange = Range(match.range(withName: "version"), in: string) {
|
||||
version = String(string[versionRange])
|
||||
}
|
||||
|
||||
if let buildRange = Range(match.range(withName: "build"), in: string) {
|
||||
build = String(string[buildRange])
|
||||
}
|
||||
|
||||
if let suffixRange = Range(match.range(withName: "suffix"), in: string) {
|
||||
suffix = String(string[suffixRange])
|
||||
}
|
||||
|
||||
return AppVersion(
|
||||
version: version,
|
||||
build: build,
|
||||
suffix: suffix
|
||||
)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public static func fromCurrentVersion() -> AppVersion {
|
||||
return AppVersion.from("\(Executable.shortVersion)_\(Executable.bundleVersion)")!
|
||||
}
|
||||
|
||||
public var tagged: String {
|
||||
if version.suffix(2) == ".0" && version.count > 3 {
|
||||
return String(version.dropLast(2))
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
public var computerReadable: String {
|
||||
return "\(version)_\(build ?? 0)"
|
||||
}
|
||||
|
||||
public var humanReadable: String {
|
||||
return "\(version) (\(build ?? 0))"
|
||||
}
|
||||
|
||||
// MARK: - Comparable Protocol
|
||||
|
||||
static func < (lhs: AppVersion, rhs: AppVersion) -> Bool {
|
||||
if lhs.version < rhs.version {
|
||||
return true
|
||||
}
|
||||
|
||||
return lhs.build ?? 0 < rhs.build ?? 0
|
||||
}
|
||||
|
||||
static func == (lhs: AppVersion, rhs: AppVersion) -> Bool {
|
||||
lhs.version == rhs.version
|
||||
&& lhs.build == rhs.build
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
static func ==(lhs: String, rhs: String) -> Bool {
|
||||
return lhs.compare(rhs, options: .numeric) == .orderedSame
|
||||
}
|
||||
|
||||
static func <(lhs: String, rhs: String) -> Bool {
|
||||
return lhs.compare(rhs, options: .numeric) == .orderedAscending
|
||||
}
|
||||
|
||||
static func <=(lhs: String, rhs: String) -> Bool {
|
||||
return lhs.compare(rhs, options: .numeric) == .orderedAscending || lhs.compare(rhs, options: .numeric) == .orderedSame
|
||||
}
|
||||
|
||||
static func >(lhs: String, rhs: String) -> Bool {
|
||||
return lhs.compare(rhs, options: .numeric) == .orderedDescending
|
||||
}
|
||||
|
||||
static func >=(lhs: String, rhs: String) -> Bool {
|
||||
return lhs.compare(rhs, options: .numeric) == .orderedDescending || lhs.compare(rhs, options: .numeric) == .orderedSame
|
||||
}
|
||||
|
||||
}
|
70
Sources/AppUpdater/Support/CaskFile.swift
Normal file
70
Sources/AppUpdater/Support/CaskFile.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Created by Nico Verbruggen on 30/05/2024.
|
||||
// Copyright © 2024 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CaskFile {
|
||||
var properties: [String: String]
|
||||
|
||||
var name: String { return self.properties["name"]! }
|
||||
var url: String { return self.properties["url"]! }
|
||||
var sha256: String { return self.properties["sha256"]! }
|
||||
var version: String { return self.properties["version"]! }
|
||||
|
||||
static func from(url: URL) -> CaskFile? {
|
||||
var string: String?
|
||||
|
||||
if url.scheme == "file" {
|
||||
string = try? String(contentsOf: url)
|
||||
} else {
|
||||
string = system("curl -s --max-time 10 '\(url.absoluteString)'")
|
||||
}
|
||||
|
||||
guard let string else {
|
||||
Log.text("The content of the URL for the CaskFile could not be retrieved")
|
||||
return nil
|
||||
}
|
||||
|
||||
let lines = string.split(separator: "\n")
|
||||
.map { line in
|
||||
return line.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
.filter { $0 != "" }
|
||||
|
||||
if lines.count < 4 {
|
||||
Log.text("The CaskFile is <4 lines long, which is too short")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !lines.first!.starts(with: "cask") || !lines.last!.starts(with: "end") {
|
||||
Log.text("The CaskFile does not start with 'cask' or does not end with 'end'")
|
||||
return nil
|
||||
}
|
||||
|
||||
var props: [String: String] = [:]
|
||||
|
||||
let regex = try! NSRegularExpression(pattern: "(\\w+)\\s+'([^']+)'")
|
||||
|
||||
for line in lines {
|
||||
if let match = regex.firstMatch(
|
||||
in: String(line),
|
||||
range: NSRange(location: 0, length: line.utf16.count)
|
||||
) {
|
||||
let keyRange = match.range(at: 1)
|
||||
let valueRange = match.range(at: 2)
|
||||
let key = (line as NSString).substring(with: keyRange)
|
||||
let value = (line as NSString).substring(with: valueRange)
|
||||
props[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
for required in ["version", "sha256", "url", "name"] where !props.keys.contains(required) {
|
||||
Log.text("Property '\(required)' expected on CaskFile, assuming CaskFile is invalid")
|
||||
return nil
|
||||
}
|
||||
|
||||
return CaskFile(properties: props)
|
||||
}
|
||||
}
|
16
Sources/AppUpdater/Support/Log.swift
Normal file
16
Sources/AppUpdater/Support/Log.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Nico Verbruggen on 30/05/2024.
|
||||
// Copyright © 2024 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Log {
|
||||
public static func text(_ text: String) {
|
||||
self.handler(text)
|
||||
}
|
||||
|
||||
public static var handler: (_ text: String) -> Void = { text in
|
||||
print(text)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user