mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 11:10:08 +01:00
✨ Improved update check
This commit is contained in:
@@ -21,13 +21,25 @@ struct Constants {
|
||||
/**
|
||||
The amount of seconds that is considered the threshold for
|
||||
PHP Monitor to mark any given launch as a "slow" launch.
|
||||
|
||||
|
||||
If the startup procedure was slow (or hangs), this message should
|
||||
be displayed. This is based on an appropriate launch time on a
|
||||
basic M1 Apple chip, with some margin for slower Intel chips.
|
||||
*/
|
||||
static let SlowBootThresholdInterval: TimeInterval = 30.0
|
||||
|
||||
/**
|
||||
The interval between automatic background update checks.
|
||||
*/
|
||||
static let AutomaticUpdateCheckInterval: TimeInterval = 60 // 60.0 * 60 * 24 // 24 hours
|
||||
|
||||
/**
|
||||
The minimum interval that must pass before allowing another
|
||||
automatic update check. This prevents excessive checking
|
||||
on frequent app restarts (due to crashes or bad config).
|
||||
*/
|
||||
static let MinimumUpdateCheckInterval: TimeInterval = 60 // 60.0 * 60 // 60 minutes
|
||||
|
||||
/**
|
||||
PHP Monitor supplies a hardcoded list of PHP packages in its own
|
||||
PHP Version Manager.
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
// TODO: Add anonymous analytics system
|
||||
// Batch events and dispatch them every hour.
|
||||
// Reset the counts when send successfully.
|
||||
// That's the plan. Currently not implemented!
|
||||
// Also, there should be an opt-out.
|
||||
|
||||
enum LoggableEvent: String {
|
||||
case menuOpened = "menu_opened"
|
||||
|
||||
@@ -17,9 +23,5 @@ enum LoggableEvent: String {
|
||||
|
||||
case openedSettings = "opened_settings"
|
||||
|
||||
// TODO: Add one for each feature and make sure each feature used actually increments a count somewhere
|
||||
// Ensure that the events are broadcast within 24 hrs since launch OR when the app quits
|
||||
// If the events are broadcast after 24 hrs of the app being running, reset analytics
|
||||
// Alternatively, batch events and dispatch them every hour (and keep track of what was sent)
|
||||
// I will think about this some more, these are just ideas for now
|
||||
// TODO: Add more tracked things.
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ extension MainMenu {
|
||||
}
|
||||
} else {
|
||||
// Check for updates
|
||||
await AppUpdater().checkForUpdates(userInitiated: false)
|
||||
await performAutomaticUpdateCheck()
|
||||
|
||||
// Check if the linked version has changed between launches of phpmon
|
||||
await PhpGuard().compareToLastGlobalVersion()
|
||||
@@ -205,4 +205,60 @@ extension MainMenu {
|
||||
|
||||
Log.info("Detected applications: \(appNames)")
|
||||
}
|
||||
|
||||
/**
|
||||
Perform an automatic update check and schedule the next one.
|
||||
*/
|
||||
private func performAutomaticUpdateCheck() async {
|
||||
guard Preferences.isEnabled(.automaticBackgroundUpdateCheck) else {
|
||||
// The user has chosen not to receive update notifications
|
||||
return
|
||||
}
|
||||
|
||||
guard automaticUpdateCheckIsNotThrottled() else {
|
||||
// If we are throttled, just schedule a regular check 24 hours from now
|
||||
scheduleUpdateCheckTimer()
|
||||
return
|
||||
}
|
||||
|
||||
await AppUpdater().checkForUpdates(userInitiated: false)
|
||||
|
||||
UserDefaults.standard.set(Date(), forKey: PersistentAppState.lastAutomaticUpdateCheck.rawValue)
|
||||
|
||||
scheduleUpdateCheckTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
Determine whether another automatic update check should occur based on the last check timestamp.
|
||||
Returns true if a check should happen, false otherwise.
|
||||
*/
|
||||
private func automaticUpdateCheckIsNotThrottled() -> Bool {
|
||||
guard Preferences.isEnabled(.automaticBackgroundUpdateCheck) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let minimumTimeAgo = Date().addingTimeInterval(-Constants.MinimumUpdateCheckInterval)
|
||||
let lastCheckTime = UserDefaults.standard.object(
|
||||
forKey: PersistentAppState.lastAutomaticUpdateCheck.rawValue
|
||||
) as? Date
|
||||
|
||||
// If no previous check or last check was > minimum time frame, should check now
|
||||
return lastCheckTime == nil || lastCheckTime! < minimumTimeAgo
|
||||
}
|
||||
|
||||
/**
|
||||
Schedule a timer to perform an update check after the specified interval.
|
||||
*/
|
||||
private func scheduleUpdateCheckTimer() {
|
||||
let interval = Constants.AutomaticUpdateCheckInterval
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { _ in
|
||||
Task {
|
||||
Log.info("Performing scheduled update check after \(interval) seconds.")
|
||||
await self.performAutomaticUpdateCheck()
|
||||
}
|
||||
}
|
||||
|
||||
Log.info("A new update check will occur in \(interval) seconds from now.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
These are the keys used for every preference in the app.
|
||||
*/
|
||||
enum PreferenceName: String, Codable {
|
||||
// FIRST-TIME LAUNCH
|
||||
case wasLaunchedBefore = "launched_before"
|
||||
|
||||
// GENERAL
|
||||
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
|
||||
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
|
||||
@@ -104,6 +101,17 @@ enum RetiredPreferenceName: String {
|
||||
case shouldDisplayPhpHintInIcon = "add_php_to_icon"
|
||||
}
|
||||
|
||||
/**
|
||||
Persistent internal application state keys for UserDefaults.
|
||||
These track internal app state and behavior that persists across launches,
|
||||
but are not user preferences or statistics.
|
||||
*/
|
||||
enum PersistentAppState: String {
|
||||
case wasLaunchedBefore = "launched_before"
|
||||
case lastAutomaticUpdateCheck = "last_automatic_update_check"
|
||||
case userFavorites = "user_favorites"
|
||||
}
|
||||
|
||||
/**
|
||||
These are internal stats. They NEVER get shared.
|
||||
*/
|
||||
|
||||
@@ -83,6 +83,9 @@ class Preferences {
|
||||
PreferenceName.displayPresets.rawValue: true,
|
||||
PreferenceName.displayMisc.rawValue: true,
|
||||
|
||||
/// Persistent App State
|
||||
PersistentAppState.lastAutomaticUpdateCheck.rawValue: 0,
|
||||
|
||||
/// Stats
|
||||
InternalStats.switchCount.rawValue: 0,
|
||||
InternalStats.launchCount.rawValue: 0,
|
||||
@@ -90,13 +93,13 @@ class Preferences {
|
||||
InternalStats.lastGlobalPhpVersion.rawValue: ""
|
||||
])
|
||||
|
||||
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
|
||||
if UserDefaults.standard.bool(forKey: PersistentAppState.wasLaunchedBefore.rawValue) {
|
||||
handleMigration()
|
||||
return
|
||||
}
|
||||
|
||||
Log.info("Saving first-time preferences!")
|
||||
UserDefaults.standard.setValue(true, forKey: PreferenceName.wasLaunchedBefore.rawValue)
|
||||
UserDefaults.standard.setValue(true, forKey: PersistentAppState.wasLaunchedBefore.rawValue)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class Favorites {
|
||||
var items: [String]
|
||||
|
||||
init() {
|
||||
if let items = UserDefaults.standard.array(forKey: "user_favorites") as? [String] {
|
||||
if let items = UserDefaults.standard.array(forKey: PersistentAppState.userFavorites.rawValue) as? [String] {
|
||||
self.items = items
|
||||
} else {
|
||||
self.items = []
|
||||
@@ -32,7 +32,7 @@ class Favorites {
|
||||
items.append(domain)
|
||||
}
|
||||
|
||||
UserDefaults.standard.setValue(items, forKey: "user_favorites")
|
||||
UserDefaults.standard.setValue(items, forKey: PersistentAppState.userFavorites.rawValue)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user