diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 812ad9693..9824386d4 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -279,7 +279,19 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, webFeedMetadataFile.load() opmlFile.load() + var shouldHandleRetentionPolicyChange = false + if type == .onMyMac { + let didHandlePolicyChange = metadata.performedApril2020RetentionPolicyChange ?? false + shouldHandleRetentionPolicyChange = !didHandlePolicyChange + } + DispatchQueue.main.async { + 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.flattenedWebFeeds().webFeedIDs()) self.fetchAllUnreadCounts() } diff --git a/Frameworks/Account/AccountMetadata.swift b/Frameworks/Account/AccountMetadata.swift index f4c50ab44..9f5845a64 100644 --- a/Frameworks/Account/AccountMetadata.swift +++ b/Frameworks/Account/AccountMetadata.swift @@ -24,6 +24,7 @@ final class AccountMetadata: Codable { case lastArticleFetchEndTime case endpointURL case lastCredentialRenewTime = "lastCredentialRenewTime" + case performedApril2020RetentionPolicyChange } var name: String? { @@ -92,6 +93,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/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index d0f3c424c..0ee215866 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -277,6 +277,24 @@ public final class ArticlesDatabase { articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs) articlesTable.deleteOldStatuses() } + + /// Do database cleanups made necessary by the retention policy change in April 2020. + /// + /// The retention policy for feed-based systems changed in April 2020: + /// we keep articles only for as long as they’re in the feed. + /// This change could result in a bunch of older articles suddenly + /// appearing as unread articles. + /// + /// These are articles that were in the database, + /// but weren’t appearing in the UI because they were beyond the 90-day window. + /// (The previous retention policy used a 90-day window.) + /// + /// This function marks everything as read that’s beyond that 90-day window. + /// It’s intended to be called only once on an account. + public func performApril2020RetentionPolicyChange() { + precondition(retentionStyle == .feedBased) + articlesTable.markOlderStatusesAsRead() + } } // MARK: - Private diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 635d1d9b3..ff36a8f9b 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -580,6 +580,22 @@ final class ArticlesTable: DatabaseTable { } } } + + /// Mark statuses beyond the 90-day window as read. + /// + /// This is not intended for wide use: this is part of implementing + /// the April 2020 retention policy change for feed-based accounts. + func markOlderStatusesAsRead() { + queue.runInDatabase { databaseResult in + guard let database = databaseResult.database else { + return + } + + let sql = "update statuses set read = true where dateArrived