Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
5c3f0f6f42
|
@ -129,7 +129,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
public var folders: Set<Folder>? = Set<Folder>()
|
public var folders: Set<Folder>? = Set<Folder>()
|
||||||
private var feedDictionaryNeedsUpdate = true
|
private var feedDictionaryNeedsUpdate = true
|
||||||
private var _idToFeedDictionary = [String: Feed]()
|
private var _idToFeedDictionary = [String: Feed]()
|
||||||
private var idToFeedDictionary: [String: Feed] {
|
var idToFeedDictionary: [String: Feed] {
|
||||||
if feedDictionaryNeedsUpdate {
|
if feedDictionaryNeedsUpdate {
|
||||||
rebuildFeedDictionaries()
|
rebuildFeedDictionaries()
|
||||||
}
|
}
|
||||||
|
@ -172,17 +172,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
private var flattenedFeedsNeedUpdate = true
|
private var flattenedFeedsNeedUpdate = true
|
||||||
|
|
||||||
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||||
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.opml"), account: self)
|
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
|
||||||
var metadata = AccountMetadata()
|
var metadata = AccountMetadata()
|
||||||
|
|
||||||
private let feedMetadataPath: String
|
private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
|
||||||
private typealias FeedMetadataDictionary = [String: FeedMetadata]
|
typealias FeedMetadataDictionary = [String: FeedMetadata]
|
||||||
private var feedMetadata = FeedMetadataDictionary()
|
var feedMetadata = FeedMetadataDictionary()
|
||||||
private var feedMetadataDirty = false {
|
|
||||||
didSet {
|
|
||||||
queueSaveFeedMetadataIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var startingUp = true
|
private var startingUp = true
|
||||||
|
|
||||||
|
@ -243,8 +238,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
||||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
||||||
|
|
||||||
self.feedMetadataPath = (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist")
|
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .onMyMac:
|
case .onMyMac:
|
||||||
defaultName = Account.defaultLocalAccountName
|
defaultName = Account.defaultLocalAccountName
|
||||||
|
@ -266,7 +259,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||||
|
|
||||||
pullObjectsFromDisk()
|
metadataFile.load()
|
||||||
|
feedMetadataFile.load()
|
||||||
|
opmlFile.load()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.fetchAllUnreadCounts()
|
self.fetchAllUnreadCounts()
|
||||||
|
@ -756,12 +751,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func saveFeedMetadataIfNeeded() {
|
|
||||||
if feedMetadataDirty && !isDeleted {
|
|
||||||
saveFeedMetadata()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Hashable
|
// MARK: - Hashable
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
@ -788,7 +777,7 @@ extension Account: AccountMetadataDelegate {
|
||||||
extension Account: FeedMetadataDelegate {
|
extension Account: FeedMetadataDelegate {
|
||||||
|
|
||||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||||
feedMetadataDirty = true
|
feedMetadataFile.markAsDirty()
|
||||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -929,55 +918,6 @@ private extension Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Disk (Private)
|
|
||||||
|
|
||||||
private extension Account {
|
|
||||||
|
|
||||||
func pullObjectsFromDisk() {
|
|
||||||
metadataFile.load()
|
|
||||||
loadFeedMetadata()
|
|
||||||
opmlFile.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFeedMetadata() {
|
|
||||||
let url = URL(fileURLWithPath: feedMetadataPath)
|
|
||||||
guard let data = try? Data(contentsOf: url) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let decoder = PropertyListDecoder()
|
|
||||||
feedMetadata = (try? decoder.decode(FeedMetadataDictionary.self, from: data)) ?? FeedMetadataDictionary()
|
|
||||||
feedMetadata.values.forEach { $0.delegate = self }
|
|
||||||
}
|
|
||||||
|
|
||||||
func queueSaveFeedMetadataIfNeeded() {
|
|
||||||
Account.saveQueue.add(self, #selector(saveFeedMetadataIfNeeded))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func metadataForOnlySubscribedToFeeds() -> FeedMetadataDictionary {
|
|
||||||
let feedIDs = idToFeedDictionary.keys
|
|
||||||
return feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
|
|
||||||
return feedIDs.contains(metadata.feedID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFeedMetadata() {
|
|
||||||
feedMetadataDirty = false
|
|
||||||
|
|
||||||
let d = metadataForOnlySubscribedToFeeds()
|
|
||||||
let encoder = PropertyListEncoder()
|
|
||||||
encoder.outputFormat = .binary
|
|
||||||
let url = URL(fileURLWithPath: feedMetadataPath)
|
|
||||||
do {
|
|
||||||
let data = try encoder.encode(d)
|
|
||||||
try data.write(to: url)
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
assertionFailure(error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private extension Account {
|
private extension Account {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
||||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
||||||
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; };
|
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; };
|
||||||
|
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */; };
|
||||||
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedSyncTest.swift */; };
|
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedSyncTest.swift */; };
|
||||||
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
|
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
|
||||||
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; };
|
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; };
|
||||||
|
@ -124,6 +125,7 @@
|
||||||
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
|
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
|
||||||
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
|
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
|
||||||
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = "<group>"; };
|
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = "<group>"; };
|
||||||
|
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadataFile.swift; sourceTree = "<group>"; };
|
||||||
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedSyncTest.swift; sourceTree = "<group>"; };
|
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedSyncTest.swift; sourceTree = "<group>"; };
|
||||||
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
|
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
|
||||||
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
|
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
|
||||||
|
@ -347,6 +349,7 @@
|
||||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
||||||
844B297C2106C7EC004020B3 /* Feed.swift */,
|
844B297C2106C7EC004020B3 /* Feed.swift */,
|
||||||
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */,
|
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */,
|
||||||
|
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */,
|
||||||
841974001F6DD1EC006346C4 /* Folder.swift */,
|
841974001F6DD1EC006346C4 /* Folder.swift */,
|
||||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */,
|
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */,
|
||||||
5165D71F22835E9800D9D53D /* FeedFinder */,
|
5165D71F22835E9800D9D53D /* FeedFinder */,
|
||||||
|
@ -576,6 +579,7 @@
|
||||||
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
||||||
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
||||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||||
|
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
||||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
||||||
|
|
|
@ -9,11 +9,10 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
|
||||||
|
|
||||||
final class AccountMetadataFile {
|
final class AccountMetadataFile {
|
||||||
|
|
||||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
|
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "accountMetadataFile")
|
||||||
|
|
||||||
private let fileURL: URL
|
private let fileURL: URL
|
||||||
private let account: Account
|
private let account: Account
|
||||||
|
@ -68,7 +67,7 @@ private extension AccountMetadataFile {
|
||||||
let errorPointer: NSErrorPointer = nil
|
let errorPointer: NSErrorPointer = nil
|
||||||
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||||
|
|
||||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: .forReplacing, error: errorPointer, byAccessor: { writeURL in
|
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in
|
||||||
do {
|
do {
|
||||||
let data = try encoder.encode(account.metadata)
|
let data = try encoder.encode(account.metadata)
|
||||||
try data.write(to: writeURL)
|
try data.write(to: writeURL)
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
//
|
||||||
|
// FeedMetadataFile.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 9/13/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
import RSCore
|
||||||
|
|
||||||
|
final class FeedMetadataFile {
|
||||||
|
|
||||||
|
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "feedMetadataFile")
|
||||||
|
|
||||||
|
private let fileURL: URL
|
||||||
|
private let account: Account
|
||||||
|
private lazy var managedFile = ManagedResourceFile(fileURL: fileURL, load: loadCallback, save: saveCallback)
|
||||||
|
|
||||||
|
init(filename: String, account: Account) {
|
||||||
|
self.fileURL = URL(fileURLWithPath: filename)
|
||||||
|
self.account = account
|
||||||
|
}
|
||||||
|
|
||||||
|
func markAsDirty() {
|
||||||
|
managedFile.markAsDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func queueSaveToDiskIfNeeded() {
|
||||||
|
managedFile.queueSaveToDiskIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
managedFile.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension FeedMetadataFile {
|
||||||
|
|
||||||
|
func loadCallback() {
|
||||||
|
|
||||||
|
let errorPointer: NSErrorPointer = nil
|
||||||
|
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||||
|
|
||||||
|
fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in
|
||||||
|
if let fileData = try? Data(contentsOf: readURL) {
|
||||||
|
let decoder = PropertyListDecoder()
|
||||||
|
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let error = errorPointer?.pointee {
|
||||||
|
os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.feedMetadata.values.forEach { $0.delegate = account }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveCallback() {
|
||||||
|
guard !account.isDeleted else { return }
|
||||||
|
|
||||||
|
let feedMetadata = metadataForOnlySubscribedToFeeds()
|
||||||
|
|
||||||
|
let encoder = PropertyListEncoder()
|
||||||
|
encoder.outputFormat = .binary
|
||||||
|
|
||||||
|
let errorPointer: NSErrorPointer = nil
|
||||||
|
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||||
|
|
||||||
|
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in
|
||||||
|
do {
|
||||||
|
let data = try encoder.encode(feedMetadata)
|
||||||
|
try data.write(to: writeURL)
|
||||||
|
} catch let error as NSError {
|
||||||
|
os_log(.error, log: log, "Save to disk failed: %@.", error.localizedDescription)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let error = errorPointer?.pointee {
|
||||||
|
os_log(.error, log: log, "Save to disk coordination failed: %@.", error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func metadataForOnlySubscribedToFeeds() -> Account.FeedMetadataDictionary {
|
||||||
|
let feedIDs = account.idToFeedDictionary.keys
|
||||||
|
return account.feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
|
||||||
|
return feedIDs.contains(metadata.feedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ private extension OPMLFile {
|
||||||
let errorPointer: NSErrorPointer = nil
|
let errorPointer: NSErrorPointer = nil
|
||||||
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||||
|
|
||||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: .forReplacing, error: errorPointer, byAccessor: { writeURL in
|
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in
|
||||||
do {
|
do {
|
||||||
try opmlDocumentString.write(to: writeURL, atomically: true, encoding: .utf8)
|
try opmlDocumentString.write(to: writeURL, atomically: true, encoding: .utf8)
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
|
|
Loading…
Reference in New Issue