mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Perform one-time retention policy adjustment — mark articles older than 90 days as read.
This commit is contained in:
@@ -212,7 +212,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return delegate.supportsSubFolders
|
||||
}
|
||||
|
||||
init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) {
|
||||
init?(dataFolder: String, type: AccountType, accountID: String, defaults: AccountDefaults, transport: Transport? = nil) {
|
||||
switch type {
|
||||
case .onMyMac:
|
||||
self.delegate = LocalAccountDelegate()
|
||||
@@ -229,7 +229,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
self.opmlFilePath = (dataFolder as NSString).appendingPathComponent("Subscriptions.opml")
|
||||
|
||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
||||
let retentionStyle: ArticlesDatabase.RetentionStyle = type == .onMyMac ? .feedBased : .syncSystem
|
||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID, retentionStyle: retentionStyle)
|
||||
|
||||
self.feedMetadataPath = (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist")
|
||||
self.metadataPath = (dataFolder as NSString).appendingPathComponent("Settings.plist")
|
||||
@@ -254,9 +255,24 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||
|
||||
pullObjectsFromDisk()
|
||||
|
||||
|
||||
var shouldHandleRetentionPolicyChange = false
|
||||
if !defaults.performedApril2020RetentionPolicyChange {
|
||||
// Check each account’s metadata as well as UserDefaults.
|
||||
// We want to make sure we do this only once per account.
|
||||
if type == .onMyMac {
|
||||
let didHandlePolicyChange = metadata.performedApril2020RetentionPolicyChange ?? false
|
||||
shouldHandleRetentionPolicyChange = !didHandlePolicyChange
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs())
|
||||
if shouldHandleRetentionPolicyChange {
|
||||
// Handle one-time database changes made necessary by April 2020 retention policy change.
|
||||
self.database.performApril2020RetentionPolicyChange()
|
||||
self.metadata.performedApril2020RetentionPolicyChange = true
|
||||
}
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedFeeds().feedIDs())
|
||||
self.fetchAllUnreadCounts()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* Feed.swift */; };
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
|
||||
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
|
||||
8467472124C381CF00A4E594 /* AccountDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8467472024C381CF00A4E594 /* AccountDefaults.swift */; };
|
||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
||||
846CA1882392349E00B55117 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846CA1872392349E00B55117 /* SyncDatabase.framework */; };
|
||||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419740D1F6DD25F006346C4 /* Container.swift */; };
|
||||
@@ -154,6 +155,7 @@
|
||||
844B297C2106C7EC004020B3 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
|
||||
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8467472024C381CF00A4E594 /* AccountDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDefaults.swift; sourceTree = "<group>"; };
|
||||
846CA1872392349E00B55117 /* SyncDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SyncDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
|
||||
848934F61F62484F00CEBD24 /* Account.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Account.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -303,6 +305,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
848935101F62486800CEBD24 /* Account.swift */,
|
||||
8467472024C381CF00A4E594 /* AccountDefaults.swift */,
|
||||
841974241F6DDCE4006346C4 /* AccountDelegate.swift */,
|
||||
51E3EB40229AF61B00645299 /* AccountError.swift */,
|
||||
846E77531F6F00E300A165E2 /* AccountManager.swift */,
|
||||
@@ -545,6 +548,7 @@
|
||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
||||
8467472124C381CF00A4E594 /* AccountDefaults.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
||||
515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */,
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||
|
||||
26
Frameworks/Account/AccountDefaults.swift
Normal file
26
Frameworks/Account/AccountDefaults.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// AccountDefaults.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Brent Simmons on 7/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AccountDefaults {
|
||||
|
||||
enum Key {
|
||||
static let performedApril2020RetentionPolicyChangeKey = "performedApril2020RetentionPolicyChange"
|
||||
}
|
||||
|
||||
var performedApril2020RetentionPolicyChange: Bool {
|
||||
get {
|
||||
return UserDefaults.standard.bool(forKey: Key.performedApril2020RetentionPolicyChangeKey)
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: Key.performedApril2020RetentionPolicyChangeKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ public final class AccountManager: UnreadCountProvider {
|
||||
let downloadProgressArray = activeAccounts.map { $0.refreshProgress }
|
||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
|
||||
private var defaults = AccountDefaults()
|
||||
|
||||
public init() {
|
||||
// The local "On My Mac" account must always exist, even if it's empty.
|
||||
@@ -86,10 +88,11 @@ public final class AccountManager: UnreadCountProvider {
|
||||
abort()
|
||||
}
|
||||
|
||||
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier)!
|
||||
defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier, defaults: defaults)!
|
||||
accountsDictionary[defaultAccount.accountID] = defaultAccount
|
||||
|
||||
readAccountsFromDisk()
|
||||
defaults.performedApril2020RetentionPolicyChange = true // Part of a paranoid safeguard to make sure we don’t do it more than once.
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil)
|
||||
@@ -112,7 +115,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
abort()
|
||||
}
|
||||
|
||||
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID)!
|
||||
let account = Account(dataFolder: accountFolder, type: type, accountID: accountID, defaults: defaults)!
|
||||
accountsDictionary[accountID] = account
|
||||
|
||||
NotificationCenter.default.post(name: .AccountsDidChange, object: self)
|
||||
@@ -255,7 +258,7 @@ private extension AccountManager {
|
||||
}
|
||||
|
||||
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
|
||||
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier)
|
||||
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier, defaults: defaults)
|
||||
}
|
||||
|
||||
func loadAccount(_ filename: String) -> Account? {
|
||||
|
||||
@@ -21,6 +21,7 @@ final class AccountMetadata: Codable {
|
||||
case username
|
||||
case conditionalGetInfo
|
||||
case lastArticleFetch
|
||||
case performedApril2020RetentionPolicyChange
|
||||
}
|
||||
|
||||
var name: String? {
|
||||
@@ -63,6 +64,14 @@ final class AccountMetadata: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
var performedApril2020RetentionPolicyChange: Bool? {
|
||||
didSet {
|
||||
if performedApril2020RetentionPolicyChange != oldValue {
|
||||
valueDidChange(.performedApril2020RetentionPolicyChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: AccountMetadataDelegate?
|
||||
|
||||
func valueDidChange(_ key: CodingKeys) {
|
||||
|
||||
@@ -27,7 +27,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
}()
|
||||
|
||||
// TODO: update articleCutoffDate as time passes and based on user preferences.
|
||||
private var articleCutoffDate = Date().bySubtracting(days: 3 * 31)
|
||||
let articleCutoffDate = Date().bySubtracting(days: 90)
|
||||
private var maximumArticleCutoffDate = Date().bySubtracting(days: 4 * 31)
|
||||
|
||||
private typealias ArticlesFetchMethod = (FMDatabase) -> Set<Article>
|
||||
@@ -397,7 +397,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
// MARK: - Indexing
|
||||
|
||||
func indexUnindexedArticles() {
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInTransaction { (database) in
|
||||
let sql = "select articleID from articles where searchRowID is null limit 500;"
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
return
|
||||
@@ -406,7 +406,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
if articleIDs.isEmpty {
|
||||
return
|
||||
}
|
||||
self.searchTable.ensureIndexedArticles(for: articleIDs)
|
||||
self.searchTable.ensureIndexedArticles(articleIDs, database)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.indexUnindexedArticles()
|
||||
@@ -510,7 +510,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
/// the April 2020 retention policy change for feed-based accounts.
|
||||
func markOlderStatusesAsRead() {
|
||||
queue.runInTransaction { database in
|
||||
let sql = "update statuses set read = true where dateArrived<?;"
|
||||
let sql = "update statuses set read = 1 where dateArrived<?;"
|
||||
let parameters = [self.articleCutoffDate] as [Any]
|
||||
database.executeUpdate(sql, withArgumentsIn: parameters)
|
||||
}
|
||||
|
||||
@@ -77,12 +77,8 @@ final class SearchTable: DatabaseTable {
|
||||
self.ensureIndexedArticles(articleIDs, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension SearchTable {
|
||||
|
||||
/// Add to, or update, the search index for articles with specified IDs.
|
||||
func ensureIndexedArticles(_ articleIDs: Set<String>, _ database: FMDatabase) {
|
||||
guard let articlesTable = articlesTable else {
|
||||
return
|
||||
@@ -97,6 +93,11 @@ private extension SearchTable {
|
||||
let indexedArticles = articleSearchInfos.filter { $0.searchRowID != nil }
|
||||
updateIndexForArticles(indexedArticles, database)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension SearchTable {
|
||||
|
||||
func performInitialIndexForArticles(_ articles: Set<ArticleSearchInfo>, _ database: FMDatabase) {
|
||||
articles.forEach { performInitialIndex($0, database) }
|
||||
|
||||
Reference in New Issue
Block a user