Continue transition from ODB to FeedMetadata.plist. It’s simpler and uses less memory.
This commit is contained in:
parent
57c5854efa
commit
79a6d5f761
|
@ -80,6 +80,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
|
||||
private let feedMetadataPath: String
|
||||
private typealias FeedMetadataDictionary = [String: FeedMetadata]
|
||||
private var feedMetadata = FeedMetadataDictionary()
|
||||
private var feedMetadataDirty = false {
|
||||
didSet {
|
||||
queueSaveMetadataIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private var startingUp = true
|
||||
|
||||
private struct SettingsKey {
|
||||
|
@ -137,6 +146,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
||||
|
||||
self.feedMetadataPath = (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist")
|
||||
let settingsODBFilePath = (dataFolder as NSString).appendingPathComponent("Settings.odb")
|
||||
self.settingsODB = ODB(filepath: settingsODBFilePath)
|
||||
self.settingsODB.vacuum()
|
||||
|
@ -167,6 +177,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
delegate.refreshAll(for: self)
|
||||
}
|
||||
|
||||
func metadata(for feed: Feed) -> FeedMetadata {
|
||||
if let d = feedMetadata[feed.feedID] {
|
||||
assert(d.delegate === self)
|
||||
return d
|
||||
}
|
||||
let d = FeedMetadata(feedID: feed.feedID)
|
||||
d.delegate = self
|
||||
feedMetadata[feed.feedID] = d
|
||||
return d
|
||||
}
|
||||
|
||||
public func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping RSVoidCompletionBlock) {
|
||||
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
|
@ -559,6 +580,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
}
|
||||
}
|
||||
|
||||
@objc func saveMetadataIfNeeded() {
|
||||
if feedMetadataDirty {
|
||||
saveFeedMetadata()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
@ -590,6 +617,15 @@ extension Account {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - FeedMetadataDelegate
|
||||
|
||||
extension Account: FeedMetadataDelegate {
|
||||
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||
feedMetadataDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Disk (Private)
|
||||
|
||||
private extension Account {
|
||||
|
@ -614,7 +650,7 @@ private extension Account {
|
|||
}
|
||||
|
||||
func pullObjectsFromDisk() {
|
||||
|
||||
|
||||
// 9/16/2018: Turning a corner — we used to store data in a plist file,
|
||||
// but now we’re switching over to OPML. Read the plist file one last time,
|
||||
// then rename it so we never read from it again.
|
||||
|
@ -654,9 +690,20 @@ private extension Account {
|
|||
return
|
||||
}
|
||||
|
||||
importFeedMetadata()
|
||||
importOPMLFile(path: opmlFilePath)
|
||||
}
|
||||
|
||||
func importFeedMetadata() {
|
||||
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 importOPMLFile(path: String) {
|
||||
let opmlFileURL = URL(fileURLWithPath: path)
|
||||
var fileData: Data?
|
||||
|
@ -703,6 +750,33 @@ private extension Account {
|
|||
NSApplication.shared.presentError(error)
|
||||
}
|
||||
}
|
||||
|
||||
func queueSaveMetadataIfNeeded() {
|
||||
Account.saveQueue.add(self, #selector(saveMetadataIfNeeded))
|
||||
}
|
||||
|
||||
private func metadataForOnlySubscribedToFeeds() -> FeedMetadataDictionary {
|
||||
let feedIDs = idToFeedDictionary.keys
|
||||
return feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
|
||||
return feedIDs.contains(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
|
||||
|
|
|
@ -35,33 +35,33 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
|
||||
public var homePageURL: String? {
|
||||
get {
|
||||
return settingsTable.string(for: Key.homePageURL)
|
||||
return metadata?.homePageURL
|
||||
}
|
||||
set {
|
||||
if let url = newValue {
|
||||
settingsTable.setString(url.rs_normalizedURL(), for: Key.homePageURL)
|
||||
metadata?.homePageURL = url.rs_normalizedURL()
|
||||
}
|
||||
else {
|
||||
settingsTable.setString(nil, for: Key.homePageURL)
|
||||
metadata?.homePageURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var iconURL: String? {
|
||||
get {
|
||||
return settingsTable.string(for: Key.iconURL)
|
||||
return metadata?.iconURL
|
||||
}
|
||||
set {
|
||||
settingsTable.setString(newValue, for: Key.iconURL)
|
||||
metadata?.iconURL = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public var faviconURL: String? {
|
||||
get {
|
||||
return settingsTable.string(for: Key.faviconURL)
|
||||
return metadata?.faviconURL
|
||||
}
|
||||
set {
|
||||
settingsTable.setString(newValue, for: Key.faviconURL)
|
||||
metadata?.faviconURL = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,17 +80,17 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
|
||||
public var authors: Set<Author>? {
|
||||
get {
|
||||
guard let authorsJSON = settingsTable.string(for: Key.authors) else {
|
||||
return nil
|
||||
if let authorsArray = metadata?.authors {
|
||||
return Set(authorsArray)
|
||||
}
|
||||
return Author.authorsWithJSON(authorsJSON)
|
||||
return nil
|
||||
}
|
||||
set {
|
||||
if let authorsJSON = newValue?.json() {
|
||||
settingsTable.setString(authorsJSON, for: Key.authors)
|
||||
if let authorsSet = newValue {
|
||||
metadata?.authors = Array(authorsSet)
|
||||
}
|
||||
else {
|
||||
settingsTable.setString(nil, for: Key.authors)
|
||||
metadata?.authors = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,22 +118,19 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
|
||||
public var conditionalGetInfo: HTTPConditionalGetInfo? {
|
||||
get {
|
||||
let lastModified = settingsTable.string(for: Key.conditionalGetLastModified)
|
||||
let etag = settingsTable.string(for: Key.conditionalGetEtag)
|
||||
return HTTPConditionalGetInfo(lastModified: lastModified, etag: etag)
|
||||
return metadata?.conditionalGetInfo
|
||||
}
|
||||
set {
|
||||
settingsTable.setString(newValue?.lastModified, for: Key.conditionalGetLastModified)
|
||||
settingsTable.setString(newValue?.etag, for: Key.conditionalGetEtag)
|
||||
metadata?.conditionalGetInfo = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public var contentHash: String? {
|
||||
get {
|
||||
return settingsTable.string(for: Key.contentHash)
|
||||
return metadata?.contentHash
|
||||
}
|
||||
set {
|
||||
settingsTable.setString(newValue, for: Key.contentHash)
|
||||
metadata?.contentHash = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,6 +170,15 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
private let settingsTable: ODBRawValueTable
|
||||
private let accountID: String // Used for hashing and equality; account may turn nil
|
||||
|
||||
private var _metadata: FeedMetadata?
|
||||
private var metadata: FeedMetadata? {
|
||||
if let cachedMetadata = _metadata {
|
||||
return cachedMetadata
|
||||
}
|
||||
_metadata = account?.metadata(for: self)
|
||||
return _metadata
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(account: Account, url: String, feedID: String) {
|
||||
|
|
|
@ -96,9 +96,8 @@ final class FeedMetadata: Codable {
|
|||
|
||||
weak var delegate: FeedMetadataDelegate?
|
||||
|
||||
init(feedID: String, delegate: FeedMetadataDelegate) {
|
||||
init(feedID: String) {
|
||||
self.feedID = feedID
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
func valueDidChange(_ key: CodingKeys) {
|
||||
|
|
Loading…
Reference in New Issue