secretive/Sources/Packages/Sources/Brief/Updater.swift

98 lines
3.9 KiB
Swift
Raw Normal View History

2020-03-15 08:01:40 +00:00
import Foundation
import Combine
2022-01-02 05:46:01 +00:00
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
2020-03-22 01:43:26 +00:00
public class Updater: ObservableObject, UpdaterProtocol {
2020-03-15 08:01:40 +00:00
2020-03-22 01:43:26 +00:00
@Published public var update: Release?
public let testBuild: Bool
2020-03-15 08:01:40 +00:00
2022-01-02 05:46:01 +00:00
/// The current OS version.
2021-01-18 08:49:26 +00:00
private let osVersion: SemVer
2022-01-02 05:46:01 +00:00
/// The current version of the app that is running.
2021-09-23 04:10:04 +00:00
private let currentVersion: SemVer
2021-01-18 08:49:26 +00:00
2022-01-02 05:46:01 +00:00
/// Initializes an Updater.
/// - Parameters:
/// - checkOnLaunch: A boolean describing whether the Updater should check for available updates on launch.
/// - checkFrequency: The interval at which the Updater should check for updates. Subject to a tolerance of 1 hour.
/// - osVersion: The current OS version.
/// - currentVersion: The current version of the app that is running.
public init(checkOnLaunch: Bool, checkFrequency: TimeInterval = Measurement(value: 24, unit: UnitDuration.hours).converted(to: .seconds).value, osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion), currentVersion: SemVer = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")) {
2021-01-18 08:49:26 +00:00
self.osVersion = osVersion
2021-09-23 04:10:04 +00:00
self.currentVersion = currentVersion
testBuild = currentVersion == SemVer("0.0.0")
2020-09-22 06:12:50 +00:00
if checkOnLaunch {
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
checkForUpdates()
}
2022-01-02 05:46:01 +00:00
let timer = Timer.scheduledTimer(withTimeInterval: checkFrequency, repeats: true) { _ in
2020-03-15 08:01:40 +00:00
self.checkForUpdates()
}
timer.tolerance = 60*60
}
2022-01-02 05:46:01 +00:00
/// Manually trigger an update check.
2020-03-22 01:43:26 +00:00
public func checkForUpdates() {
2020-03-15 08:01:40 +00:00
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
guard let data = data else { return }
2021-01-18 08:49:26 +00:00
guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
self.evaluate(releases: releases)
2020-03-15 08:01:40 +00:00
}.resume()
}
2022-01-02 05:46:01 +00:00
/// Ignores a specified release. `update` will be nil if the user has ignored the latest available release.
/// - Parameter release: The release to ignore.
public func ignore(release: Release) {
guard !release.critical else { return }
defaults.set(true, forKey: release.name)
DispatchQueue.main.async {
self.update = nil
}
}
}
extension Updater {
2022-01-02 05:46:01 +00:00
/// Evaluates the available downloadable releases, and selects the newest non-prerelease release that the user is able to run.
/// - Parameter releases: An array of ``Release`` objects.
2021-01-18 08:49:26 +00:00
func evaluate(releases: [Release]) {
guard let release = releases
.sorted()
.reversed()
.filter({ !$0.prerelease })
.first(where: { $0.minimumOSVersion <= osVersion }) else { return }
guard !userIgnored(release: release) else { return }
2020-09-22 06:12:50 +00:00
guard !release.prerelease else { return }
2020-09-22 07:17:22 +00:00
let latestVersion = SemVer(release.name)
if latestVersion > currentVersion {
DispatchQueue.main.async {
self.update = release
2020-03-15 08:01:40 +00:00
}
}
}
2022-01-02 05:46:01 +00:00
/// Checks whether the user has ignored a release.
/// - Parameter release: The release to check.
/// - Returns: A boolean describing whether the user has ignored the release. Will always be false if the release is critical.
func userIgnored(release: Release) -> Bool {
guard !release.critical else { return false }
return defaults.bool(forKey: release.name)
}
2022-01-02 05:46:01 +00:00
/// The user defaults used to store user ignore state.
var defaults: UserDefaults {
UserDefaults(suiteName: "com.maxgoedjen.Secretive.updater.ignorelist")!
}
2020-09-22 07:17:22 +00:00
}
2020-03-15 08:01:40 +00:00
extension Updater {
enum Constants {
2021-01-18 08:49:26 +00:00
static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")!
2020-03-15 08:01:40 +00:00
}
}