Add the unread statuses on receipt to CloudKit.

This commit is contained in:
Maurice Parker 2020-04-10 17:23:39 -05:00
parent 983138366f
commit a8dcf3eeee
6 changed files with 59 additions and 28 deletions

View File

@ -715,7 +715,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
webFeedDictionariesNeedUpdate = true webFeedDictionariesNeedUpdate = true
} }
func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping DatabaseCompletionBlock) { func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
// Used only by an On My Mac or iCloud account. // Used only by an On My Mac or iCloud account.
precondition(Thread.isMainThread) precondition(Thread.isMainThread)
precondition(type == .onMyMac || type == .cloudKit) precondition(type == .onMyMac || type == .cloudKit)
@ -723,14 +723,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
webFeed.takeSettings(from: parsedFeed) webFeed.takeSettings(from: parsedFeed)
let parsedItems = parsedFeed.items let parsedItems = parsedFeed.items
guard !parsedItems.isEmpty else { guard !parsedItems.isEmpty else {
completion(nil) completion(.success(NewAndUpdatedArticles()))
return return
} }
update(webFeed.webFeedID, with: parsedItems, completion: completion) update(webFeed.webFeedID, with: parsedItems, completion: completion)
} }
func update(_ webFeedID: String, with parsedItems: Set<ParsedItem>, completion: @escaping DatabaseCompletionBlock) { func update(_ webFeedID: String, with parsedItems: Set<ParsedItem>, completion: @escaping UpdateArticlesCompletionBlock) {
// Used only by an On My Mac or iCloud account. // Used only by an On My Mac or iCloud account.
precondition(Thread.isMainThread) precondition(Thread.isMainThread)
precondition(type == .onMyMac || type == .cloudKit) precondition(type == .onMyMac || type == .cloudKit)
@ -739,9 +739,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
switch updateArticlesResult { switch updateArticlesResult {
case .success(let newAndUpdatedArticles): case .success(let newAndUpdatedArticles):
self.sendNotificationAbout(newAndUpdatedArticles) self.sendNotificationAbout(newAndUpdatedArticles)
completion(nil) completion(.success(newAndUpdatedArticles))
case .failure(let databaseError): case .failure(let databaseError):
completion(databaseError) completion(.failure(databaseError))
} }
} }
} }

View File

@ -13,6 +13,7 @@ import SyncDatabase
import RSCore import RSCore
import RSParser import RSParser
import Articles import Articles
import ArticlesDatabase
import RSWeb import RSWeb
public enum CloudKitAccountDelegateError: String, Error { public enum CloudKitAccountDelegateError: String, Error {
@ -50,7 +51,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
var accountMetadata: AccountMetadata? var accountMetadata: AccountMetadata?
var refreshProgress = DownloadProgress(numberOfTasks: 0) var refreshProgress = DownloadProgress(numberOfTasks: 0)
var refreshAllCompletion: ((Result<Void, Error>) -> Void)? = nil
init(dataFolder: String) { init(dataFolder: String) {
accountZone = CloudKitAccountZone(container: container) accountZone = CloudKitAccountZone(container: container)
@ -79,13 +79,12 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) { func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
guard refreshAllCompletion == nil else { guard refreshProgress.isComplete else {
completion(.success(())) completion(.success(()))
return return
} }
refreshAllCompletion = completion
refreshAll(for: account, downloadFeeds: true) refreshAll(for: account, downloadFeeds: true, completion: completion)
} }
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) { func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
@ -155,11 +154,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) { func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
guard refreshAllCompletion == nil else { guard refreshProgress.isComplete else {
completion(.success(())) completion(.success(()))
return return
} }
refreshAllCompletion = completion
var fileData: Data? var fileData: Data?
@ -214,7 +212,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in
self.refreshAll(for: account, downloadFeeds: false) self.refreshAll(for: account, downloadFeeds: false, completion: completion)
} }
} }
@ -493,7 +491,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
switch result { switch result {
case .success(let externalID): case .success(let externalID):
account.externalID = externalID account.externalID = externalID
self.refreshAll(for: account, downloadFeeds: false) self.refreshAll(for: account, downloadFeeds: false) { _ in }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Error adding account container: %@", error.localizedDescription) os_log(.error, log: self.log, "Error adding account container: %@", error.localizedDescription)
} }
@ -533,7 +531,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
private extension CloudKitAccountDelegate { private extension CloudKitAccountDelegate {
func refreshAll(for account: Account, downloadFeeds: Bool) { func refreshAll(for account: Account, downloadFeeds: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
let intialWebFeedsCount = downloadFeeds ? account.flattenedWebFeeds().count : 0 let intialWebFeedsCount = downloadFeeds ? account.flattenedWebFeeds().count : 0
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount) refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount)
@ -541,8 +539,7 @@ private extension CloudKitAccountDelegate {
func fail(_ error: Error) { func fail(_ error: Error) {
self.processAccountError(account, error) self.processAccountError(account, error)
self.refreshProgress.clear() self.refreshProgress.clear()
self.refreshAllCompletion?(.failure(error)) completion(.failure(error))
self.refreshAllCompletion = nil
} }
BatchUpdate.shared.start() BatchUpdate.shared.start()
@ -569,12 +566,24 @@ private extension CloudKitAccountDelegate {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
guard downloadFeeds else { guard downloadFeeds else {
self.refreshAllCompletion?(.success(())) completion(.success(()))
self.refreshAllCompletion = nil
return return
} }
self.refresher?.refreshFeeds(webFeeds) self.refresher?.refreshFeeds(webFeeds) {
account.metadata.lastArticleFetchEndTime = Date()
self.sendArticleStatus(for: account) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
fail(error)
}
}
}
case .failure(let error): case .failure(let error):
fail(error) fail(error)
@ -606,15 +615,21 @@ private extension CloudKitAccountDelegate {
extension CloudKitAccountDelegate: LocalAccountRefresherDelegate { extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles) {
if let newArticles = newAndUpdatedArticles.newArticles {
let syncStatuses = newArticles.map { article in
return SyncStatus(articleID: article.articleID, key: .read, flag: false)
}
database.insertStatuses(syncStatuses)
}
}
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) { func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) {
refreshProgress.completeTask() refreshProgress.completeTask()
} }
func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) { func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) {
self.refreshProgress.clear() self.refreshProgress.clear()
account?.metadata.lastArticleFetchEndTime = Date()
refreshAllCompletion?(.success(()))
refreshAllCompletion = nil
} }
} }

