Make AccountManager init itself, and use AccountManager.shared in AppDelegate.

This commit is contained in:
Brent Simmons 2024-07-07 16:23:47 -07:00
parent aa807249b9
commit c784569040
4 changed files with 68 additions and 81 deletions

View File

@ -101,21 +101,16 @@ import Sparkle
#endif
private var themeImportPath: String?
private let accountManager: AccountManager
override init() {
NSWindow.allowsAutomaticWindowTabbing = false
self.accountManager = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
AccountManager.shared = self.accountManager
super.init()
#if !MAC_APP_STORE
let crashReporterConfig = PLCrashReporterConfig.defaultConfiguration()
crashReporter = PLCrashReporter(configuration: crashReporterConfig)
crashReporter.enable()
self.crashReporter = PLCrashReporter(configuration: crashReporterConfig)
self.crashReporter.enable()
#endif
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
@ -172,9 +167,9 @@ import Sparkle
FaviconGenerator.faviconTemplateImage = AppAssets.faviconTemplateImage
let localAccount = accountManager.defaultAccount
let localAccount = AccountManager.shared.defaultAccount
if isFirstRun && !accountManager.anyAccountHasAtLeastOneFeed() {
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
// Import feeds. Either old NNW 3 feeds or the default feeds.
if !NNW3ImportController.importSubscriptionsIfFileExists(account: localAccount) {
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
@ -197,7 +192,7 @@ import Sparkle
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
Task {
self.unreadCount = self.accountManager.unreadCount
self.unreadCount = AccountManager.shared.unreadCount
}
if InspectorWindowController.shouldOpenAtStartup {
@ -290,7 +285,7 @@ import Sparkle
ArticleStringFormatter.emptyCaches()
MultilineTextFieldSizer.emptyCache()
IconImageCache.shared.emptyCache()
accountManager.emptyCaches()
AccountManager.shared.emptyCaches()
saveState()
@ -302,7 +297,7 @@ import Sparkle
func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
Task {
await self.accountManager.receiveRemoteNotification(userInfo: userInfo)
await AccountManager.shared.receiveRemoteNotification(userInfo: userInfo)
}
}
@ -319,7 +314,7 @@ import Sparkle
ArticleThemeDownloader.cleanUp()
Task {
await accountManager.sendArticleStatusAll()
await AccountManager.shared.sendArticleStatusAll()
self.isShutDownSyncDone = true
}
@ -330,7 +325,7 @@ import Sparkle
// MARK: Notifications
@objc func unreadCountDidChange(_ note: Notification) {
if note.object is AccountManager {
unreadCount = accountManager.unreadCount
unreadCount = AccountManager.shared.unreadCount
}
}
@ -430,15 +425,15 @@ import Sparkle
let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
if item.action == #selector(refreshAll(_:)) {
return !accountManager.refreshInProgress && !accountManager.activeAccounts.isEmpty
return !AccountManager.shared.refreshInProgress && !AccountManager.shared.activeAccounts.isEmpty
}
if item.action == #selector(importOPMLFromFile(_:)) {
return accountManager.activeAccounts.contains(where: { !$0.behaviors.contains(where: { $0 == .disallowOPMLImports }) })
return AccountManager.shared.activeAccounts.contains(where: { !$0.behaviors.contains(where: { $0 == .disallowOPMLImports }) })
}
if item.action == #selector(addAppNews(_:)) {
return !isDisplayingSheet && !accountManager.anyAccountHasNetNewsWireNewsSubscription() && !accountManager.activeAccounts.isEmpty
return !isDisplayingSheet && !AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() && !AccountManager.shared.activeAccounts.isEmpty
}
if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) {
@ -446,7 +441,7 @@ import Sparkle
}
if item.action == #selector(showAddFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
return !isDisplayingSheet && !accountManager.activeAccounts.isEmpty
return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty
}
#if !MAC_APP_STORE
@ -526,7 +521,7 @@ import Sparkle
@IBAction func refreshAll(_ sender: Any?) {
Task {
await accountManager.refreshAll(errorHandler: ErrorHandler.present)
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
}
}
@ -601,7 +596,7 @@ import Sparkle
}
@IBAction func addAppNews(_ sender: Any?) {
if accountManager.anyAccountHasNetNewsWireNewsSubscription() {
if AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() {
return
}
addFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
@ -698,12 +693,12 @@ import Sparkle
extension AppDelegate {
@IBAction func debugSearch(_ sender: Any?) {
accountManager.defaultAccount.debugRunSearch()
AccountManager.shared.defaultAccount.debugRunSearch()
}
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
#if DEBUG
for account in accountManager.activeAccounts {
for account in AccountManager.shared.activeAccounts {
account.debugDropConditionalGetInfo()
}
#endif
@ -969,7 +964,7 @@ private extension AppDelegate {
func handleMarkAsRead(articlePathInfo: ArticlePathInfo) {
guard let accountID = articlePathInfo.accountID, let account = accountManager.existingAccount(with: accountID) else {
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
os_log(.debug, "No account found from notification.")
return
}
@ -989,7 +984,7 @@ private extension AppDelegate {
func handleMarkAsStarred(articlePathInfo: ArticlePathInfo) {
guard let accountID = articlePathInfo.accountID, let account = accountManager.existingAccount(with: accountID) else {
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
os_log(.debug, "No account found from notification.")
return
}

View File

@ -11,6 +11,7 @@ import Web
import Articles
import ArticlesDatabase
import Database
import Core
@MainActor public final class AccountManager: UnreadCountProvider {
@ -20,7 +21,11 @@ import Database
public let defaultAccount: Account
private let accountsFolder: String
private let accountsFolderURL: URL
private var accountsFolder: String {
accountsFolderURL.path
}
private var accountsDictionary = [String: Account]()
private let defaultAccountFolderName = "OnMyMac"
@ -91,19 +96,12 @@ import Database
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
}
public init(accountsFolder: String) {
public init() {
self.accountsFolder = accountsFolder
self.accountsFolderURL = AppConfig.dataSubfolder(named: "Accounts")
// The local "On My Mac" account must always exist, even if it's empty.
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
do {
try FileManager.default.createDirectory(atPath: localAccountFolder, withIntermediateDirectories: true, attributes: nil)
}
catch {
assertionFailure("Could not create folder for OnMyMac account.")
abort()
}
let localAccountFolder = AppConfig.ensureSubfolder(named: "OnMyMac", folderURL: self.accountsFolderURL).path
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier)
accountsDictionary[defaultAccount.accountID] = defaultAccount

View File

@ -22,7 +22,7 @@ public final class AppConfig {
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
let tempFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier)
folderURL = URL(fileURLWithPath: tempFolder, isDirectory: true)
try! FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
createFolderIfNecessary(folderURL)
}
return folderURL
@ -30,36 +30,38 @@ public final class AppConfig {
/// Returns URL to subfolder in cache folder (creating the folder if it doesnt exist)
public static func cacheSubfolder(named name: String) -> URL {
subfolder(name, in: cacheFolder)
ensureSubfolder(named: name, folderURL: cacheFolder)
}
public static let dataFolder: URL = {
#if os(macOS)
var dataFolder = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
var dataFolder = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
dataFolder = dataFolder.appendingPathComponent(appName)
try! FileManager.default.createDirectory(at: dataFolder, withIntermediateDirectories: true, attributes: nil)
return dataFolder
#elseif os(iOS)
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
var dataFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
#endif
createFolderIfNecessary(dataFolder)
return dataFolder
}()
/// Returns URL to subfolder in data folder (creating the folder if it doesnt exist)
public static func dataSubfolder(named name: String) -> URL {
subfolder(name, in: dataFolder)
ensureSubfolder(named: name, folderURL: dataFolder)
}
public static func ensureSubfolder(named name: String, folderURL: URL) -> URL {
let folder = folderURL.appendingPathComponent(name, isDirectory: true)
createFolderIfNecessary(folder)
return folder
}
}
private extension AppConfig {
static func subfolder(_ name: String, in folderURL: URL) -> URL {
let folder = folderURL.appendingPathComponent(name, isDirectory: true)
try! FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
return folder
static func createFolderIfNecessary(_ folderURL: URL) {
try! FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
}
}

View File

@ -52,17 +52,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var isSyncArticleStatusRunning = false
var isWaitingForSyncTasks = false
let accountManager: AccountManager
override init() {
let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString
let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7)))
self.accountManager = AccountManager(accountsFolder: documentAccountsFolderPath)
AccountManager.shared = accountManager
super.init()
appDelegate = self
@ -79,8 +71,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
os_log("Is first run.", log: log, type: .info)
}
if isFirstRun && !accountManager.anyAccountHasAtLeastOneFeed() {
let localAccount = accountManager.defaultAccount
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
let localAccount = AccountManager.shared.defaultAccount
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
}
@ -92,7 +84,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
initializeHomeScreenQuickActions()
Task { @MainActor in
self.unreadCount = accountManager.unreadCount
self.unreadCount = AccountManager.shared.unreadCount
}
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in
@ -121,7 +113,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
resumeDatabaseProcessingIfNecessary()
await accountManager.receiveRemoteNotification(userInfo: userInfo)
await AccountManager.shared.receiveRemoteNotification(userInfo: userInfo)
suspendApplication()
return .newData
}
@ -136,7 +128,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
MultilineUILabelSizer.emptyCache()
SingleLineUILabelSizer.emptyCache()
IconImageCache.shared.emptyCache()
accountManager.emptyCaches()
AccountManager.shared.emptyCaches()
Task.detached {
await DownloadWithCacheManager.shared.cleanupCache()
@ -147,7 +139,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
@objc func unreadCountDidChange(_ note: Notification) {
if note.object is AccountManager {
unreadCount = accountManager.unreadCount
unreadCount = AccountManager.shared.unreadCount
}
}
@ -165,13 +157,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
Task { @MainActor in
await self.accountManager.refreshAll(errorHandler: errorHandler)
await AccountManager.shared.refreshAll(errorHandler: errorHandler)
}
}
func resumeDatabaseProcessingIfNecessary() {
if accountManager.isSuspended {
accountManager.resumeAll()
if AccountManager.shared.isSuspended {
AccountManager.shared.resumeAll()
os_log("Application processing resumed.", log: self.log, type: .info)
}
}
@ -191,12 +183,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
Task { @MainActor in
if let lastRefresh = AppDefaults.shared.lastRefresh {
if Date() > lastRefresh.addingTimeInterval(15 * 60) {
await accountManager.refreshAll(errorHandler: ErrorHandler.log)
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
} else {
await accountManager.syncArticleStatusAll()
await AccountManager.shared.syncArticleStatusAll()
}
} else {
await accountManager.refreshAll(errorHandler: ErrorHandler.log)
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
}
}
}
@ -281,7 +273,7 @@ private extension AppDelegate {
return
}
if accountManager.refreshInProgress || isSyncArticleStatusRunning {
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning {
os_log("Waiting for sync to finish...", log: self.log, type: .info)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.waitToComplete(completion: completion)
@ -318,7 +310,7 @@ private extension AppDelegate {
}
Task { @MainActor in
await self.accountManager.syncArticleStatusAll()
await AccountManager.shared.syncArticleStatusAll()
completeProcessing()
}
}
@ -326,8 +318,8 @@ private extension AppDelegate {
func suspendApplication() {
guard UIApplication.shared.applicationState == .background else { return }
accountManager.suspendNetworkAll()
accountManager.suspendDatabaseAll()
AccountManager.shared.suspendNetworkAll()
AccountManager.shared.suspendDatabaseAll()
ArticleThemeDownloader.cleanUp()
CoalescingQueue.standard.performCallsImmediately()
@ -382,11 +374,11 @@ private extension AppDelegate {
os_log("Woken to perform account refresh.", log: self.log, type: .info)
Task { @MainActor in
if self.accountManager.isSuspended {
self.accountManager.resumeAll()
if AccountManager.shared.isSuspended {
AccountManager.shared.resumeAll()
}
await self.accountManager.refreshAll(errorHandler: ErrorHandler.log)
if !self.accountManager.isSuspended {
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self.suspendApplication()
os_log("Account refresh operation completed.", log: self.log, type: .info)
@ -418,7 +410,7 @@ private extension AppDelegate {
resumeDatabaseProcessingIfNecessary()
guard let accountID = articlePathInfo.accountID, let account = accountManager.existingAccount(with: accountID) else {
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
os_log(.debug, "No account found from notification.")
return
}
@ -438,7 +430,7 @@ private extension AppDelegate {
self.prepareAccountsForBackground()
try? await account.syncArticleStatus()
if !self.accountManager.isSuspended {
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self.prepareAccountsForBackground()
self.suspendApplication()
@ -454,7 +446,7 @@ private extension AppDelegate {
resumeDatabaseProcessingIfNecessary()
guard let accountID = articlePathInfo.accountID, let account = accountManager.existingAccount(with: accountID) else {
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
os_log(.debug, "No account found from notification.")
return
}
@ -473,7 +465,7 @@ private extension AppDelegate {
try? await account.markArticles(articles, statusKey: .starred, flag: true)
try? await account.syncArticleStatus()
if !self.accountManager.isSuspended {
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self.prepareAccountsForBackground()
self.suspendApplication()