Convert update methods to async await.

This commit is contained in:
Brent Simmons 2024-04-04 21:15:13 -07:00
parent 3f2db0ef12
commit a88d57952e
7 changed files with 89 additions and 111 deletions

View File

@ -784,58 +784,36 @@ public enum FetchType {
} }
@discardableResult @discardableResult
func update(_ feed: Feed, with parsedFeed: ParsedFeed) async throws -> ArticleChanges { func update(feed: Feed, with parsedFeed: ParsedFeed) async throws -> ArticleChanges {
try await withCheckedThrowingContinuation { continuation in
self.update(feed, with: parsedFeed) { result in
switch result {
case .success(let articleChanges):
continuation.resume(returning: articleChanges)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
// 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)
feed.takeSettings(from: parsedFeed) feed.takeSettings(from: parsedFeed)
let parsedItems = parsedFeed.items let parsedItems = parsedFeed.items
guard !parsedItems.isEmpty else { if parsedItems.isEmpty {
completion(.success(ArticleChanges())) return ArticleChanges()
return
} }
update(feed.feedID, with: parsedItems, completion: completion) return try await update(feedID: feed.feedID, with: parsedItems)
} }
func update(_ feedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) { func update(feedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true) async throws -> ArticleChanges {
// 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)
database.update(with: parsedItems, feedID: feedID, deleteOlder: deleteOlder) { updateArticlesResult in
MainActor.assumeIsolated { let articleChanges = try await database.update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder)
switch updateArticlesResult { self.sendNotificationAbout(articleChanges)
case .success(let articleChanges): return articleChanges
self.sendNotificationAbout(articleChanges)
completion(.success(articleChanges))
case .failure(let databaseError):
completion(.failure(databaseError))
}
}
}
} }
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) { func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
// Used only by syncing systems.
precondition(Thread.isMainThread) precondition(Thread.isMainThread)
precondition(type != .onMyMac && type != .cloudKit) precondition(type != .onMyMac && type != .cloudKit)
guard !feedIDsAndItems.isEmpty else { guard !feedIDsAndItems.isEmpty else {
completion(nil) completion(nil)
return return

View File

@ -849,45 +849,43 @@ private extension CloudKitAccountDelegate {
InitialFeedDownloader.download(url) { parsedFeed in InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed { if let parsedFeed {
account.update(feed, with: parsedFeed) { result in
MainActor.assumeIsolated { Task { @MainActor in
switch result {
case .success: do {
try await account.update(feed: feed, with: parsedFeed)
self.accountZone.createFeed(url: bestFeedSpecifier.urlString,
name: parsedFeed.title, self.accountZone.createFeed(url: bestFeedSpecifier.urlString,
editedName: editedName, name: parsedFeed.title,
homePageURL: parsedFeed.homePageURL, editedName: editedName,
container: container) { result in homePageURL: parsedFeed.homePageURL,
container: container) { result in
self.refreshProgress.completeTask()
switch result { self.refreshProgress.completeTask()
case .success(let externalID): switch result {
feed.externalID = externalID case .success(let externalID):
self.sendNewArticlesToTheCloud(account, feed) feed.externalID = externalID
completion(.success(feed)) self.sendNewArticlesToTheCloud(account, feed)
case .failure(let error): completion(.success(feed))
container.removeFeed(feed) case .failure(let error):
self.refreshProgress.completeTasks(2) container.removeFeed(feed)
completion(.failure(error)) self.refreshProgress.completeTasks(2)
} completion(.failure(error))
} }
case .failure(let error):
container.removeFeed(feed)
self.refreshProgress.completeTasks(3)
completion(.failure(error))
} }
} catch {
container.removeFeed(feed)
self.refreshProgress.completeTasks(3)
completion(.failure(error))
} }
} }
} else { } else {
self.refreshProgress.completeTasks(3) self.refreshProgress.completeTasks(3)
container.removeFeed(feed) container.removeFeed(feed)
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
} }
} }
case .failure: case .failure:

View File

@ -139,27 +139,27 @@ private extension CloudKitArticlesZoneDelegate {
let parsedItems = records.compactMap { self.makeParsedItem($0) } let parsedItems = records.compactMap { self.makeParsedItem($0) }
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
DispatchQueue.main.async { Task { @MainActor in
for (feedID, parsedItems) in feedIDsAndItems { for (feedID, parsedItems) in feedIDsAndItems {
group.enter() group.enter()
self.account?.update(feedID, with: parsedItems, deleteOlder: false) { result in
Task { @MainActor in do {
switch result {
case .success(let articleChanges): let articleChanges = try await self.account?.update(feedID: feedID, with: parsedItems, deleteOlder: false)
guard let deletes = articleChanges.deletedArticles, !deletes.isEmpty else {
group.leave() guard let deletes = articleChanges?.deletedArticles, !deletes.isEmpty else {
return group.leave()
} return
let syncStatuses = deletes.map { SyncStatus(articleID: $0.articleID, key: .deleted, flag: true) }
try? await self.database.insertStatuses(syncStatuses)
group.leave()
case .failure(let databaseError):
errorOccurred = true
os_log(.error, log: self.log, "Error occurred while storing articles: %@", databaseError.localizedDescription)
group.leave()
}
} }
let syncStatuses = deletes.map { SyncStatus(articleID: $0.articleID, key: .deleted, flag: true) }
try? await self.database.insertStatuses(syncStatuses)
group.leave()
} catch {
errorOccurred = true
os_log(.error, log: self.log, "Error occurred while storing articles: %@", error.localizedDescription)
group.leave()
} }
} }
} }

View File

@ -227,7 +227,7 @@ private extension LocalAccountDelegate {
feed.editedName = editedName feed.editedName = editedName
container.addFeed(feed) container.addFeed(feed)
try await account.update(feed, with: parsedFeed) try await account.update(feed: feed, with: parsedFeed)
return feed return feed
} }

View File

@ -109,24 +109,26 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
return return
} }
account.update(feed, with: parsedFeed) { result in Task { @MainActor in
MainActor.assumeIsolated {
if case .success(let articleChanges) = result { do {
if let httpResponse = response as? HTTPURLResponse { let articleChanges = try await account.update(feed: feed, with: parsedFeed)
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
} if let httpResponse = response as? HTTPURLResponse {
feed.contentHash = dataHash feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) {
completion()
}
} else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
} }
feed.contentHash = dataHash
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) {
completion()
}
} catch {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
} }
} }
} }
} }

