mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-07 14:23:28 +01:00
Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
b5b2dde3b9
@ -167,18 +167,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
|
||||
|
||||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
private lazy var opmlFile: OPMLFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||
|
||||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
|
||||
private let metadataPath: String
|
||||
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)
|
||||
var metadata = AccountMetadata()
|
||||
private var metadataDirty = false {
|
||||
didSet {
|
||||
queueSaveAccountMetadatafNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private let feedMetadataPath: String
|
||||
private typealias FeedMetadataDictionary = [String: FeedMetadata]
|
||||
@ -249,7 +244,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
||||
|
||||
self.feedMetadataPath = (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist")
|
||||
self.metadataPath = (dataFolder as NSString).appendingPathComponent("Settings.plist")
|
||||
|
||||
switch type {
|
||||
case .onMyMac:
|
||||
@ -768,12 +762,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
@objc func saveAccountMetadataIfNeeded() {
|
||||
if metadataDirty && !isDeleted {
|
||||
saveAccountMetadata()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
@ -791,7 +779,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
extension Account: AccountMetadataDelegate {
|
||||
func valueDidChange(_ accountMetadata: AccountMetadata, key: AccountMetadata.CodingKeys) {
|
||||
metadataDirty = true
|
||||
metadataFile.markAsDirty()
|
||||
}
|
||||
}
|
||||
|
||||
@ -946,22 +934,11 @@ private extension Account {
|
||||
private extension Account {
|
||||
|
||||
func pullObjectsFromDisk() {
|
||||
loadAccountMetadata()
|
||||
metadataFile.load()
|
||||
loadFeedMetadata()
|
||||
opmlFile.load()
|
||||
}
|
||||
|
||||
func loadAccountMetadata() {
|
||||
let url = URL(fileURLWithPath: metadataPath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
metadata.delegate = self
|
||||
return
|
||||
}
|
||||
let decoder = PropertyListDecoder()
|
||||
metadata = (try? decoder.decode(AccountMetadata.self, from: data)) ?? AccountMetadata()
|
||||
metadata.delegate = self
|
||||
}
|
||||
|
||||
func loadFeedMetadata() {
|
||||
let url = URL(fileURLWithPath: feedMetadataPath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
@ -999,24 +976,6 @@ private extension Account {
|
||||
}
|
||||
}
|
||||
|
||||
func queueSaveAccountMetadatafNeeded() {
|
||||
Account.saveQueue.add(self, #selector(saveAccountMetadataIfNeeded))
|
||||
}
|
||||
|
||||
func saveAccountMetadata() {
|
||||
metadataDirty = false
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
let url = URL(fileURLWithPath: metadataPath)
|
||||
do {
|
||||
let data = try encoder.encode(metadata)
|
||||
try data.write(to: url)
|
||||
}
|
||||
catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
@ -10,6 +10,7 @@
|
||||
5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */; };
|
||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
||||
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.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 */; };
|
||||
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; };
|
||||
@ -122,6 +123,7 @@
|
||||
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.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>"; };
|
||||
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.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>"; };
|
||||
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
|
||||
@ -337,6 +339,7 @@
|
||||
846E77531F6F00E300A165E2 /* AccountManager.swift */,
|
||||
5170743B232AEDB500A461A3 /* OPMLFile.swift */,
|
||||
84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */,
|
||||
510BD110232C3801002692E4 /* AccountMetadataFile.swift */,
|
||||
84F73CF0202788D80000BCEF /* ArticleFetcher.swift */,
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */,
|
||||
8419740D1F6DD25F006346C4 /* Container.swift */,
|
||||
@ -600,6 +603,7 @@
|
||||
84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */,
|
||||
5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */,
|
||||
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
||||
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */,
|
||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||
|
85
Frameworks/Account/AccountMetadataFile.swift
Normal file
85
Frameworks/Account/AccountMetadataFile.swift
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// AccountMetadataFile.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
|
||||
import RSParser
|
||||
|
||||
final class AccountMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
|
||||
|
||||
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 AccountMetadataFile {
|
||||
|
||||
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.metadata = (try? decoder.decode(AccountMetadata.self, from: fileData)) ?? AccountMetadata()
|
||||
}
|
||||
})
|
||||
|
||||
if let error = errorPointer?.pointee {
|
||||
os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription)
|
||||
}
|
||||
|
||||
account.metadata.delegate = account
|
||||
}
|
||||
|
||||
func saveCallback() {
|
||||
guard !account.isDeleted else { return }
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
|
||||
let errorPointer: NSErrorPointer = nil
|
||||
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: .forReplacing, error: errorPointer, byAccessor: { writeURL in
|
||||
do {
|
||||
let data = try encoder.encode(account.metadata)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -11,81 +11,50 @@ import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
|
||||
final class OPMLFile: NSObject, NSFilePresenter {
|
||||
final class OPMLFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "account")
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
|
||||
|
||||
private var isDirty = false {
|
||||
didSet {
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private var isLoading = false
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
private let operationQueue: OperationQueue
|
||||
|
||||
var presentedItemURL: URL? {
|
||||
return fileURL
|
||||
}
|
||||
|
||||
var presentedItemOperationQueue: OperationQueue {
|
||||
return operationQueue
|
||||
}
|
||||
private lazy var managedFile = ManagedResourceFile(fileURL: fileURL, load: loadCallback, save: saveCallback)
|
||||
|
||||
init(filename: String, account: Account) {
|
||||
self.fileURL = URL(fileURLWithPath: filename)
|
||||
self.account = account
|
||||
operationQueue = OperationQueue()
|
||||
operationQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
super.init()
|
||||
|
||||
NSFileCoordinator.addFilePresenter(self)
|
||||
}
|
||||
|
||||
func presentedItemDidChange() {
|
||||
DispatchQueue.main.async {
|
||||
self.reload()
|
||||
}
|
||||
}
|
||||
|
||||
func markAsDirty() {
|
||||
if !isLoading {
|
||||
isDirty = true
|
||||
}
|
||||
managedFile.markAsDirty()
|
||||
}
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
Account.saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
managedFile.queueSaveToDiskIfNeeded()
|
||||
}
|
||||
|
||||
func load() {
|
||||
isLoading = true
|
||||
guard let opmlItems = parsedOPMLItems() else { return }
|
||||
BatchUpdate.shared.perform {
|
||||
account.loadOPMLItems(opmlItems, parentFolder: nil)
|
||||
}
|
||||
isLoading = false
|
||||
managedFile.load()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension OPMLFile {
|
||||
|
||||
@objc func saveToDiskIfNeeded() {
|
||||
if isDirty && !account.isDeleted {
|
||||
isDirty = false
|
||||
save()
|
||||
|
||||
func loadCallback() {
|
||||
guard let opmlItems = parsedOPMLItems() else { return }
|
||||
BatchUpdate.shared.perform {
|
||||
account.topLevelFeeds.removeAll()
|
||||
account.loadOPMLItems(opmlItems, parentFolder: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func save() {
|
||||
|
||||
func saveCallback() {
|
||||
guard !account.isDeleted else { return }
|
||||
|
||||
let opmlDocumentString = opmlDocument()
|
||||
|
||||
let errorPointer: NSErrorPointer = nil
|
||||
let fileCoordinator = NSFileCoordinator(filePresenter: self)
|
||||
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: .forReplacing, error: errorPointer, byAccessor: { writeURL in
|
||||
do {
|
||||
@ -100,21 +69,11 @@ private extension OPMLFile {
|
||||
}
|
||||
}
|
||||
|
||||
func reload() {
|
||||
isLoading = true
|
||||
guard let opmlItems = parsedOPMLItems() else { return }
|
||||
BatchUpdate.shared.perform {
|
||||
account.topLevelFeeds.removeAll()
|
||||
account.loadOPMLItems(opmlItems, parentFolder: nil)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func parsedOPMLItems() -> [RSOPMLItem]? {
|
||||
|
||||
var fileData: Data? = nil
|
||||
let errorPointer: NSErrorPointer = nil
|
||||
let fileCoordinator = NSFileCoordinator(filePresenter: self)
|
||||
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
|
||||
|
||||
fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in
|
||||
do {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit d640a2310b96a0a3d4d34c49c08c7bce195d0762
|
||||
Subproject commit ee343a204d2f402240fe1c226ff4b8dbe33a3129
|
Loading…
x
Reference in New Issue
Block a user