// // AccountManager.swift // Evergreen // // Created by Brent Simmons on 7/18/15. // Copyright © 2015 Ranchero Software, LLC. All rights reserved. // import Foundation import RSCore import DataModel import LocalAccount let AccountsDidChangeNotification = "AccountsDidChangeNotification" private let localAccountFolderName = "OnMyMac" private let localAccountIdentifier = "OnMyMac" final class AccountManager: UnreadCountProvider { static let sharedInstance = AccountManager() private let accountsFolder = RSDataSubfolder(nil, "Accounts")! private var accountsDictionary = [String: Account]() let localAccount: Account var unreadCount = 0 { didSet { postUnreadCountDidChangeNotification() } } var accounts: [Account] { get { return Array(accountsDictionary.values) } } var sortedAccounts: [Account] { get { return accountsSortedByName() } } var refreshInProgress: Bool { get { for oneAccount in accountsDictionary.values { if oneAccount.refreshInProgress { return true } } return false } } init() { // 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 localAccountSettingsFile = accountFilePathWithFolder(localAccountFolder) localAccount = LocalAccount(settingsFile: localAccountSettingsFile, dataFolder: localAccountFolder, identifier: localAccountIdentifier) accountsDictionary[localAccount.identifier] = localAccount readNonLocalAccountsFromDisk() NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) } // MARK: API func existingAccountWithIdentifier(_ identifier: String) -> Account? { return accountsDictionary[identifier] } func refreshAll() { accounts.forEach { (oneAccount) in oneAccount.refreshAll() } } func anyAccountHasAtLeastOneFeed() -> Bool { for oneAccount in accounts { if oneAccount.hasAtLeastOneFeed { return true } } return false } func anyAccountHasFeedWithURL(_ urlString: String) -> Bool { for oneAccount in accounts { if let _ = oneAccount.existingFeedWithURL(urlString) { return true } } return false } // MARK: UnreadCountProvider func updateUnreadCount() { let updatedUnreadCount = calculateUnreadCount(accounts) if updatedUnreadCount != unreadCount { unreadCount = updatedUnreadCount } } // MARK: Notifications dynamic func unreadCountDidChange(_ notification: Notification) { guard let _ = notification.object as? Account else { return } updateUnreadCount() } // MARK: Private private func createAccount(_ accountSpecifier: AccountSpecifier) -> Account? { return nil } private func createAccount(_ filename: String) -> Account? { let folderPath = (accountsFolder as NSString).appendingPathComponent(filename) if let accountSpecifier = AccountSpecifier(folderPath: folderPath) { return createAccount(accountSpecifier) } return nil } private func readNonLocalAccountsFromDisk() { var filenames: [String]? do { filenames = try FileManager.default.contentsOfDirectory(atPath: accountsFolder) } catch { print("Error reading Accounts folder: \(error)") return } filenames?.forEach { (oneFilename) in guard oneFilename != localAccountFolderName else { return } if let oneAccount = createAccount(oneFilename) { accountsDictionary[oneAccount.identifier] = oneAccount } } } private func accountsSortedByName() -> [Account] { // LocalAccount is first. return accounts.sorted { (account1, account2) -> Bool in if account1 === localAccount { return true } if account2 === localAccount { return false } //TODO: Use localizedCaseInsensitiveCompare: return account1.nameForDisplay < account2.nameForDisplay } } } private let accountDataFileName = "AccountData.plist" private func accountFilePathWithFolder(_ folderPath: String) -> String { return NSString(string: folderPath).appendingPathComponent(accountDataFileName) } private struct AccountSpecifier { let type: String let identifier: String let folderPath: String let folderName: String let dataFilePath: String init?(folderPath: String) { self.folderPath = folderPath self.folderName = NSString(string: folderPath).lastPathComponent let nameComponents = self.folderName.components(separatedBy: "-") let satisfyCompilerFolderName = self.folderName assert(nameComponents.count == 2, "Can’t determine account info from \(satisfyCompilerFolderName)") if nameComponents.count != 2 { return nil } self.type = nameComponents[0] self.identifier = nameComponents[1] self.dataFilePath = accountFilePathWithFolder(self.folderPath) } }