View File

@ -14,7 +14,7 @@ import Parser
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
public struct ArticleChanges { public struct ArticleChanges: Sendable {
public let newArticles: Set<Article>? public let newArticles: Set<Article>?
public let updatedArticles: Set<Article>? public let updatedArticles: Set<Article>?
public let deletedArticles: Set<Article>? public let deletedArticles: Set<Article>?

View File

@ -25,17 +25,17 @@ public extension ArticlesDatabase {
// MARK: - Saving, Updating, and Deleting Articles // MARK: - Saving, Updating, and Deleting Articles
/// Update articles and save new ones  for feed-based systems (local and iCloud). /// Update articles and save new ones  for feed-based systems (local and iCloud).
nonisolated func update(with parsedItems: Set<ParsedItem>, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) { // nonisolated func update(with parsedItems: Set<ParsedItem>, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
//
Task { // Task {
do { // do {
let articleChanges = try await update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder) // let articleChanges = try await update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder)
callUpdateArticlesCompletion(completion, .success(articleChanges)) // callUpdateArticlesCompletion(completion, .success(articleChanges))
} catch { // } catch {
callUpdateArticlesCompletion(completion, .failure(.suspended)) // callUpdateArticlesCompletion(completion, .failure(.suspended))
} // }
} // }
} // }
/// Update articles and save new ones for sync systems (Feedbin, Feedly, etc.). /// Update articles and save new ones for sync systems (Feedbin, Feedly, etc.).
nonisolated func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) { nonisolated func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {