Finish changes mandated by DatabaseQueue changes.
This commit is contained in:
parent
3c8097404f
commit
15184aa3f1
|
@ -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<ArticleStatus>, 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<String>, 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<String>, 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<String>) {
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
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<Article>) {
|
||||
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<String>) {
|
||||
fetchAllUnreadCounts()
|
||||
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.articleIDs: articleIDs])
|
||||
}
|
||||
|
||||
func fetchAllUnreadCounts() {
|
||||
fetchingAllUnreadCounts = true
|
||||
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
|
||||
import Foundation
|
||||
import Articles
|
||||
import ArticlesDatabase
|
||||
|
||||
public protocol ArticleFetcher {
|
||||
|
||||
func fetchArticles() -> Set<Article>
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock)
|
||||
func fetchUnreadArticles() -> Set<Article>
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock)
|
||||
func fetchArticles() throws -> Set<Article>
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
|
||||
func fetchUnreadArticles() throws -> Set<Article>
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
|
||||
}
|
||||
|
||||
extension WebFeed: ArticleFetcher {
|
||||
|
@ -23,26 +24,33 @@ extension WebFeed: ArticleFetcher {
|
|||
return try account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
|
||||
}
|
||||
|
||||
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<Article>())
|
||||
completion(.success(Set<Article>()))
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.webFeed(self), completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles() -> Set<Article> {
|
||||
return fetchArticles().unreadArticles()
|
||||
public func fetchUnreadArticles() throws -> Set<Article> {
|
||||
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<Article>())
|
||||
completion(.success(Set<Article>()))
|
||||
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<Article>())
|
||||
completion(.success(Set<Article>()))
|
||||
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<Article>())
|
||||
completion(.success(Set<Article>()))
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.folder(self, true), completion)
|
||||
|
|
|
@ -165,10 +165,13 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func refreshMissingArticles(for account: Account, completion: @escaping ((Result<Void, Error>)-> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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, Error>) -> 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, Error>) -> Void)) {
|
||||
os_log(.debug, log: log, "Refreshing missing articles...")
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
var fetchedArticleIDs = Set<String>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Article>, statusKey: ArticleStatus.Key, flag: Bool) throws -> Set<Article>? {
|
||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
return try account.update(articles, statusKey: statusKey, flag: flag)
|
||||
return try? account.update(articles, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
||||
func accountDidInitialize(_ account: Account) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ParsedItem> {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Article> {
|
||||
return account.fetchArticles(.articleIDs(Set([articleID])))
|
||||
public func fetchArticles() throws -> Set<Article> {
|
||||
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<Article> {
|
||||
return account.fetchArticles(.articleIDs(Set([articleID])))
|
||||
public func fetchUnreadArticles() throws -> Set<Article> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -188,6 +188,10 @@ public final class ArticlesDatabase {
|
|||
return try articlesTable.mark(articles, statusKey, flag)
|
||||
}
|
||||
|
||||
public func mark(articleIDs: Set<String>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping DatabaseCompletionBlock) {
|
||||
articlesTable.mark(articleIDs, statusKey, flag, completion)
|
||||
}
|
||||
|
||||
public func fetchStatuses(articleIDs: Set<String>, createIfNeeded: Bool, completion: @escaping ArticleStatusesResultBlock) {
|
||||
articlesTable.fetchStatuses(articleIDs, createIfNeeded, completion)
|
||||
}
|
||||
|
|
|
@ -457,6 +457,18 @@ final class ArticlesTable: DatabaseTable {
|
|||
return statuses
|
||||
}
|
||||
|
||||
func mark(_ articleIDs: Set<String>, _ 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() {
|
||||
|
|
|
@ -85,6 +85,12 @@ final class StatusesTable: DatabaseTable {
|
|||
return updatedStatuses
|
||||
}
|
||||
|
||||
func mark(_ articleIDs: Set<String>, _ 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<String> {
|
||||
|
|
|
@ -261,7 +261,9 @@ private extension SidebarViewController {
|
|||
var articles = Set<Article>()
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<Article> else {
|
||||
guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String> else {
|
||||
return
|
||||
}
|
||||
reloadVisibleCells(for: articles)
|
||||
reloadVisibleCells(for: articleIDs)
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
|
@ -1019,9 +1019,13 @@ private extension TimelineViewController {
|
|||
var fetchedArticles = Set<Article>()
|
||||
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
|
||||
|
|
|
@ -165,7 +165,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
|||
|
||||
@objc(articles)
|
||||
var articles:NSArray {
|
||||
let feedArticles = webFeed.fetchArticles()
|
||||
let feedArticles = (try? webFeed.fetchArticles()) ?? Set<Article>()
|
||||
// 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<Article>()
|
||||
guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
|
||||
return ScriptableArticle(article, container:self)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Article> {
|
||||
return delegate.fetchArticles()
|
||||
func fetchArticles() throws -> Set<Article> {
|
||||
return try delegate.fetchArticles()
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
delegate.fetchArticlesAsync(completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
return delegate.fetchUnreadArticles()
|
||||
func fetchUnreadArticles() throws -> Set<Article> {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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<Article> {
|
||||
return AccountManager.shared.fetchArticles(fetchType)
|
||||
func fetchArticles() throws -> Set<Article> {
|
||||
return try AccountManager.shared.fetchArticles(fetchType)
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
AccountManager.shared.fetchArticlesAsync(fetchType, completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
return fetchArticles().unreadArticles()
|
||||
func fetchUnreadArticles() throws -> Set<Article> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Article> {
|
||||
return fetchUnreadArticles()
|
||||
func fetchArticles() throws -> Set<Article> {
|
||||
return try fetchUnreadArticles()
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchUnreadArticlesAsync(completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
return AccountManager.shared.fetchArticles(fetchType)
|
||||
func fetchUnreadArticles() throws -> Set<Article> {
|
||||
return try AccountManager.shared.fetchArticles(fetchType)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetBlock) {
|
||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
AccountManager.shared.fetchArticlesAsync(fetchType, completion)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ final class FetchRequestOperation {
|
|||
var fetchersReturned = 0
|
||||
var fetchedArticles = Set<Article>()
|
||||
|
||||
func process(articles: Set<Article>) {
|
||||
func process(_ articles: Set<Article>) {
|
||||
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<Article>()
|
||||
process(articles)
|
||||
}
|
||||
} else {
|
||||
articleFetcher.fetchArticlesAsync { (articles) in
|
||||
process(articles: articles)
|
||||
}
|
||||
else {
|
||||
articleFetcher.fetchArticlesAsync { articleSetResult in
|
||||
let articles = (try? articleSetResult.get()) ?? Set<Article>()
|
||||
process(articles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -239,10 +239,13 @@ class ArticleViewController: UIViewController {
|
|||
}
|
||||
|
||||
@objc func statusesDidChange(_ note: Notification) {
|
||||
guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else {
|
||||
guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String> else {
|
||||
return
|
||||
}
|
||||
if articles.count == 1 && articles.first?.articleID == currentArticle?.articleID {
|
||||
guard let currentArticle = currentArticle else {
|
||||
return
|
||||
}
|
||||
if articleIDs.contains(currentArticle.articleID) {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,12 +340,12 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
}
|
||||
|
||||
@objc func statusesDidChange(_ note: Notification) {
|
||||
guard let updatedArticles = note.userInfo?[Account.UserInfoKey.articles] as? Set<Article> else {
|
||||
guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String>, !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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue