1
0
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:
2025-09-26 15:46:37 +02:00
parent 5b6a804667
commit 1a8fe7e7fc
6 changed files with 95 additions and 14 deletions

View File

@@ -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.

View File

@@ -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.
}

View File

@@ -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.")
}
}

View File

@@ -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.
*/

View File

@@ -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()
}

View File

@@ -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()
}
}