From 473e5c83500dd61271443607d90bcb060c09d6d6 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 25 Jul 2020 12:00:31 -0700 Subject: [PATCH] =?UTF-8?q?Perform=20one-time=20retention=20policy=20adjus?= =?UTF-8?q?tment=20=E2=80=94=C2=A0mark=20articles=20older=20than=2090=20da?= =?UTF-8?q?ys=20as=20read.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Frameworks/Account/Account.swift | 24 ++++++++++++++--- .../Account/Account.xcodeproj/project.pbxproj | 4 +++ Frameworks/Account/AccountDefaults.swift | 26 +++++++++++++++++++ Frameworks/Account/AccountManager.swift | 9 ++++--- Frameworks/Account/AccountMetadata.swift | 9 +++++++ .../ArticlesDatabase/ArticlesTable.swift | 8 +++--- Frameworks/ArticlesDatabase/SearchTable.swift | 11 ++++---- 7 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 Frameworks/Account/AccountDefaults.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 23a612b5c..dfaa183d0 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -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() } diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 8775622f4..90723fbf8 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -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 = ""; }; 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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 */, diff --git a/Frameworks/Account/AccountDefaults.swift b/Frameworks/Account/AccountDefaults.swift new file mode 100644 index 000000000..5be1c5313 --- /dev/null +++ b/Frameworks/Account/AccountDefaults.swift @@ -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) + } + } +} + diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index a004c48b5..4ed15b290 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -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? { diff --git a/Frameworks/Account/AccountMetadata.swift b/Frameworks/Account/AccountMetadata.swift index c878fbe5b..1b481d877 100644 --- a/Frameworks/Account/AccountMetadata.swift +++ b/Frameworks/Account/AccountMetadata.swift @@ -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) { diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 6344a4a0e..5e47a9df1 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -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
@@ -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, _ 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, _ database: FMDatabase) { articles.forEach { performInitialIndex($0, database) }