Add completion callbacks so that we can ensure that unreads have been marked before determining the next unread. Fixes #2993

This commit is contained in:
Maurice Parker 2021-04-12 19:41:01 -05:00
parent 3a1b3f96bb
commit c95daa208f
14 changed files with 60 additions and 41 deletions

View File

@ -536,8 +536,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
addOPMLItems(OPMLNormalizer.normalize(items)) addOPMLItems(OPMLNormalizer.normalize(items))
} }
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.markArticles(for: self, articles: articles, statusKey: statusKey, flag: flag) delegate.markArticles(for: self, articles: articles, statusKey: statusKey, flag: flag, completion: completion)
} }
func existingContainer(withExternalID externalID: String) -> Container? { func existingContainer(withExternalID externalID: String) -> Container? {

View File

@ -44,7 +44,7 @@ protocol AccountDelegate {
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void)
// Called at the end of accounts init method. // Called at the end of accounts init method.
func accountDidInitialize(_ account: Account) func accountDidInitialize(_ account: Account)

View File

@ -387,7 +387,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -398,12 +398,12 @@ final class CloudKitAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account, showProgress: false) { _ in } self.sendArticleStatus(for: account, showProgress: false, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -454,7 +454,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
fatalError() fatalError()
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -465,12 +465,12 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account) { _ in } self.sendArticleStatus(for: account, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -536,7 +536,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -547,12 +547,12 @@ final class FeedbinAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account) { _ in } self.sendArticleStatus(for: account, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -487,7 +487,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
} }
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -498,12 +498,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account) { _ in } self.sendArticleStatus(for: account, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -209,8 +209,14 @@ final class LocalAccountDelegate: AccountDelegate {
completion(.success(())) completion(.success(()))
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { _ in } account.update(articles, statusKey: statusKey, flag: flag) { result in
if case .failure(let error) = result {
completion(.failure(error))
} else {
completion(.success(()))
}
}
} }
func accountDidInitialize(_ account: Account) { func accountDidInitialize(_ account: Account) {

View File

@ -564,7 +564,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
} }
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -575,12 +575,12 @@ final class NewsBlurAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account) { _ in } self.sendArticleStatus(for: account, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -529,7 +529,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
} }
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
account.update(articles, statusKey: statusKey, flag: flag) { result in account.update(articles, statusKey: statusKey, flag: flag) { result in
switch result { switch result {
case .success(let articles): case .success(let articles):
@ -540,12 +540,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
self.database.insertStatuses(syncStatuses) { _ in self.database.insertStatuses(syncStatuses) { _ in
self.database.selectPendingCount { result in self.database.selectPendingCount { result in
if let count = try? result.get(), count > 100 { if let count = try? result.get(), count > 100 {
self.sendArticleStatus(for: account) { _ in } self.sendArticleStatus(for: account, completion: completion)
} }
} }
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error marking article status: %@", error.localizedDescription) completion(.failure(error))
} }
} }
} }

View File

@ -20,8 +20,9 @@ final class MarkStatusCommand: UndoableCommand {
let undoManager: UndoManager let undoManager: UndoManager
let flag: Bool let flag: Bool
let statusKey: ArticleStatus.Key let statusKey: ArticleStatus.Key
var completion: (() -> Void)? = nil
init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager) { init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
// Filter out articles that already have the desired status or can't be marked. // Filter out articles that already have the desired status or can't be marked.
let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag) let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag)
@ -33,6 +34,7 @@ final class MarkStatusCommand: UndoableCommand {
self.flag = flag self.flag = flag
self.statusKey = statusKey self.statusKey = statusKey
self.undoManager = undoManager self.undoManager = undoManager
self.completion = completion
let actionName = MarkStatusCommand.actionName(statusKey, flag) let actionName = MarkStatusCommand.actionName(statusKey, flag)
self.undoActionName = actionName self.undoActionName = actionName
@ -40,12 +42,10 @@ final class MarkStatusCommand: UndoableCommand {
} }
convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) { convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) {
self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager) self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager)
} }
convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager) { convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager) {
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager) self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager)
} }
@ -63,8 +63,8 @@ final class MarkStatusCommand: UndoableCommand {
private extension MarkStatusCommand { private extension MarkStatusCommand {
func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) { func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
markArticles(articles, statusKey: statusKey, flag: flag, completion: completion)
markArticles(articles, statusKey: statusKey, flag: flag) completion = nil
} }
static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command") static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command")

View File

@ -13,15 +13,24 @@ import Account
// These handle multiple accounts. // These handle multiple accounts.
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) { func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles) let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
let group = DispatchGroup()
for (accountID, accountArticles) in d { for (accountID, accountArticles) in d {
guard let account = AccountManager.shared.existingAccount(with: accountID) else { guard let account = AccountManager.shared.existingAccount(with: accountID) else {
continue continue
} }
account.markArticles(accountArticles, statusKey: statusKey, flag: flag) group.enter()
account.markArticles(accountArticles, statusKey: statusKey, flag: flag) { _ in
group.leave()
}
}
group.notify(queue: .main) {
completion?()
} }
} }

View File

@ -428,7 +428,7 @@ private extension AppDelegate {
os_log(.debug, "No article found from search using %@", articleID) os_log(.debug, "No article found from search using %@", articleID)
return return
} }
account!.markArticles(article!, statusKey: .read, flag: true) account!.markArticles(article!, statusKey: .read, flag: true) { _ in }
self.prepareAccountsForBackground() self.prepareAccountsForBackground()
account!.syncArticleStatus(completion: { [weak self] _ in account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
@ -458,7 +458,7 @@ private extension AppDelegate {
os_log(.debug, "No article found from search using %@", articleID) os_log(.debug, "No article found from search using %@", articleID)
return return
} }
account!.markArticles(article!, statusKey: .starred, flag: true) account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) { if #available(iOS 14, *) {

View File

@ -58,8 +58,9 @@ class RootSplitViewController: UISplitViewController {
} }
@objc func markAllAsReadAndGoToNextUnread(_ sender: Any?) { @objc func markAllAsReadAndGoToNextUnread(_ sender: Any?) {
coordinator.markAllAsReadInTimeline() coordinator.markAllAsReadInTimeline() {
coordinator.selectNextUnread() self.coordinator.selectNextUnread()
}
} }
@objc func markAboveAsRead(_ sender: Any?) { @objc func markAboveAsRead(_ sender: Any?) {

View File

@ -997,13 +997,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
} }
} }
func markAllAsRead(_ articles: [Article]) { func markAllAsRead(_ articles: [Article], completion: (() -> Void)? = nil) {
markArticlesWithUndo(articles, statusKey: .read, flag: true) markArticlesWithUndo(articles, statusKey: .read, flag: true, completion: completion)
} }
func markAllAsReadInTimeline() { func markAllAsReadInTimeline(completion: (() -> Void)? = nil) {
markAllAsRead(articles) markAllAsRead(articles) {
masterNavigationController.popViewController(animated: true) self.masterNavigationController.popViewController(animated: true)
completion?()
}
} }
func canMarkAboveAsRead(for article: Article) -> Bool { func canMarkAboveAsRead(for article: Article) -> Bool {
@ -1372,8 +1374,9 @@ extension SceneCoordinator: UINavigationControllerDelegate {
private extension SceneCoordinator { private extension SceneCoordinator {
func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) { func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else { guard let undoManager = undoManager,
let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager, completion: completion) else {
return return
} }
runCommand(markReadCommand) runCommand(markReadCommand)