Perform one-time retention policy adjustment — mark articles older than 90 days as read.

This commit is contained in:
Brent Simmons
2020-07-25 12:00:31 -07:00
parent cd44f28acc
commit 473e5c8350
7 changed files with 75 additions and 16 deletions

View File

@@ -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 accounts 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()
}

View File

@@ -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 */,

View 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)
}
}
}

View File

@@ -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 dont 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? {

View File

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

View File

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

View File

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