diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 51ea64a90..196b189cd 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -448,6 +448,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } + public func fetchArticles(forArticleIDs articleIDs: Set) -> Set
{ + return database.fetchArticles(forArticleIDs: articleIDs) + } + public func fetchArticles(for feed: Feed) -> Set
{ let articles = database.fetchArticles(for: feed.feedID) @@ -535,6 +539,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback) } + public func fetchUnreadArticleIDs() -> Set { + return database.fetchUnreadArticleIDs() + } + + public func fetchStarredArticleIDs() -> Set { + return database.fetchStarredArticleIDs() + } + public func opmlDocument() -> String { let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters() let openingText = diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index d020a9148..8ca6128df 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -103,9 +103,8 @@ final class FeedbinAccountDelegate: AccountDelegate { caller.retrieveUnreadEntries() { [weak self] result in switch result { case .success(let articleIDs): - self?.syncArticleReadState(account: account, articleIDs: articleIDs) { - group.leave() - } + self?.syncArticleReadState(account: account, articleIDs: articleIDs) + group.leave() case .failure(let error): guard let self = self else { return } os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription) @@ -117,9 +116,8 @@ final class FeedbinAccountDelegate: AccountDelegate { caller.retrieveStarredEntries() { [weak self] result in switch result { case .success(let articleIDs): - self?.syncArticleStarredState(account: account, articleIDs: articleIDs) { - group.leave() - } + self?.syncArticleStarredState(account: account, articleIDs: articleIDs) + group.leave() case .failure(let error): guard let self = self else { return } os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription) @@ -1013,29 +1011,49 @@ private extension FeedbinAccountDelegate { } - func syncArticleReadState(account: Account, articleIDs: [Int]?, completion: (() -> Void)) { + func syncArticleReadState(account: Account, articleIDs: [Int]?) { guard let articleIDs = articleIDs, !articleIDs.isEmpty else { - completion() return } - let ids = Set(articleIDs.map { String($0) } ) + let feedbinUnreadArticleIDs = Set(articleIDs.map { String($0) } ) + let currentUnreadArticleIDs = account.fetchUnreadArticleIDs() - completion() + let deltaUnreadArticleIDs = feedbinUnreadArticleIDs.subtracting(currentUnreadArticleIDs) + let markUnreadArticles = account.fetchArticles(forArticleIDs: deltaUnreadArticleIDs) + DispatchQueue.main.async { + _ = account.markArticles(markUnreadArticles, statusKey: .read, flag: false) + } + + let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) + let markReadArticles = account.fetchArticles(forArticleIDs: deltaReadArticleIDs) + DispatchQueue.main.async { + _ = account.markArticles(markReadArticles, statusKey: .read, flag: true) + } } - func syncArticleStarredState(account: Account, articleIDs: [Int]?, completion: (() -> Void)) { + func syncArticleStarredState(account: Account, articleIDs: [Int]?) { guard let articleIDs = articleIDs, !articleIDs.isEmpty else { - completion() return } - let ids = Set(articleIDs.map { String($0) } ) + let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } ) + let currentStarredArticleIDs = account.fetchStarredArticleIDs() - completion() + let deltaStarredArticleIDs = feedbinStarredArticleIDs.subtracting(currentStarredArticleIDs) + let markStarredArticles = account.fetchArticles(forArticleIDs: deltaStarredArticleIDs) + DispatchQueue.main.async { + _ = account.markArticles(markStarredArticles, statusKey: .starred, flag: true) + } + + let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) + let markUnstarredArticles = account.fetchArticles(forArticleIDs: deltaUnstarredArticleIDs) + DispatchQueue.main.async { + _ = account.markArticles(markUnstarredArticles, statusKey: .starred, flag: false) + } } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 5105dbdec..cb6a3af31 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -49,6 +49,10 @@ public final class ArticlesDatabase { public func fetchArticles(for feedID: String) -> Set
{ return articlesTable.fetchArticles(feedID) } + + public func fetchArticles(forArticleIDs articleIDs: Set) -> Set
{ + return articlesTable.fetchArticles(forArticleIDs: articleIDs) + } public func fetchArticlesAsync(for feedID: String, _ resultBlock: @escaping ArticleResultBlock) { articlesTable.fetchArticlesAsync(feedID, withLimits: true, resultBlock) @@ -96,6 +100,14 @@ public final class ArticlesDatabase { // MARK: - Status + public func fetchUnreadArticleIDs() -> Set { + return articlesTable.fetchUnreadArticleIDs() + } + + public func fetchStarredArticleIDs() -> Set { + return articlesTable.fetchStarredArticleIDs() + } + public func mark(_ articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set? { return articlesTable.mark(articles, statusKey, flag) } diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index d5aa1505d..6e5f00f28 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -56,6 +56,11 @@ final class ArticlesTable: DatabaseTable { return articles } + public func fetchArticles(forArticleIDs articleIDs: Set) -> Set
{ + + return fetchArticlesForIDs(articleIDs) + } + func fetchArticlesAsync(_ feedID: String, withLimits: Bool, _ resultBlock: @escaping ArticleResultBlock) { queue.fetch { (database) in @@ -284,6 +289,14 @@ final class ArticlesTable: DatabaseTable { // MARK: Status + func fetchUnreadArticleIDs() -> Set { + return statusesTable.fetchUnreadArticleIDs() + } + + func fetchStarredArticleIDs() -> Set { + return statusesTable.fetchStarredArticleIDs() + } + func mark(_ articles: Set
, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set? { return statusesTable.mark(articles.statuses(), statusKey, flag) @@ -428,6 +441,25 @@ private extension ArticlesTable { return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject], withLimits: withLimits) } + func fetchArticlesForIDs(_ articleIDs: Set) -> Set
{ + + if articleIDs.isEmpty { + return Set
() + } + + var articles = Set
() + + queue.fetchSync { (database) in + + let parameters = articleIDs.map { $0 as AnyObject } + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))! + let whereClause = "articleID in \(placeholders)" + articles = self.fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false) + } + + return articles + } + func fetchUnreadArticles(_ feedIDs: Set) -> Set
{ if feedIDs.isEmpty { diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index 85207c8cf..080bb4054 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -78,6 +78,33 @@ final class StatusesTable: DatabaseTable { // MARK: Fetching + func fetchUnreadArticleIDs() -> Set { + return fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;") + } + + func fetchStarredArticleIDs() -> Set { + return fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;") + } + + func fetchArticleIDs(_ sql: String) -> Set { + + var statuses: Set? = nil + + queue.fetchSync { (database) in + if let resultSet = database.executeQuery(sql, withArgumentsIn: nil) { + statuses = resultSet.mapToSet(self.articleIDWithRow) + } + + } + + return statuses != nil ? statuses! : Set() + + } + + func articleIDWithRow(_ row: FMResultSet) -> String? { + return row.string(forColumn: DatabaseKey.articleID) + } + func statusWithRow(_ row: FMResultSet) -> ArticleStatus? { guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {