diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index d673103ed..7bbf644a7 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -735,7 +735,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, webFeed.takeSettings(from: parsedFeed) let parsedItems = parsedFeed.items guard !parsedItems.isEmpty else { - completion(.success(NewAndUpdatedArticles())) + completion(.success(ArticleChanges())) return } @@ -1302,30 +1302,39 @@ 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 - self.updateUnreadCounts(for: webFeeds) } - if let updatedArticles = newAndUpdatedArticles.updatedArticles, !updatedArticles.isEmpty { + 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) + } + if shouldSendNotification { userInfo[UserInfoKey.webFeeds] = webFeeds NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index c0488a0eb..2c8708079 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -627,8 +627,8 @@ private extension CloudKitAccountDelegate { extension CloudKitAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) { - if let newArticles = newAndUpdatedArticles.newArticles { + func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) { + if let newArticles = articleChanges.newArticles { articlesZone.sendNewArticles(newArticles) { _ in completion() } diff --git a/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift index 3cc535d18..ab7cb23ce 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift @@ -202,7 +202,7 @@ private extension CloudKitArticlesZoneDelegate { extension CloudKitArticlesZoneDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) { + func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) { completion() } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 70941c911..bbe13374f 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -234,7 +234,7 @@ final class LocalAccountDelegate: AccountDelegate { extension LocalAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) { + func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) { completion() } diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift index 7a5873f07..5695b809a 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift @@ -14,7 +14,7 @@ import Articles import ArticlesDatabase protocol LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess: NewAndUpdatedArticles, completion: @escaping () -> Void) + func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess: ArticleChanges, completion: @escaping () -> Void) func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 51e382578..04e3e915b 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -24,22 +24,26 @@ 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 init() { self.newArticles = Set
() self.updatedArticles = Set
() + self.deletedArticles = Set
() } - public init(newArticles: Set
?, updatedArticles: Set
?) { + public init(newArticles: Set
?, updatedArticles: Set
?, deletedArticles: Set
?) { self.newArticles = newArticles self.updatedArticles = updatedArticles + self.deletedArticles = deletedArticles } + } -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 e69a9def7..dc67a779f 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)) } } 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) } } 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