diff --git a/NetNewsWire/AppDefaults.swift b/NetNewsWire/AppDefaults.swift index 387269f24..44a19f7a5 100644 --- a/NetNewsWire/AppDefaults.swift +++ b/NetNewsWire/AppDefaults.swift @@ -23,6 +23,25 @@ enum RefreshInterval: Int { case every2Hours = 5 case every4Hours = 6 case every8Hours = 7 + + func inSeconds() -> TimeInterval { + switch self { + case .manually: + return 0 + case .every10Minutes: + return 10 * 60 + case .every30Minutes: + return 30 * 60 + case .everyHour: + return 60 * 60 + case .every2Hours: + return 2 * 60 * 60 + case .every4Hours: + return 4 * 60 * 60 + case .every8Hours: + return 8 * 60 * 60 + } + } } struct AppDefaults { @@ -118,7 +137,6 @@ struct AppDefaults { set { UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) } - } static func registerDefaults() { diff --git a/NetNewsWire/AppDelegate.swift b/NetNewsWire/AppDelegate.swift index 8a114e7c9..a4425b9ce 100644 --- a/NetNewsWire/AppDelegate.swift +++ b/NetNewsWire/AppDelegate.swift @@ -25,7 +25,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, var authorAvatarDownloader: AuthorAvatarDownloader! var feedIconDownloader: FeedIconDownloader! var appName: String! - + var refreshTimer: Timer? + var lastTimedRefresh: Date? + let launchTime = Date() + var shuttingDown = false { + didSet { + if shuttingDown { + invalidateRefreshTimer() + } + } + } + @IBOutlet var debugMenuItem: NSMenuItem! @IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem! @IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem! @@ -169,10 +179,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, #if RELEASE debugMenuItem.menu?.removeItem(debugMenuItem) DispatchQueue.main.async { - self.refreshAll(self) + self.timedRefresh(nil) } #endif + #if DEBUG + updateRefreshTimer() + #endif + #if !MAC_APP_STORE DispatchQueue.main.async { CrashReporter.check(appName: "NetNewsWire") @@ -185,6 +199,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return false } + func applicationDidBecomeActive(_ notification: Notification) { + // It’s possible there’s a refresh timer set to go off in the past. + // In that case, refresh now and update the timer. + if let timer = refreshTimer { + if timer.fireDate < Date() { + if AppDefaults.refreshInterval != .manually { + timedRefresh(nil) + } + } + } + } + func applicationDidResignActive(_ notification: Notification) { TimelineStringFormatter.emptyCaches() @@ -193,7 +219,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } func applicationWillTerminate(_ notification: Notification) { - + shuttingDown = true saveState() } @@ -223,8 +249,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } @objc func userDefaultsDidChange(_ note: Notification) { - updateSortMenuItems() + updateRefreshTimer() } // MARK: Main Window @@ -247,6 +273,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, // MARK: NSUserInterfaceValidations func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { + if shuttingDown { + return false + } let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false @@ -265,6 +294,54 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return true } + // MARK: Timed Refresh + + @objc func timedRefresh(_ sender: Timer?) { + guard !shuttingDown else { + return + } + lastTimedRefresh = Date() + updateRefreshTimer() + refreshAll(self) + } + + private func invalidateRefreshTimer() { + guard let timer = refreshTimer else { + return + } + if timer.isValid { + timer.invalidate() + } + refreshTimer = nil + } + + private func updateRefreshTimer() { + guard !shuttingDown else { + return + } + + let refreshInterval = AppDefaults.refreshInterval + if refreshInterval == .manually { + invalidateRefreshTimer() + return + } + let lastRefreshDate = lastTimedRefresh ?? launchTime + let secondsToAdd = refreshInterval.inSeconds() + var nextRefreshTime = lastRefreshDate.addingTimeInterval(secondsToAdd) + if nextRefreshTime < Date() { + nextRefreshTime = Date().addingTimeInterval(secondsToAdd) + } + if let currentNextFireDate = refreshTimer?.fireDate, currentNextFireDate == nextRefreshTime { + return + } + + invalidateRefreshTimer() + let timer = Timer(fireAt: nextRefreshTime, interval: 0, target: self, selector: #selector(timedRefresh(_:)), userInfo: nil, repeats: false) + RunLoop.main.add(timer, forMode: .common) + refreshTimer = timer + print("Next refresh date: \(nextRefreshTime)") + } + // MARK: Add Feed func addFeed(_ urlString: String?, name: String? = nil, folder: Folder? = nil) { diff --git a/NetNewsWire/MainWindow/MainWindowController.swift b/NetNewsWire/MainWindow/MainWindowController.swift index d4de9f237..7323d10a5 100644 --- a/NetNewsWire/MainWindow/MainWindowController.swift +++ b/NetNewsWire/MainWindow/MainWindowController.swift @@ -592,7 +592,9 @@ private extension MainWindowController { } let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) } - AppDefaults.mainWindowWidths = widths + if AppDefaults.mainWindowWidths != widths { + AppDefaults.mainWindowWidths = widths + } } func restoreSplitViewState() {