From bc2d3250226e63a98a363fa66ef4bb9ac013c8d7 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 22 Apr 2020 20:41:13 -0700 Subject: [PATCH 1/4] =?UTF-8?q?Remove=20an=20error=20log=20that=E2=80=99s?= =?UTF-8?q?=20just=20noise.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Shared/HTMLMetadata/HTMLMetadataDownloader.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Shared/HTMLMetadata/HTMLMetadataDownloader.swift b/Shared/HTMLMetadata/HTMLMetadataDownloader.swift index 327257782..40651d420 100644 --- a/Shared/HTMLMetadata/HTMLMetadataDownloader.swift +++ b/Shared/HTMLMetadata/HTMLMetadataDownloader.swift @@ -28,10 +28,6 @@ struct HTMLMetadataDownloader { return } - if let error = error { - appDelegate.logMessage("Error downloading metadata at \(url): \(error)", type: .warning) - } - completion(nil) } } From f00df4a8e9d5bb9294c59cb02e702fbf703b6d10 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 22 Apr 2020 21:41:56 -0700 Subject: [PATCH 2/4] Bump version to 41. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 4a153915d..b76a85b25 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0.1 -CURRENT_PROJECT_VERSION = 40 +CURRENT_PROJECT_VERSION = 41 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From c55d8c540e3616416b087139534b9b0bfd187781 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 22 Apr 2020 21:43:10 -0700 Subject: [PATCH 3/4] Implement revised retention policy for feed-based accounts. Keep all articles currently in the feed and all articles in the last 30 days. --- Frameworks/Account/Account.swift | 27 ++++++++++++------ .../ArticlesDatabase/ArticlesDatabase.swift | 5 ++-- .../ArticlesDatabase/ArticlesTable.swift | 28 +++++++++++-------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 9824386d4..2c1637eaa 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -1248,32 +1248,41 @@ private extension Account { } } - func sendNotificationAbout(_ newAndUpdatedArticles: NewAndUpdatedArticles) { + func sendNotificationAbout(_ articleChanges: ArticleChanges) { var webFeeds = Set() - if let newArticles = newAndUpdatedArticles.newArticles { + if let newArticles = articleChanges.newArticles { webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed })) } - if let updatedArticles = newAndUpdatedArticles.updatedArticles { + if let updatedArticles = articleChanges.updatedArticles { webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed })) } var shouldSendNotification = false + var shouldUpdateUnreadCounts = false var userInfo = [String: Any]() - if let newArticles = newAndUpdatedArticles.newArticles, !newArticles.isEmpty { + if let newArticles = articleChanges.newArticles, !newArticles.isEmpty { shouldSendNotification = true + shouldUpdateUnreadCounts = true userInfo[UserInfoKey.newArticles] = newArticles + } + + if let updatedArticles = articleChanges.updatedArticles, !updatedArticles.isEmpty { + shouldSendNotification = true + userInfo[UserInfoKey.updatedArticles] = updatedArticles + } + + if let deletedArticles = articleChanges.deletedArticles, !deletedArticles.isEmpty { + shouldUpdateUnreadCounts = true + } + + if shouldUpdateUnreadCounts { self.updateUnreadCounts(for: webFeeds) { NotificationCenter.default.post(name: .DownloadArticlesDidUpdateUnreadCounts, object: self, userInfo: nil) } } - if let updatedArticles = newAndUpdatedArticles.updatedArticles, !updatedArticles.isEmpty { - shouldSendNotification = true - userInfo[UserInfoKey.updatedArticles] = updatedArticles - } - if shouldSendNotification { userInfo[UserInfoKey.webFeeds] = webFeeds NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo) diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 0ee215866..289ce96c0 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -24,12 +24,13 @@ public typealias UnreadCountDictionaryCompletionBlock = (UnreadCountDictionaryCo public typealias SingleUnreadCountResult = Result public typealias SingleUnreadCountCompletionBlock = (SingleUnreadCountResult) -> Void -public struct NewAndUpdatedArticles { +public struct ArticleChanges { public let newArticles: Set
? public let updatedArticles: Set
? + public let deletedArticles: Set
? } -public typealias UpdateArticlesResult = Result +public typealias UpdateArticlesResult = Result public typealias UpdateArticlesCompletionBlock = (UpdateArticlesResult) -> Void public typealias ArticleSetResult = Result, DatabaseError> diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index ff36a8f9b..db997afb6 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -175,7 +175,7 @@ final class ArticlesTable: DatabaseTable { func update(_ parsedItems: Set, _ webFeedID: String, _ completion: @escaping UpdateArticlesCompletionBlock) { precondition(retentionStyle == .feedBased) if parsedItems.isEmpty { - callUpdateArticlesCompletionBlock(nil, nil, completion) + callUpdateArticlesCompletionBlock(nil, nil, nil, completion) return } @@ -199,7 +199,7 @@ final class ArticlesTable: DatabaseTable { let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2 if incomingArticles.isEmpty { - self.callUpdateArticlesCompletionBlock(nil, nil, completion) + self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion) return } @@ -209,13 +209,19 @@ final class ArticlesTable: DatabaseTable { let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5 let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6 - self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7 + // Articles to delete are 1) no longer in feed and 2) older than 30 days. + let cutoffDate = Date().bySubtracting(days: 30) + let articlesToDelete = fetchedArticles.filter { (article) -> Bool in + return article.status.dateArrived < cutoffDate && !articleIDs.contains(article.articleID) + } + + self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, articlesToDelete, completion) //7 self.addArticlesToCache(newArticles) self.addArticlesToCache(updatedArticles) // 8. Delete articles no longer in feed. - let articleIDsToDelete = fetchedArticles.articleIDs().filter { !(articleIDs.contains($0)) } + let articleIDsToDelete = articlesToDelete.articleIDs() if !articleIDsToDelete.isEmpty { self.removeArticles(articleIDsToDelete, database) self.removeArticleIDsFromCache(articleIDsToDelete) @@ -244,7 +250,7 @@ final class ArticlesTable: DatabaseTable { func update(_ webFeedIDsAndItems: [String: Set], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { precondition(retentionStyle == .syncSystem) if webFeedIDsAndItems.isEmpty { - callUpdateArticlesCompletionBlock(nil, nil, completion) + callUpdateArticlesCompletionBlock(nil, nil, nil, completion) return } @@ -270,13 +276,13 @@ final class ArticlesTable: DatabaseTable { let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2 if allIncomingArticles.isEmpty { - self.callUpdateArticlesCompletionBlock(nil, nil, completion) + self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion) return } let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3 if incomingArticles.isEmpty { - self.callUpdateArticlesCompletionBlock(nil, nil, completion) + self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion) return } @@ -287,7 +293,7 @@ final class ArticlesTable: DatabaseTable { let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5 let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6 - self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7 + self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, nil, completion) //7 self.addArticlesToCache(newArticles) self.addArticlesToCache(updatedArticles) @@ -849,10 +855,10 @@ private extension ArticlesTable { // MARK: - Saving Parsed Items - func callUpdateArticlesCompletionBlock(_ newArticles: Set
?, _ updatedArticles: Set
?, _ completion: @escaping UpdateArticlesCompletionBlock) { - let newAndUpdatedArticles = NewAndUpdatedArticles(newArticles: newArticles, updatedArticles: updatedArticles) + func callUpdateArticlesCompletionBlock(_ newArticles: Set
?, _ updatedArticles: Set
?, _ deletedArticles: Set
?, _ completion: @escaping UpdateArticlesCompletionBlock) { + let articleChanges = ArticleChanges(newArticles: newArticles, updatedArticles: updatedArticles, deletedArticles: deletedArticles) DispatchQueue.main.async { - completion(.success(newAndUpdatedArticles)) + completion(.success(articleChanges)) } } From a7ca5bfa79858fde95069b6c779d7ab23ce33fca Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 23 Apr 2020 16:39:09 -0500 Subject: [PATCH 4/4] Delete old articles and article statuses from CloudKit --- .../CloudKit/CloudKitAccountDelegate.swift | 9 +++++---- .../Account/CloudKit/CloudKitArticlesZone.swift | 15 +++++++++++++++ Frameworks/Account/CloudKit/CloudKitZone.swift | 4 ++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index 2c8708079..f352c35cb 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -628,12 +628,13 @@ private extension CloudKitAccountDelegate { extension CloudKitAccountDelegate: LocalAccountRefresherDelegate { func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) { - if let newArticles = articleChanges.newArticles { - articlesZone.sendNewArticles(newArticles) { _ in + let newArticles = articleChanges.newArticles ?? Set
() + let deletedArticles = articleChanges.deletedArticles ?? Set
() + + articlesZone.deleteArticles(deletedArticles) { _ in + self.articlesZone.sendNewArticles(newArticles) { _ in completion() } - } else { - completion() } } diff --git a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift index 1f71d3c46..36eddda80 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift @@ -82,10 +82,25 @@ final class CloudKitArticlesZone: CloudKitZone { } func sendNewArticles(_ articles: Set
, completion: @escaping ((Result) -> Void)) { + guard !articles.isEmpty else { + completion(.success(())) + return + } + let records = makeNewStatusRecords(articles) saveIfNew(records, completion: completion) } + func deleteArticles(_ articles: Set
, completion: @escaping ((Result) -> Void)) { + guard !articles.isEmpty else { + completion(.success(())) + return + } + + let recordIDs = articles.map { CKRecord.ID(recordName: $0.articleID, zoneID: Self.zoneID) } + delete(recordIDs: recordIDs, completion: completion) + } + func sendArticleStatus(_ syncStatuses: [SyncStatus], articles: Set
, completion: @escaping ((Result) -> Void)) { var records = makeStatusRecords(syncStatuses, articles) diff --git a/Frameworks/Account/CloudKit/CloudKitZone.swift b/Frameworks/Account/CloudKit/CloudKitZone.swift index 3bd02c1ee..e0cc2c891 100644 --- a/Frameworks/Account/CloudKit/CloudKitZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitZone.swift @@ -371,6 +371,10 @@ extension CloudKitZone { modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion) } + func delete(recordIDs: [CKRecord.ID], completion: @escaping (Result) -> Void) { + modify(recordsToSave: [], recordIDsToDelete: recordIDs, completion: completion) + } + /// Delete a CKRecord using its externalID func delete(externalID: String?, completion: @escaping (Result) -> Void) { guard let externalID = externalID else {