NetNewsWire/Frameworks/Account/AccountManager.swift

224 lines
5.1 KiB
Swift
Raw Normal View History

2017-05-27 19:43:27 +02:00
//
// AccountManager.swift
2018-08-29 07:18:24 +02:00
// NetNewsWire
2017-05-27 19:43:27 +02:00
//
// Created by Brent Simmons on 7/18/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
import Articles
2017-05-27 19:43:27 +02:00
let AccountsDidChangeNotification = "AccountsDidChangeNotification"
private let localAccountFolderName = "OnMyMac"
private let localAccountIdentifier = "OnMyMac"
2017-09-17 21:34:10 +02:00
public final class AccountManager: UnreadCountProvider {
2017-05-27 19:43:27 +02:00
2017-09-23 21:17:14 +02:00
public static let shared = AccountManager()
2017-09-17 21:34:10 +02:00
public let localAccount: Account
2017-05-27 19:43:27 +02:00
private let accountsFolder = RSDataSubfolder(nil, "Accounts")!
private var accountsDictionary = [String: Account]()
2017-09-17 21:34:10 +02:00
public var unreadCount = 0 {
2017-05-27 19:43:27 +02:00
didSet {
if unreadCount != oldValue {
postUnreadCountDidChangeNotification()
}
2017-05-27 19:43:27 +02:00
}
}
2017-09-24 21:24:44 +02:00
public var accounts: [Account] {
return Array(accountsDictionary.values)
2017-05-27 19:43:27 +02:00
}
2017-09-17 21:54:08 +02:00
public var sortedAccounts: [Account] {
return accountsSortedByName()
2017-05-27 19:43:27 +02:00
}
2017-09-17 21:34:10 +02:00
public var refreshInProgress: Bool {
for account in accounts {
if account.refreshInProgress {
return true
2017-05-27 19:43:27 +02:00
}
}
return false
2017-05-27 19:43:27 +02:00
}
public var combinedRefreshProgress: CombinedRefreshProgress {
let downloadProgressArray = accounts.map { $0.refreshProgress }
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
}
2017-09-17 21:34:10 +02:00
public init() {
2017-05-27 19:43:27 +02:00
// 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 = Account(dataFolder: localAccountFolder, settingsFile: localAccountSettingsFile, type: .onMyMac, accountID: localAccountIdentifier)!
accountsDictionary[localAccount.accountID] = localAccount
2017-05-27 19:43:27 +02:00
readNonLocalAccountsFromDisk()
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
DispatchQueue.main.async {
self.updateUnreadCount()
}
2017-05-27 19:43:27 +02:00
}
// MARK: API
2017-09-18 02:56:04 +02:00
public func existingAccount(with accountID: String) -> Account? {
2017-05-27 19:43:27 +02:00
return accountsDictionary[accountID]
2017-05-27 19:43:27 +02:00
}
2017-09-17 21:34:10 +02:00
public func refreshAll() {
2017-05-27 19:43:27 +02:00
accounts.forEach { $0.refreshAll() }
2017-05-27 19:43:27 +02:00
}
2017-10-02 22:15:07 +02:00
public func anyAccountHasAtLeastOneFeed() -> Bool {
2017-05-27 19:43:27 +02:00
for account in accounts {
if account.hasAtLeastOneFeed() {
2017-05-27 19:43:27 +02:00
return true
}
}
return false
}
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
2017-05-27 19:43:27 +02:00
for account in accounts {
if let _ = account.existingFeed(withURL: urlString) {
2017-05-27 19:43:27 +02:00
return true
}
}
return false
}
func updateUnreadCount() {
unreadCount = calculateUnreadCount(accounts)
2017-05-27 19:43:27 +02:00
}
// MARK: Notifications
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
2017-05-27 19:43:27 +02:00
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.accountID] = oneAccount
2017-05-27 19:43:27 +02:00
}
}
}
private func accountsSortedByName() -> [Account] {
// LocalAccount is first.
return accounts.sorted { (account1, account2) -> Bool in
if account1 === localAccount {
return true
}
if account2 === localAccount {
return false
}
return (account1.nameForDisplay as NSString).localizedStandardCompare(account2.nameForDisplay) == .orderedAscending
2017-05-27 19:43:27 +02:00
}
}
}
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) {
if !FileManager.default.rs_fileIsFolder(folderPath) {
return nil
}
let name = NSString(string: folderPath).lastPathComponent
if name.hasPrefix(".") {
return nil
}
let nameComponents = name.components(separatedBy: "-")
2017-05-27 19:43:27 +02:00
if nameComponents.count != 2 {
return nil
}
self.folderPath = folderPath
self.folderName = name
2017-05-27 19:43:27 +02:00
self.type = nameComponents[0]
self.identifier = nameComponents[1]
self.dataFilePath = accountFilePathWithFolder(self.folderPath)
}
}