diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 7834f8252..484adda15 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -64,6 +64,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles public static let statuses = "statuses" // StatusesDidChange public static let articles = "articles" // StatusesDidChange + public static let articleIDs = "articleIDs" // StatusesDidChange public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange } @@ -776,16 +777,41 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, database.ensureStatuses(articleIDs, defaultRead, statusKey, flag, completion: completion) } - /// Update statuses — set a key and value. This updates the database, and sends a .StatusesDidChange notification. - func update(statuses: Set, statusKey: ArticleStatus.Key, flag: Bool) { - // TODO: https://github.com/brentsimmons/NetNewsWire/issues/1420 + /// Mark articleIDs statuses based on statusKey and flag. + /// Will create statuses in the database and in memory as needed. Sends a .StatusesDidChange notification. + func mark(articleIDs: Set, statusKey: ArticleStatus.Key, flag: Bool, completion: DatabaseCompletionBlock? = nil) { + guard !articleIDs.isEmpty else { + completion?(nil) + return + } + database.mark(articleIDs: articleIDs, statusKey: statusKey, flag: flag) { error in + if let error = error { + completion?(error) + return + } + self.noteStatusesForArticleIDsDidChange(articleIDs) + completion?(nil) + } } - /// Update statuses specified by articleIDs — set a key and value. - /// This updates the database, and sends a .StatusesDidChange notification. - /// Any statuses that don’t exist will be automatically created. - func mark(articleIDs: Set, statusKey: ArticleStatus.Key, flag: Bool, completion: DatabaseCompletionBlock? = nil) { - // TODO + /// Mark articleIDs as read. Will create statuses in the database and in memory as needed. Sends a .StatusesDidChange notification. + func markAsRead(_ articleIDs: Set) { + mark(articleIDs: articleIDs, statusKey: .read, flag: true) + } + + /// Mark articleIDs as unread. Will create statuses in the database and in memory as needed. Sends a .StatusesDidChange notification. + func markAsUnread(_ articleIDs: Set) { + mark(articleIDs: articleIDs, statusKey: .read, flag: false) + } + + /// Mark articleIDs as starred. Will create statuses in the database and in memory as needed. Sends a .StatusesDidChange notification. + func markAsStarred(_ articleIDs: Set) { + mark(articleIDs: articleIDs, statusKey: .starred, flag: true) + } + + /// Mark articleIDs as unstarred. Will create statuses in the database and in memory as needed. Sends a .StatusesDidChange notification. + func markAsUnstarred(_ articleIDs: Set) { + mark(articleIDs: articleIDs, statusKey: .starred, flag: false) } /// Fetch statuses for the specified articleIDs. The completion handler will get nil if the app is suspended. @@ -1158,14 +1184,20 @@ private extension Account { func noteStatusesForArticlesDidChange(_ articles: Set
) { let feeds = Set(articles.compactMap { $0.webFeed }) let statuses = Set(articles.map { $0.status }) - + let articleIDs = Set(articles.map { $0.articleID }) + // .UnreadCountDidChange notification will get sent to Folder and Account objects, // which will update their own unread counts. updateUnreadCounts(for: feeds) - NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.webFeeds: feeds]) + NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.articleIDs: articleIDs, UserInfoKey.webFeeds: feeds]) } + func noteStatusesForArticleIDsDidChange(_ articleIDs: Set) { + fetchAllUnreadCounts() + NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.articleIDs: articleIDs]) + } + func fetchAllUnreadCounts() { fetchingAllUnreadCounts = true diff --git a/Frameworks/Account/ArticleFetcher.swift b/Frameworks/Account/ArticleFetcher.swift index 09d5d63a6..308cb155e 100644 --- a/Frameworks/Account/ArticleFetcher.swift +++ b/Frameworks/Account/ArticleFetcher.swift @@ -8,13 +8,14 @@ import Foundation import Articles +import ArticlesDatabase public protocol ArticleFetcher { - func fetchArticles() -> Set
- func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) - func fetchUnreadArticles() -> Set
- func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) + func fetchArticles() throws -> Set
+ func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) + func fetchUnreadArticles() throws -> Set
+ func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) } extension WebFeed: ArticleFetcher { @@ -23,26 +24,33 @@ extension WebFeed: ArticleFetcher { return try account?.fetchArticles(.webFeed(self)) ?? Set
() } - public func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { guard let account = account else { assertionFailure("Expected feed.account, but got nil.") - completion(Set
()) + completion(.success(Set
())) return } account.fetchArticlesAsync(.webFeed(self), completion) } - public func fetchUnreadArticles() -> Set
{ - return fetchArticles().unreadArticles() + public func fetchUnreadArticles() throws -> Set
{ + return try fetchArticles().unreadArticles() } - public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { guard let account = account else { assertionFailure("Expected feed.account, but got nil.") - completion(Set
()) + completion(.success(Set
())) return } - account.fetchArticlesAsync(.webFeed(self)) { completion($0.unreadArticles()) } + account.fetchArticlesAsync(.webFeed(self)) { articleSetResult in + switch articleSetResult { + case .success(let articles): + completion(.success(articles.unreadArticles())) + case .failure(let error): + completion(.failure(error)) + } + } } } @@ -56,10 +64,10 @@ extension Folder: ArticleFetcher { return try account.fetchArticles(.folder(self, false)) } - public func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { guard let account = account else { assertionFailure("Expected folder.account, but got nil.") - completion(Set
()) + completion(.success(Set
())) return } account.fetchArticlesAsync(.folder(self, false), completion) @@ -73,10 +81,10 @@ extension Folder: ArticleFetcher { return try account.fetchArticles(.folder(self, true)) } - public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { guard let account = account else { assertionFailure("Expected folder.account, but got nil.") - completion(Set
()) + completion(.success(Set
())) return } account.fetchArticlesAsync(.folder(self, true), completion) diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift index 09b88cd12..905fb4f44 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift @@ -165,10 +165,13 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } func refreshMissingArticles(for account: Account, completion: @escaping ((Result)-> Void)) { + guard let fetchedArticleIDs = try? account.fetchArticleIDsForStatusesWithoutArticles() else { + return + } + os_log(.debug, log: log, "Refreshing missing articles...") let group = DispatchGroup() - let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticles() let articleIDs = Array(fetchedArticleIDs) let chunkedArticleIDs = articleIDs.chunked(into: 100) @@ -428,7 +431,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - return account.update(articles, statusKey: statusKey, flag: flag) + return try? account.update(articles, statusKey: statusKey, flag: flag) } func accountDidInitialize(_ account: Account) { @@ -495,7 +498,7 @@ private extension FeedWranglerAccountDelegate { } } - func syncFeedItems(_ account: Account, _ feedItems: [FeedWranglerFeedItem], completion: @escaping (() -> Void)) { + func syncFeedItems(_ account: Account, _ feedItems: [FeedWranglerFeedItem], completion: @escaping VoidCompletionBlock) { let parsedItems = feedItems.map { (item: FeedWranglerFeedItem) -> ParsedItem in let itemID = String(item.feedItemID) // let authors = ... @@ -505,50 +508,35 @@ private extension FeedWranglerAccountDelegate { } let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { $0.feedURL }).mapValues { Set($0) } - account.update(webFeedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion) + account.update(webFeedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in + completion() + } } func syncArticleReadState(_ account: Account, _ unreadFeedItems: [FeedWranglerFeedItem]) { let unreadServerItemIDs = Set(unreadFeedItems.map { String($0.feedItemID) }) - account.fetchUnreadArticleIDs { unreadLocalItemIDs in - // unread if unread on server - let unreadDiffItemIDs = unreadServerItemIDs.subtracting(unreadLocalItemIDs) - let unreadFoundArticles = account.fetchArticles(.articleIDs(unreadDiffItemIDs)) - account.update(unreadFoundArticles, statusKey: .read, flag: false) - - let unreadFoundItemIDs = Set(unreadFoundArticles.map { $0.articleID }) - let missingArticleIDs = unreadDiffItemIDs.subtracting(unreadFoundItemIDs) - account.ensureStatuses(missingArticleIDs, true, .read, false) + account.fetchUnreadArticleIDs { articleIDsResult in + guard let unreadLocalItemIDs = try? articleIDsResult.get() else { + return + } + account.markAsUnread(unreadServerItemIDs) let readItemIDs = unreadLocalItemIDs.subtracting(unreadServerItemIDs) - let readArtices = account.fetchArticles(.articleIDs(readItemIDs)) - account.update(readArtices, statusKey: .read, flag: true) - - let foundReadArticleIDs = Set(readArtices.map { $0.articleID }) - let readMissingIDs = readItemIDs.subtracting(foundReadArticleIDs) - account.ensureStatuses(readMissingIDs, true, .read, true) + account.markAsRead(readItemIDs) } } - func syncArticleStarredState(_ account: Account, _ unreadFeedItems: [FeedWranglerFeedItem]) { - let unreadServerItemIDs = Set(unreadFeedItems.map { String($0.feedItemID) }) - account.fetchUnreadArticleIDs { unreadLocalItemIDs in - // starred if start on server - let unreadDiffItemIDs = unreadServerItemIDs.subtracting(unreadLocalItemIDs) - let unreadFoundArticles = account.fetchArticles(.articleIDs(unreadDiffItemIDs)) - account.update(unreadFoundArticles, statusKey: .starred, flag: true) + func syncArticleStarredState(_ account: Account, _ starredFeedItems: [FeedWranglerFeedItem]) { + let starredServerItemIDs = Set(starredFeedItems.map { String($0.feedItemID) }) + account.fetchStarredArticleIDs { articleIDsResult in + guard let starredLocalItemIDs = try? articleIDsResult.get() else { + return + } - let unreadFoundItemIDs = Set(unreadFoundArticles.map { $0.articleID }) - let missingArticleIDs = unreadDiffItemIDs.subtracting(unreadFoundItemIDs) - account.ensureStatuses(missingArticleIDs, true, .starred, true) + account.markAsStarred(starredServerItemIDs) - let readItemIDs = unreadLocalItemIDs.subtracting(unreadServerItemIDs) - let readArtices = account.fetchArticles(.articleIDs(readItemIDs)) - account.update(readArtices, statusKey: .starred, flag: false) - - let foundReadArticleIDs = Set(readArtices.map { $0.articleID }) - let readMissingIDs = readItemIDs.subtracting(foundReadArticleIDs) - account.ensureStatuses(readMissingIDs, true, .starred, false) + let unstarredItemIDs = starredLocalItemIDs.subtracting(starredServerItemIDs) + account.markAsUnstarred(unstarredItemIDs) } } diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 1d7279a08..24731aa1f 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -8,6 +8,7 @@ import Articles import RSCore +import RSDatabase import RSParser import RSWeb import SyncDatabase @@ -541,7 +542,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } } - return account.update(articles, statusKey: statusKey, flag: flag) + return try? account.update(articles, statusKey: statusKey, flag: flag) } func accountDidInitialize(_ account: Account) { @@ -1035,7 +1036,12 @@ private extension FeedbinAccountDelegate { switch result { case .success(let (entries, page)): - self.processEntries(account: account, entries: entries) { + self.processEntries(account: account, entries: entries) { error in + if let error = error { + completion(.failure(error)) + return + } + self.refreshArticleStatus(for: account) { result in switch result { case .success: @@ -1080,7 +1086,7 @@ private extension FeedbinAccountDelegate { } - func refreshArticles(_ account: Account, completion: @escaping ((Result) -> Void)) { + func refreshArticles(_ account: Account, completion: @escaping VoidResultCompletionBlock) { os_log(.debug, log: log, "Refreshing articles...") @@ -1093,9 +1099,15 @@ private extension FeedbinAccountDelegate { self.refreshProgress.addToNumberOfTasksAndRemaining(last - 1) } - self.processEntries(account: account, entries: entries) { + self.processEntries(account: account, entries: entries) { error in self.refreshProgress.completeTask() + + if let error = error { + completion(.failure(error)) + return + } + self.refreshArticles(account, page: page, updateFetchDate: updateFetchDate) { result in os_log(.debug, log: self.log, "Done refreshing articles.") switch result { @@ -1105,23 +1117,29 @@ private extension FeedbinAccountDelegate { completion(.failure(error)) } } - } case .failure(let error): completion(.failure(error)) } - } - } func refreshMissingArticles(_ account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing missing articles...") let group = DispatchGroup() var errorOccurred = false + var fetchedArticleIDs = Set() + + do { + fetchedArticleIDs = try account.fetchArticleIDsForStatusesWithoutArticles() + } + catch(let error) { + self.refreshProgress.completeTask() + completion(.failure(error)) + return + } - let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticles() let articleIDs = Array(fetchedArticleIDs) let chunkedArticleIDs = articleIDs.chunked(into: 100) @@ -1132,7 +1150,7 @@ private extension FeedbinAccountDelegate { switch result { case .success(let entries): - self.processEntries(account: account, entries: entries) { + self.processEntries(account: account, entries: entries) { _ in group.leave() } @@ -1170,7 +1188,7 @@ private extension FeedbinAccountDelegate { switch result { case .success(let (entries, nextPage)): - self.processEntries(account: account, entries: entries) { + self.processEntries(account: account, entries: entries) { _ in self.refreshProgress.completeTask() self.refreshArticles(account, page: nextPage, updateFetchDate: updateFetchDate, completion: completion) } @@ -1181,7 +1199,7 @@ private extension FeedbinAccountDelegate { } } - func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) { + func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping DatabaseCompletionBlock) { let parsedItems = mapEntriesToParsedItems(entries: entries) let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion) @@ -1207,26 +1225,18 @@ private extension FeedbinAccountDelegate { } let feedbinUnreadArticleIDs = Set(articleIDs.map { String($0) } ) - account.fetchUnreadArticleIDs { currentUnreadArticleIDs in + account.fetchUnreadArticleIDs { articleIDsResult in + guard let currentUnreadArticleIDs = try? articleIDsResult.get() else { + return + } + // Mark articles as unread let deltaUnreadArticleIDs = feedbinUnreadArticleIDs.subtracting(currentUnreadArticleIDs) - let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs)) - account.update(markUnreadArticles, statusKey: .read, flag: false) - - // Save any unread statuses for articles we haven't yet received - let markUnreadArticleIDs = Set(markUnreadArticles.map { $0.articleID }) - let missingUnreadArticleIDs = deltaUnreadArticleIDs.subtracting(markUnreadArticleIDs) - account.ensureStatuses(missingUnreadArticleIDs, true, .read, false) + account.markAsUnread(deltaUnreadArticleIDs) // Mark articles as read let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) - let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs)) - account.update(markReadArticles, statusKey: .read, flag: true) - - // Save any read statuses for articles we haven't yet received - let markReadArticleIDs = Set(markReadArticles.map { $0.articleID }) - let missingReadArticleIDs = deltaReadArticleIDs.subtracting(markReadArticleIDs) - account.ensureStatuses(missingReadArticleIDs, true, .read, true) + account.markAsRead(deltaReadArticleIDs) } } @@ -1236,26 +1246,18 @@ private extension FeedbinAccountDelegate { } let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } ) - account.fetchStarredArticleIDs { currentStarredArticleIDs in + account.fetchStarredArticleIDs { articleIDsResult in + guard let currentStarredArticleIDs = try? articleIDsResult.get() else { + return + } + // Mark articles as starred let deltaStarredArticleIDs = feedbinStarredArticleIDs.subtracting(currentStarredArticleIDs) - let markStarredArticles = account.fetchArticles(.articleIDs(deltaStarredArticleIDs)) - account.update(markStarredArticles, statusKey: .starred, flag: true) - - // Save any starred statuses for articles we haven't yet received - let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID }) - let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs) - account.ensureStatuses(missingStarredArticleIDs, true, .starred, true) + account.markAsStarred(deltaStarredArticleIDs) // Mark articles as unstarred let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) - let markUnstarredArticles = account.fetchArticles(.articleIDs(deltaUnstarredArticleIDs)) - account.update(markUnstarredArticles, statusKey: .starred, flag: false) - - // Save any unstarred statuses for articles we haven't yet received - let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID }) - let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs) - account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false) + account.markAsUnstarred(deltaUnstarredArticleIDs) } } diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 9d7cdc2eb..98af63ca2 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -14,7 +14,7 @@ import SyncDatabase import os.log final class FeedlyAccountDelegate: AccountDelegate { - + /// Feedly has a sandbox API and a production API. /// This property is referred to when clients need to know which environment it should be pointing to. /// The value of this proptery must match any `OAuthAuthorizationClient` used. @@ -476,7 +476,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) throws -> Set
? { + func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { let syncStatuses = articles.map { article in return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag) @@ -491,7 +491,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - return try account.update(articles, statusKey: statusKey, flag: flag) + return try? account.update(articles, statusKey: statusKey, flag: flag) } func accountDidInitialize(_ account: Account) { diff --git a/Frameworks/Account/Feedly/Operations/FeedlySetUnreadArticlesOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySetUnreadArticlesOperation.swift index 97e745214..6ec12bf4c 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySetUnreadArticlesOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySetUnreadArticlesOperation.swift @@ -52,13 +52,10 @@ private extension FeedlySetUnreadArticlesOperation { } let remoteUnreadArticleIDs = allUnreadIdsProvider.entryIds + account.markAsUnread(remoteUnreadArticleIDs) - // Mark articles as unread - account.mark(articleIDs: remoteUnreadArticleIDs, statusKey: .read, flag: false) - - // Mark articles as read let articleIDsToMarkRead = localUnreadArticleIDs.subtracting(remoteUnreadArticleIDs) - account.mark(articleIDs: articleIDsToMarkRead, statusKey: .read, flag: true) + account.markAsRead(articleIDsToMarkRead) didFinish() } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 565dcd695..4fcec7b45 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -118,7 +118,7 @@ final class LocalAccountDelegate: AccountDelegate { self.refreshProgress.completeTask() if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed, {}) + account.update(feed, with: parsedFeed, {_ in}) } feed.editedName = name @@ -187,7 +187,7 @@ final class LocalAccountDelegate: AccountDelegate { } func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { - return try account.update(articles, statusKey: statusKey, flag: flag) + return try? account.update(articles, statusKey: statusKey, flag: flag) } func accountDidInitialize(_ account: Account) { diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift index 81ff9893b..2b7f198e2 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift @@ -84,12 +84,14 @@ extension LocalAccountRefresher: DownloadSessionDelegate { guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else { return } - account.update(feed, with: parsedFeed) { - if let httpResponse = response as? HTTPURLResponse { - feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) + account.update(feed, with: parsedFeed) { error in + if error == nil { + if let httpResponse = response as? HTTPURLResponse { + feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) + } + + feed.contentHash = dataHash } - - feed.contentHash = dataHash completion() } } diff --git a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 2767e85eb..6de5ed187 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -417,7 +417,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - return account.update(articles, statusKey: statusKey, flag: flag) + return try? account.update(articles, statusKey: statusKey, flag: flag) } @@ -836,12 +836,15 @@ private extension ReaderAPIAccountDelegate { } - func refreshMissingArticles(_ account: Account, completion: @escaping (() -> Void)) { - + func refreshMissingArticles(_ account: Account, completion: @escaping VoidCompletionBlock) { + guard let fetchedArticleIDs = try? account.fetchArticleIDsForStatusesWithoutArticles() else { + self.refreshProgress.completeTask() + return + } + os_log(.debug, log: log, "Refreshing missing articles...") let group = DispatchGroup() - let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticles() let articleIDs = Array(fetchedArticleIDs) let chunkedArticleIDs = articleIDs.chunked(into: 100) @@ -895,10 +898,12 @@ private extension ReaderAPIAccountDelegate { } - func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping (() -> Void)) { + func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping VoidCompletionBlock) { let parsedItems = mapEntriesToParsedItems(account: account, entries: entries) let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } - account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion) + account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { _ in + completion() + } } func mapEntriesToParsedItems(account: Account, entries: [ReaderAPIEntry]?) -> Set { @@ -924,26 +929,18 @@ private extension ReaderAPIAccountDelegate { } let feedbinUnreadArticleIDs = Set(articleIDs.map { String($0) } ) - account.fetchUnreadArticleIDs { currentUnreadArticleIDs in + account.fetchUnreadArticleIDs { articleIDsResult in + guard let currentUnreadArticleIDs = try? articleIDsResult.get() else { + return + } + // Mark articles as unread let deltaUnreadArticleIDs = feedbinUnreadArticleIDs.subtracting(currentUnreadArticleIDs) - let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs)) - account.update(markUnreadArticles, statusKey: .read, flag: false) - - // Save any unread statuses for articles we haven't yet received - let markUnreadArticleIDs = Set(markUnreadArticles.map { $0.articleID }) - let missingUnreadArticleIDs = deltaUnreadArticleIDs.subtracting(markUnreadArticleIDs) - account.ensureStatuses(missingUnreadArticleIDs, true, .read, false) + account.markAsUnread(deltaUnreadArticleIDs) // Mark articles as read let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) - let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs)) - account.update(markReadArticles, statusKey: .read, flag: true) - - // Save any read statuses for articles we haven't yet received - let markReadArticleIDs = Set(markReadArticles.map { $0.articleID }) - let missingReadArticleIDs = deltaReadArticleIDs.subtracting(markReadArticleIDs) - account.ensureStatuses(missingReadArticleIDs, true, .read, true) + account.markAsRead(deltaReadArticleIDs) } } @@ -953,26 +950,18 @@ private extension ReaderAPIAccountDelegate { } let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } ) - account.fetchStarredArticleIDs { currentStarredArticleIDs in + account.fetchStarredArticleIDs { articleIDsResult in + guard let currentStarredArticleIDs = try? articleIDsResult.get() else { + return + } + // Mark articles as starred let deltaStarredArticleIDs = feedbinStarredArticleIDs.subtracting(currentStarredArticleIDs) - let markStarredArticles = account.fetchArticles(.articleIDs(deltaStarredArticleIDs)) - account.update(markStarredArticles, statusKey: .starred, flag: true) - - // Save any starred statuses for articles we haven't yet received - let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID }) - let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs) - account.ensureStatuses(missingStarredArticleIDs, true, .starred, true) + account.markAsStarred(deltaStarredArticleIDs) // Mark articles as unstarred let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) - let markUnstarredArticles = account.fetchArticles(.articleIDs(deltaUnstarredArticleIDs)) - account.update(markUnstarredArticles, statusKey: .starred, flag: false) - - // Save any unstarred statuses for articles we haven't yet received - let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID }) - let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs) - account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false) + account.markAsUnstarred(deltaUnstarredArticleIDs) } } diff --git a/Frameworks/Account/SingleArticleFetcher.swift b/Frameworks/Account/SingleArticleFetcher.swift index d600c52b5..f558fd5fa 100644 --- a/Frameworks/Account/SingleArticleFetcher.swift +++ b/Frameworks/Account/SingleArticleFetcher.swift @@ -8,6 +8,7 @@ import Foundation import Articles +import ArticlesDatabase public struct SingleArticleFetcher: ArticleFetcher { @@ -19,19 +20,19 @@ public struct SingleArticleFetcher: ArticleFetcher { self.articleID = articleID } - public func fetchArticles() -> Set
{ - return account.fetchArticles(.articleIDs(Set([articleID]))) + public func fetchArticles() throws -> Set
{ + return try account.fetchArticles(.articleIDs(Set([articleID]))) } - public func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { return account.fetchArticlesAsync(.articleIDs(Set([articleID])), completion) } - public func fetchUnreadArticles() -> Set
{ - return account.fetchArticles(.articleIDs(Set([articleID]))) + public func fetchUnreadArticles() throws -> Set
{ + return try account.fetchArticles(.articleIDs(Set([articleID]))) } - public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { + public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { return account.fetchArticlesAsync(.articleIDs(Set([articleID])), completion) } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index c64368c57..b4c40f4c1 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -188,6 +188,10 @@ public final class ArticlesDatabase { return try articlesTable.mark(articles, statusKey, flag) } + public func mark(articleIDs: Set, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping DatabaseCompletionBlock) { + articlesTable.mark(articleIDs, statusKey, flag, completion) + } + public func fetchStatuses(articleIDs: Set, createIfNeeded: Bool, completion: @escaping ArticleStatusesResultBlock) { articlesTable.fetchStatuses(articleIDs, createIfNeeded, completion) } diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 5ffea6889..be011dd22 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -457,6 +457,18 @@ final class ArticlesTable: DatabaseTable { return statuses } + func mark(_ articleIDs: Set, _ statusKey: ArticleStatus.Key, _ flag: Bool, _ completion: @escaping DatabaseCompletionBlock) { + queue.runInTransaction { databaseResult in + switch databaseResult { + case .success(let database): + self.statusesTable.mark(articleIDs, statusKey, flag, database) + completion(nil) + case .failure(let databaseError): + completion(databaseError) + } + } + } + // MARK: - Indexing func indexUnindexedArticles() { diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index bbe1deda5..3bf1a109a 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -85,6 +85,12 @@ final class StatusesTable: DatabaseTable { return updatedStatuses } + func mark(_ articleIDs: Set, _ statusKey: ArticleStatus.Key, _ flag: Bool, _ database: FMDatabase) { + let statusesDictionary = ensureStatusesForArticleIDs(articleIDs, flag, database) + let statuses = Set(statusesDictionary.values) + mark(statuses, statusKey, flag, database) + } + // MARK: - Fetching func fetchUnreadArticleIDs() throws -> Set { diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 2e1b24f57..0059e00dd 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -261,7 +261,9 @@ private extension SidebarViewController { var articles = Set
() for object in objects { if let articleFetcher = object as? ArticleFetcher { - articles.formUnion(articleFetcher.fetchUnreadArticles()) + if let unreadArticles = try? articleFetcher.fetchUnreadArticles() { + articles.formUnion(unreadArticles) + } } } return articles diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 4b5107b8a..40fd9f29b 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -254,12 +254,14 @@ private extension TimelineViewController { } func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? { - - let articles = Array(feed.fetchArticles()) + guard let articlesSet = try? feed.fetchArticles() else { + return nil + } + let articles = Array(articlesSet) guard articles.canMarkAllAsRead() else { return nil } - + let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let menuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 1ffdfa86a..bb79897f9 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -450,10 +450,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Notifications @objc func statusesDidChange(_ note: Notification) { - guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set
else { + guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set else { return } - reloadVisibleCells(for: articles) + reloadVisibleCells(for: articleIDs) updateUnreadCount() } @@ -1019,9 +1019,13 @@ private extension TimelineViewController { var fetchedArticles = Set
() for articleFetcher in articleFetchers { if articleReadFilterType != ReadFilterType.none { - fetchedArticles.formUnion(articleFetcher.fetchUnreadArticles()) + if let articles = try? articleFetcher.fetchUnreadArticles() { + fetchedArticles.formUnion(articles) + } } else { - fetchedArticles.formUnion(articleFetcher.fetchArticles()) + if let articles = try? articleFetcher.fetchArticles() { + fetchedArticles.formUnion(articles) + } } } return fetchedArticles diff --git a/Mac/Scriptability/WebFeed+Scriptability.swift b/Mac/Scriptability/WebFeed+Scriptability.swift index 47f701901..6c20c3dea 100644 --- a/Mac/Scriptability/WebFeed+Scriptability.swift +++ b/Mac/Scriptability/WebFeed+Scriptability.swift @@ -165,7 +165,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta @objc(articles) var articles:NSArray { - let feedArticles = webFeed.fetchArticles() + let feedArticles = (try? webFeed.fetchArticles()) ?? Set
() // the articles are a set, use the sorting algorithm from the viewer let sortedArticles = feedArticles.sorted(by:{ return $0.logicalDatePublished > $1.logicalDatePublished @@ -175,7 +175,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta @objc(valueInArticlesWithUniqueID:) func valueInArticles(withUniqueID id:String) -> ScriptableArticle? { - let articles = webFeed.fetchArticles() + let articles = (try? webFeed.fetchArticles()) ?? Set
() guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil } return ScriptableArticle(article, container:self) } diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 418aeb887..300aaa255 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -287,12 +287,12 @@ private extension ActivityManager { static func identifers(for feed: WebFeed) -> [String] { var ids = [String]() ids.append(identifer(for: feed)) - - for article in feed.fetchArticles() { - ids.append(identifer(for: article)) + if let articles = try? feed.fetchArticles() { + for article in articles { + ids.append(identifer(for: article)) + } } - + return ids } - } diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index dbeac37bd..246ccca75 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -10,6 +10,7 @@ import Foundation import RSCore import Account import Articles +import ArticlesDatabase struct SearchFeedDelegate: SmartFeedDelegate { @@ -31,7 +32,7 @@ struct SearchFeedDelegate: SmartFeedDelegate { self.fetchType = .search(searchString) } - func fetchUnreadCount(for: Account, completion: @escaping (Int) -> Void) { + func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) { // TODO: after 5.0 } } diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index f67a8b397..c186fa838 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -10,6 +10,7 @@ import Foundation import RSCore import Account import Articles +import ArticlesDatabase struct SearchTimelineFeedDelegate: SmartFeedDelegate { @@ -31,7 +32,7 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate { self.fetchType = .searchWithArticleIDs(searchString, articleIDs) } - func fetchUnreadCount(for: Account, completion: @escaping (Int) -> Void) { + func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) { // TODO: after 5.0 } } diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index 75090bbff..e8aa2f1bc 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -9,6 +9,7 @@ import Foundation import RSCore import Articles +import ArticlesDatabase import Account final class SmartFeed: PseudoFeed { @@ -80,19 +81,19 @@ final class SmartFeed: PseudoFeed { extension SmartFeed: ArticleFetcher { - func fetchArticles() -> Set
{ - return delegate.fetchArticles() + func fetchArticles() throws -> Set
{ + return try delegate.fetchArticles() } - func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { delegate.fetchArticlesAsync(completion) } - func fetchUnreadArticles() -> Set
{ - return delegate.fetchUnreadArticles() + func fetchUnreadArticles() throws -> Set
{ + return try delegate.fetchUnreadArticles() } - func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { + func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { delegate.fetchUnreadArticlesAsync(completion) } } @@ -104,7 +105,10 @@ private extension SmartFeed { } func fetchUnreadCount(for account: Account) { - delegate.fetchUnreadCount(for: account) { (accountUnreadCount) in + delegate.fetchUnreadCount(for: account) { singleUnreadCountResult in + guard let accountUnreadCount = try? singleUnreadCountResult.get() else { + return + } self.unreadCounts[account.accountID] = accountUnreadCount self.updateUnreadCount() } diff --git a/Shared/SmartFeeds/SmartFeedDelegate.swift b/Shared/SmartFeeds/SmartFeedDelegate.swift index 9bd7d5341..a8bb89e96 100644 --- a/Shared/SmartFeeds/SmartFeedDelegate.swift +++ b/Shared/SmartFeeds/SmartFeedDelegate.swift @@ -9,28 +9,36 @@ import Foundation import Account import Articles +import ArticlesDatabase import RSCore protocol SmartFeedDelegate: FeedIdentifiable, DisplayNameProvider, ArticleFetcher, SmallIconProvider { var fetchType: FetchType { get } - func fetchUnreadCount(for: Account, completion: @escaping (Int) -> Void) + func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) } extension SmartFeedDelegate { - func fetchArticles() -> Set
{ - return AccountManager.shared.fetchArticles(fetchType) + func fetchArticles() throws -> Set
{ + return try AccountManager.shared.fetchArticles(fetchType) } - func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { AccountManager.shared.fetchArticlesAsync(fetchType, completion) } - func fetchUnreadArticles() -> Set
{ - return fetchArticles().unreadArticles() + func fetchUnreadArticles() throws -> Set
{ + return try fetchArticles().unreadArticles() } - func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { - fetchArticlesAsync{ completion($0.unreadArticles()) } + func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { + fetchArticlesAsync{ articleSetResult in + switch articleSetResult { + case .success(let articles): + completion(.success(articles.unreadArticles())) + case .failure(let error): + completion(.failure(error)) + } + } } } diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index 000dca8c4..2c70b079e 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -9,6 +9,7 @@ import Foundation import RSCore import Articles +import ArticlesDatabase import Account // Main thread only. @@ -23,7 +24,7 @@ struct StarredFeedDelegate: SmartFeedDelegate { let fetchType: FetchType = .starred var smallIcon: IconImage? = AppAssets.starredFeedImage - func fetchUnreadCount(for account: Account, completion: @escaping (Int) -> Void) { + func fetchUnreadCount(for account: Account, completion: @escaping SingleUnreadCountCompletionBlock) { account.fetchUnreadCountForStarredArticles(completion) } } diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index 918e82054..50c275665 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -9,6 +9,7 @@ import Foundation import RSCore import Articles +import ArticlesDatabase import Account struct TodayFeedDelegate: SmartFeedDelegate { @@ -21,7 +22,7 @@ struct TodayFeedDelegate: SmartFeedDelegate { let fetchType = FetchType.today var smallIcon: IconImage? = AppAssets.todayFeedImage - func fetchUnreadCount(for account: Account, completion: @escaping (Int) -> Void) { + func fetchUnreadCount(for account: Account, completion: @escaping SingleUnreadCountCompletionBlock) { account.fetchUnreadCountForToday(completion) } } diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index b855f5f56..ed258a314 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -14,6 +14,7 @@ import Foundation import RSCore import Account import Articles +import ArticlesDatabase // This just shows the global unread count, which appDelegate already has. Easy. @@ -61,19 +62,19 @@ final class UnreadFeed: PseudoFeed { extension UnreadFeed: ArticleFetcher { - func fetchArticles() -> Set
{ - return fetchUnreadArticles() + func fetchArticles() throws -> Set
{ + return try fetchUnreadArticles() } - func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) { + func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { fetchUnreadArticlesAsync(completion) } - func fetchUnreadArticles() -> Set
{ - return AccountManager.shared.fetchArticles(fetchType) + func fetchUnreadArticles() throws -> Set
{ + return try AccountManager.shared.fetchArticles(fetchType) } - func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) { + func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { AccountManager.shared.fetchArticlesAsync(fetchType, completion) } } diff --git a/Shared/Timeline/FetchRequestOperation.swift b/Shared/Timeline/FetchRequestOperation.swift index 08f9cddc0..ad3d0b729 100644 --- a/Shared/Timeline/FetchRequestOperation.swift +++ b/Shared/Timeline/FetchRequestOperation.swift @@ -63,7 +63,7 @@ final class FetchRequestOperation { var fetchersReturned = 0 var fetchedArticles = Set
() - func process(articles: Set
) { + func process(_ articles: Set
) { precondition(Thread.isMainThread) guard !self.isCanceled else { callCompletionIfNeeded() @@ -83,17 +83,18 @@ final class FetchRequestOperation { for articleFetcher in articleFetchers { if readFilter { - articleFetcher.fetchUnreadArticlesAsync { (articles) in - process(articles: articles) + articleFetcher.fetchUnreadArticlesAsync { articleSetResult in + let articles = (try? articleSetResult.get()) ?? Set
() + process(articles) } - } else { - articleFetcher.fetchArticlesAsync { (articles) in - process(articles: articles) + } + else { + articleFetcher.fetchArticlesAsync { articleSetResult in + let articles = (try? articleSetResult.get()) ?? Set
() + process(articles) } } } - } - } diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 52b2faf39..e6dc35fd9 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -239,10 +239,13 @@ class ArticleViewController: UIViewController { } @objc func statusesDidChange(_ note: Notification) { - guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set
else { + guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set else { return } - if articles.count == 1 && articles.first?.articleID == currentArticle?.articleID { + guard let currentArticle = currentArticle else { + return + } + if articleIDs.contains(currentArticle.articleID) { updateUI() } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index bd45ae9b3..3d8ce6479 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -340,12 +340,12 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } @objc func statusesDidChange(_ note: Notification) { - guard let updatedArticles = note.userInfo?[Account.UserInfoKey.articles] as? Set
else { + guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set, !articleIDs.isEmpty else { return } - + let visibleArticles = tableView.indexPathsForVisibleRows!.compactMap { return dataSource.itemIdentifier(for: $0) } - let visibleUpdatedArticles = visibleArticles.filter { updatedArticles.contains($0) } + let visibleUpdatedArticles = visibleArticles.filter { articleIDs.contains($0.articleID) } for article in visibleUpdatedArticles { if let indexPath = dataSource.indexPath(for: article) { @@ -675,8 +675,11 @@ private extension MasterTimelineViewController { func markAllInFeedAsReadAction(_ article: Article) -> UIAction? { guard let webFeed = article.webFeed else { return nil } + guard let fetchedArticles = try? webFeed.fetchArticles() else { + return nil + } - let articles = Array(webFeed.fetchArticles()) + let articles = Array(fetchedArticles) guard articles.canMarkAllAsRead() else { return nil } @@ -692,8 +695,11 @@ private extension MasterTimelineViewController { func markAllInFeedAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? { guard let webFeed = article.webFeed else { return nil } - - let articles = Array(webFeed.fetchArticles()) + guard let fetchedArticles = try? webFeed.fetchArticles() else { + return nil + } + + let articles = Array(fetchedArticles) guard articles.canMarkAllAsRead() else { return nil }