View File

@ -138,9 +138,9 @@ private extension CloudKitArticlesZoneDelegate {
for receivedStarredArticle in receivedStarredArticles { for receivedStarredArticle in receivedStarredArticles {
if let parsedItem = makeParsedItem(receivedStarredArticle) { if let parsedItem = makeParsedItem(receivedStarredArticle) {
group.enter() group.enter()
self.account?.update(parsedItem.feedURL, with: Set([parsedItem])) { databaseError in self.account?.update(parsedItem.feedURL, with: Set([parsedItem])) { result in
group.leave() group.leave()
if let databaseError = databaseError { if case .failure(let databaseError) = result {
os_log(.error, log: self.log, "Error occurred while storing starred items: %@", databaseError.localizedDescription) os_log(.error, log: self.log, "Error occurred while storing starred items: %@", databaseError.localizedDescription)
} }
} }

View File

@ -10,6 +10,7 @@ import Foundation
import RSCore import RSCore
import RSParser import RSParser
import Articles import Articles
import ArticlesDatabase
import RSWeb import RSWeb
public enum LocalAccountDelegateError: String, Error { public enum LocalAccountDelegateError: String, Error {
@ -233,6 +234,9 @@ final class LocalAccountDelegate: AccountDelegate {
extension LocalAccountDelegate: LocalAccountRefresherDelegate { extension LocalAccountDelegate: LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles) {
}
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) { func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) {
refreshProgress.completeTask() refreshProgress.completeTask()
} }

View File

@ -11,8 +11,10 @@ import RSCore
import RSParser import RSParser
import RSWeb import RSWeb
import Articles import Articles
import ArticlesDatabase
protocol LocalAccountRefresherDelegate { protocol LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess: NewAndUpdatedArticles)
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed)
func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher)
} }
@ -97,12 +99,12 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
return return
} }
account.update(feed, with: parsedFeed) { error in account.update(feed, with: parsedFeed) { result in
if error == nil { if case .success(let newAndUpdatedArticles) = result {
self.delegate?.localAccountRefresher(self, didProcess: newAndUpdatedArticles)
if let httpResponse = response as? HTTPURLResponse { if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
} }
feed.contentHash = dataHash feed.contentHash = dataHash
} }
completion() completion()
@ -157,9 +159,9 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
} }
func downloadSessionDidCompleteDownloadObjects(_ downloadSession: DownloadSession) { func downloadSessionDidCompleteDownloadObjects(_ downloadSession: DownloadSession) {
delegate?.localAccountRefresherDidFinish(self)
completions.forEach({ $0() }) completions.forEach({ $0() })
completions = [() -> Void]() completions = [() -> Void]()
delegate?.localAccountRefresherDidFinish(self)
} }
} }

View File

@ -27,6 +27,16 @@ public typealias SingleUnreadCountCompletionBlock = (SingleUnreadCountResult) ->
public struct NewAndUpdatedArticles { public struct NewAndUpdatedArticles {
public let newArticles: Set<Article>? public let newArticles: Set<Article>?
public let updatedArticles: Set<Article>? public let updatedArticles: Set<Article>?
public init() {
self.newArticles = Set<Article>()
self.updatedArticles = Set<Article>()
}
public init(newArticles: Set<Article>?, updatedArticles: Set<Article>?) {
self.newArticles = newArticles
self.updatedArticles = updatedArticles
}
} }
public typealias UpdateArticlesResult = Result<NewAndUpdatedArticles, DatabaseError> public typealias UpdateArticlesResult = Result<NewAndUpdatedArticles, DatabaseError>