diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index f118c6ab3..30b42b49d 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -784,58 +784,36 @@ public enum FetchType { } @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(type == .onMyMac || type == .cloudKit) feed.takeSettings(from: parsedFeed) + let parsedItems = parsedFeed.items - guard !parsedItems.isEmpty else { - completion(.success(ArticleChanges())) - return + if parsedItems.isEmpty { + return ArticleChanges() } - update(feed.feedID, with: parsedItems, completion: completion) + return try await update(feedID: feed.feedID, with: parsedItems) } - func update(_ feedID: String, with parsedItems: Set, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) { - // Used only by an On My Mac or iCloud account. + func update(feedID: String, with parsedItems: Set, deleteOlder: Bool = true) async throws -> ArticleChanges { + precondition(Thread.isMainThread) precondition(type == .onMyMac || type == .cloudKit) - - database.update(with: parsedItems, feedID: feedID, deleteOlder: deleteOlder) { updateArticlesResult in - MainActor.assumeIsolated { - switch updateArticlesResult { - case .success(let articleChanges): - self.sendNotificationAbout(articleChanges) - completion(.success(articleChanges)) - case .failure(let databaseError): - completion(.failure(databaseError)) - } - } - } + let articleChanges = try await database.update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder) + self.sendNotificationAbout(articleChanges) + return articleChanges } func update(feedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) { - // Used only by syncing systems. + precondition(Thread.isMainThread) precondition(type != .onMyMac && type != .cloudKit) + guard !feedIDsAndItems.isEmpty else { completion(nil) return diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 686a181e9..ae3bc08e7 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -849,45 +849,43 @@ private extension CloudKitAccountDelegate { InitialFeedDownloader.download(url) { parsedFeed in self.refreshProgress.completeTask() - if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed) { result in - MainActor.assumeIsolated { - switch result { - case .success: - - self.accountZone.createFeed(url: bestFeedSpecifier.urlString, - name: parsedFeed.title, - editedName: editedName, - homePageURL: parsedFeed.homePageURL, - container: container) { result in - - self.refreshProgress.completeTask() - switch result { - case .success(let externalID): - feed.externalID = externalID - self.sendNewArticlesToTheCloud(account, feed) - completion(.success(feed)) - case .failure(let error): - container.removeFeed(feed) - self.refreshProgress.completeTasks(2) - completion(.failure(error)) - } - + if let parsedFeed { + + Task { @MainActor in + + do { + try await account.update(feed: feed, with: parsedFeed) + + self.accountZone.createFeed(url: bestFeedSpecifier.urlString, + name: parsedFeed.title, + editedName: editedName, + homePageURL: parsedFeed.homePageURL, + container: container) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let externalID): + feed.externalID = externalID + self.sendNewArticlesToTheCloud(account, feed) + completion(.success(feed)) + case .failure(let error): + container.removeFeed(feed) + 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 { self.refreshProgress.completeTasks(3) container.removeFeed(feed) completion(.failure(AccountError.createErrorNotFound)) - } - + } } case .failure: diff --git a/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift index 2433dc693..f1e730c89 100644 --- a/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift @@ -139,27 +139,27 @@ private extension CloudKitArticlesZoneDelegate { let parsedItems = records.compactMap { self.makeParsedItem($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 { group.enter() - self.account?.update(feedID, with: parsedItems, deleteOlder: false) { result in - Task { @MainActor in - switch result { - case .success(let articleChanges): - guard let deletes = articleChanges.deletedArticles, !deletes.isEmpty else { - 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() - } + + do { + + let articleChanges = try await self.account?.update(feedID: feedID, with: parsedItems, deleteOlder: false) + + guard let deletes = articleChanges?.deletedArticles, !deletes.isEmpty else { + group.leave() + return } + + 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() } } } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 5618b8cc4..3b5ee2418 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -227,7 +227,7 @@ private extension LocalAccountDelegate { feed.editedName = editedName container.addFeed(feed) - try await account.update(feed, with: parsedFeed) + try await account.update(feed: feed, with: parsedFeed) return feed } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index 31851db22..c8724e47f 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -109,24 +109,26 @@ extension LocalAccountRefresher: DownloadSessionDelegate { return } - account.update(feed, with: parsedFeed) { result in - MainActor.assumeIsolated { - if case .success(let articleChanges) = result { - if let httpResponse = response as? HTTPURLResponse { - feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) - } - feed.contentHash = dataHash - self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) - self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) { - completion() - } - } else { - completion() - self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) + Task { @MainActor in + + do { + let articleChanges = try await account.update(feed: feed, with: parsedFeed) + + if let httpResponse = response as? HTTPURLResponse { + feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) } + feed.contentHash = dataHash + + self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) + self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) { + completion() + } + + } catch { + completion() + self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) } } - } } diff --git a/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabase.swift b/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabase.swift index 038d521cd..adf22e002 100644 --- a/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabase.swift +++ b/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabase.swift @@ -14,7 +14,7 @@ import Parser public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount -public struct ArticleChanges { +public struct ArticleChanges: Sendable { public let newArticles: Set
? public let updatedArticles: Set
? public let deletedArticles: Set
? diff --git a/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabaseCompatibility.swift b/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabaseCompatibility.swift index ebda6daac..5e90831d2 100644 --- a/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabaseCompatibility.swift +++ b/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabaseCompatibility.swift @@ -25,17 +25,17 @@ public extension ArticlesDatabase { // MARK: - Saving, Updating, and Deleting Articles /// Update articles and save new ones — for feed-based systems (local and iCloud). - nonisolated func update(with parsedItems: Set, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) { - - Task { - do { - let articleChanges = try await update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder) - callUpdateArticlesCompletion(completion, .success(articleChanges)) - } catch { - callUpdateArticlesCompletion(completion, .failure(.suspended)) - } - } - } +// nonisolated func update(with parsedItems: Set, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) { +// +// Task { +// do { +// let articleChanges = try await update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder) +// callUpdateArticlesCompletion(completion, .success(articleChanges)) +// } catch { +// callUpdateArticlesCompletion(completion, .failure(.suspended)) +// } +// } +// } /// Update articles and save new ones — for sync systems (Feedbin, Feedly, etc.). nonisolated func update(feedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {