Continue transition from ODB to FeedMetadata.plist. It’s simpler and uses less memory.

This commit is contained in:
Brent Simmons 2019-03-13 23:41:43 -07:00
parent 57c5854efa
commit 79a6d5f761
3 changed files with 102 additions and 23 deletions

View File

@ -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 were 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

View File

@ -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) {

View File

@ -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) {