Continue deleting completion-based functions in SyncDatabase.

This commit is contained in:
Brent Simmons 2024-03-25 21:44:25 -07:00
parent 4385b2d6b7
commit 951349ffc5
7 changed files with 81 additions and 114 deletions

View File

@ -20,38 +20,39 @@ import CloudKitExtras
class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate { class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
weak var account: Account? weak var account: Account?
var database: SyncDatabase var database: SyncDatabase
weak var articlesZone: CloudKitArticlesZone? weak var articlesZone: CloudKitArticlesZone?
var compressionQueue = DispatchQueue(label: "Articles Zone Delegate Compression Queue") var compressionQueue = DispatchQueue(label: "Articles Zone Delegate Compression Queue")
init(account: Account, database: SyncDatabase, articlesZone: CloudKitArticlesZone) { init(account: Account, database: SyncDatabase, articlesZone: CloudKitArticlesZone) {
self.account = account self.account = account
self.database = database self.database = database
self.articlesZone = articlesZone self.articlesZone = articlesZone
} }
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) { func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
database.selectPendingReadStatusArticleIDs() { result in database.selectPendingReadStatusArticleIDs() { result in
switch result { switch result {
case .success(let pendingReadStatusArticleIDs): case .success(let pendingReadStatusArticleIDs):
self.database.selectPendingStarredStatusArticleIDs() { result in Task { @MainActor in
switch result {
case .success(let pendingStarredStatusArticleIDs):
do {
let pendingStarredStatusArticleIDs = (try await self.database.selectPendingStarredStatusArticleIDs()) ?? Set<String>()
self.delete(recordKeys: deleted, pendingStarredStatusArticleIDs: pendingStarredStatusArticleIDs) { self.delete(recordKeys: deleted, pendingStarredStatusArticleIDs: pendingStarredStatusArticleIDs) {
Task { @MainActor in Task { @MainActor in
self.update(records: changed, self.update(records: changed,
pendingReadStatusArticleIDs: pendingReadStatusArticleIDs, pendingReadStatusArticleIDs: pendingReadStatusArticleIDs,
pendingStarredStatusArticleIDs: pendingStarredStatusArticleIDs, pendingStarredStatusArticleIDs: pendingStarredStatusArticleIDs,
completion: completion) completion: completion)
} }
} }
case .failure(let error): } catch {
os_log(.error, log: self.log, "Error occurred getting pending starred records: %@", error.localizedDescription) os_log(.error, log: self.log, "Error occurred getting pending starred records: %@", error.localizedDescription)
completion(.failure(CloudKitZoneError.unknown)) completion(.failure(CloudKitZoneError.unknown))
} }
@ -70,12 +71,12 @@ private extension CloudKitArticlesZoneDelegate {
let receivedRecordIDs = recordKeys.filter({ $0.recordType == CloudKitArticlesZone.CloudKitArticleStatus.recordType }).map({ $0.recordID }) let receivedRecordIDs = recordKeys.filter({ $0.recordType == CloudKitArticlesZone.CloudKitArticleStatus.recordType }).map({ $0.recordID })
let receivedArticleIDs = Set(receivedRecordIDs.map({ stripPrefix($0.externalID) })) let receivedArticleIDs = Set(receivedRecordIDs.map({ stripPrefix($0.externalID) }))
let deletableArticleIDs = receivedArticleIDs.subtracting(pendingStarredStatusArticleIDs) let deletableArticleIDs = receivedArticleIDs.subtracting(pendingStarredStatusArticleIDs)
guard !deletableArticleIDs.isEmpty else { guard !deletableArticleIDs.isEmpty else {
completion() completion()
return return
} }
Task { @MainActor in Task { @MainActor in
try? await self.database.deleteSelectedForProcessing(Array(deletableArticleIDs)) try? await self.database.deleteSelectedForProcessing(Array(deletableArticleIDs))
self.account?.delete(articleIDs: deletableArticleIDs) { _ in self.account?.delete(articleIDs: deletableArticleIDs) { _ in
@ -98,7 +99,7 @@ private extension CloudKitArticlesZoneDelegate {
var errorOccurred = false var errorOccurred = false
let group = DispatchGroup() let group = DispatchGroup()
group.enter() group.enter()
account?.markAsUnread(updateableUnreadArticleIDs) { databaseError in account?.markAsUnread(updateableUnreadArticleIDs) { databaseError in
MainActor.assumeIsolated { MainActor.assumeIsolated {
@ -147,7 +148,7 @@ private extension CloudKitArticlesZoneDelegate {
compressionQueue.async { compressionQueue.async {
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 { DispatchQueue.main.async {
for (feedID, parsedItems) in feedIDsAndItems { for (feedID, parsedItems) in feedIDsAndItems {
group.enter() group.enter()
@ -174,7 +175,7 @@ private extension CloudKitArticlesZoneDelegate {
} }
} }
} }
group.notify(queue: DispatchQueue.main) { group.notify(queue: DispatchQueue.main) {
if errorOccurred { if errorOccurred {
completion(.failure(CloudKitZoneError.unknown)) completion(.failure(CloudKitZoneError.unknown))
@ -183,7 +184,7 @@ private extension CloudKitArticlesZoneDelegate {
} }
} }
} }
func stripPrefix(_ externalID: String) -> String { func stripPrefix(_ externalID: String) -> String {
return String(externalID[externalID.index(externalID.startIndex, offsetBy: 2)..<externalID.endIndex]) return String(externalID[externalID.index(externalID.startIndex, offsetBy: 2)..<externalID.endIndex])
} }
@ -192,11 +193,11 @@ private extension CloudKitArticlesZoneDelegate {
guard articleRecord.recordType == CloudKitArticlesZone.CloudKitArticle.recordType else { guard articleRecord.recordType == CloudKitArticlesZone.CloudKitArticle.recordType else {
return nil return nil
} }
var parsedAuthors = Set<ParsedAuthor>() var parsedAuthors = Set<ParsedAuthor>()
let decoder = JSONDecoder() let decoder = JSONDecoder()
if let encodedParsedAuthors = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.parsedAuthors] as? [String] { if let encodedParsedAuthors = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.parsedAuthors] as? [String] {
for encodedParsedAuthor in encodedParsedAuthors { for encodedParsedAuthor in encodedParsedAuthors {
if let data = encodedParsedAuthor.data(using: .utf8), let parsedAuthor = try? decoder.decode(ParsedAuthor.self, from: data) { if let data = encodedParsedAuthor.data(using: .utf8), let parsedAuthor = try? decoder.decode(ParsedAuthor.self, from: data) {
@ -204,26 +205,26 @@ private extension CloudKitArticlesZoneDelegate {
} }
} }
} }
guard let uniqueID = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.uniqueID] as? String, guard let uniqueID = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.uniqueID] as? String,
let feedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.feedURL] as? String else { let feedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.feedURL] as? String else {
return nil return nil
} }
var contentHTML = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTML] as? String var contentHTML = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTML] as? String
if let contentHTMLData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTMLData] as? NSData { if let contentHTMLData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTMLData] as? NSData {
if let decompressedContentHTMLData = try? contentHTMLData.decompressed(using: .lzfse) { if let decompressedContentHTMLData = try? contentHTMLData.decompressed(using: .lzfse) {
contentHTML = String(data: decompressedContentHTMLData as Data, encoding: .utf8) contentHTML = String(data: decompressedContentHTMLData as Data, encoding: .utf8)
} }
} }
var contentText = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentText] as? String var contentText = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentText] as? String
if let contentTextData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentTextData] as? NSData { if let contentTextData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentTextData] as? NSData {
if let decompressedContentTextData = try? contentTextData.decompressed(using: .lzfse) { if let decompressedContentTextData = try? contentTextData.decompressed(using: .lzfse) {
contentText = String(data: decompressedContentTextData as Data, encoding: .utf8) contentText = String(data: decompressedContentTextData as Data, encoding: .utf8)
} }
} }
let parsedItem = ParsedItem(syncServiceID: nil, let parsedItem = ParsedItem(syncServiceID: nil,
uniqueID: uniqueID, uniqueID: uniqueID,
feedURL: feedURL, feedURL: feedURL,
@ -241,8 +242,7 @@ private extension CloudKitArticlesZoneDelegate {
authors: parsedAuthors, authors: parsedAuthors,
tags: nil, tags: nil,
attachments: nil) attachments: nil)
return parsedItem return parsedItem
} }
} }

View File

@ -1360,54 +1360,52 @@ private extension FeedbinAccountDelegate {
return return
} }
database.selectPendingStarredStatusArticleIDs() { result in Task { @MainActor in
do {
let pendingArticleIDs = (try await self.database.selectPendingStarredStatusArticleIDs()) ?? Set<String>()
MainActor.assumeIsolated {
@MainActor func process(_ pendingArticleIDs: Set<String>) { @MainActor func process(_ pendingArticleIDs: Set<String>) {
let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } ) let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } )
let updatableFeedbinStarredArticleIDs = feedbinStarredArticleIDs.subtracting(pendingArticleIDs) let updatableFeedbinStarredArticleIDs = feedbinStarredArticleIDs.subtracting(pendingArticleIDs)
account.fetchStarredArticleIDs { articleIDsResult in account.fetchStarredArticleIDs { articleIDsResult in
MainActor.assumeIsolated { MainActor.assumeIsolated {
guard let currentStarredArticleIDs = try? articleIDsResult.get() else { guard let currentStarredArticleIDs = try? articleIDsResult.get() else {
return return
} }
let group = DispatchGroup() let group = DispatchGroup()
// Mark articles as starred // Mark articles as starred
let deltaStarredArticleIDs = updatableFeedbinStarredArticleIDs.subtracting(currentStarredArticleIDs) let deltaStarredArticleIDs = updatableFeedbinStarredArticleIDs.subtracting(currentStarredArticleIDs)
group.enter() group.enter()
account.markAsStarred(deltaStarredArticleIDs) { _ in account.markAsStarred(deltaStarredArticleIDs) { _ in
group.leave() group.leave()
} }
// Mark articles as unstarred // Mark articles as unstarred
let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableFeedbinStarredArticleIDs) let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableFeedbinStarredArticleIDs)
group.enter() group.enter()
account.markAsUnstarred(deltaUnstarredArticleIDs) { _ in account.markAsUnstarred(deltaUnstarredArticleIDs) { _ in
group.leave() group.leave()
} }
group.notify(queue: DispatchQueue.main) { group.notify(queue: DispatchQueue.main) {
completion() completion()
} }
} }
} }
}
switch result {
case .success(let pendingArticleIDs):
process(pendingArticleIDs)
case .failure(let error):
os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
} }
process(pendingArticleIDs)
} catch {
os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
} }
} }
} }
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) { func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {

View File

@ -77,17 +77,15 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
return return
} }
database.selectPendingStarredStatusArticleIDs { result in Task { @MainActor in
MainActor.assumeIsolated {
switch result {
case .success(let pendingArticleIds):
self.remoteEntryIds.subtract(pendingArticleIds)
self.updateStarredStatuses() do {
if let pendingArticleIDs = try await self.database.selectPendingStarredStatusArticleIDs() {
case .failure(let error): self.remoteEntryIds.subtract(pendingArticleIDs)
self.didFinish(with: error)
} }
self.updateStarredStatuses()
} catch {
self.didFinish(with: error)
} }
} }
} }

View File

@ -78,17 +78,15 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
return return
} }
database.selectPendingReadStatusArticleIDs { result in Task { @MainActor in
MainActor.assumeIsolated {
switch result {
case .success(let pendingArticleIds):
self.remoteEntryIds.subtract(pendingArticleIds)
self.updateUnreadStatuses() do {
if let pendingArticleIDs = try await self.database.selectPendingReadStatusArticleIDs() {
case .failure(let error): self.remoteEntryIds.subtract(pendingArticleIDs)
self.didFinish(with: error)
} }
self.updateUnreadStatuses()
} catch {
self.didFinish(with: error)
} }
} }
} }

View File

@ -381,8 +381,11 @@ extension NewsBlurAccountDelegate {
return return
} }
database.selectPendingStarredStatusArticleIDs() { result in Task { @MainActor in
MainActor.assumeIsolated {
do {
let pendingArticleIDs = (try await database.selectPendingStarredStatusArticleIDs()) ?? Set<String>()
@MainActor func process(_ pendingStoryHashes: Set<String>) { @MainActor func process(_ pendingStoryHashes: Set<String>) {
let newsBlurStarredStoryHashes = Set(hashes.map { $0.hash } ) let newsBlurStarredStoryHashes = Set(hashes.map { $0.hash } )
@ -393,23 +396,23 @@ extension NewsBlurAccountDelegate {
guard let currentStarredArticleIDs = try? articleIDsResult.get() else { guard let currentStarredArticleIDs = try? articleIDsResult.get() else {
return return
} }
let group = DispatchGroup() let group = DispatchGroup()
// Mark articles as starred // Mark articles as starred
let deltaStarredArticleIDs = updatableNewsBlurUnreadStoryHashes.subtracting(currentStarredArticleIDs) let deltaStarredArticleIDs = updatableNewsBlurUnreadStoryHashes.subtracting(currentStarredArticleIDs)
group.enter() group.enter()
account.markAsStarred(deltaStarredArticleIDs) { _ in account.markAsStarred(deltaStarredArticleIDs) { _ in
group.leave() group.leave()
} }
// Mark articles as unstarred // Mark articles as unstarred
let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableNewsBlurUnreadStoryHashes) let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableNewsBlurUnreadStoryHashes)
group.enter() group.enter()
account.markAsUnstarred(deltaUnstarredArticleIDs) { _ in account.markAsUnstarred(deltaUnstarredArticleIDs) { _ in
group.leave() group.leave()
} }
group.notify(queue: DispatchQueue.main) { group.notify(queue: DispatchQueue.main) {
completion() completion()
} }
@ -417,12 +420,10 @@ extension NewsBlurAccountDelegate {
} }
} }
switch result { process(pendingArticleIDs)
case .success(let pendingArticleIDs):
process(pendingArticleIDs) } catch {
case .failure(let error): os_log(.error, log: self.log, "Sync Story Starred Status failed: %@.", error.localizedDescription)
os_log(.error, log: self.log, "Sync Story Starred Status failed: %@.", error.localizedDescription)
}
} }
} }
} }

View File

@ -1161,9 +1161,12 @@ private extension ReaderAPIAccountDelegate {
return return
} }
database.selectPendingStarredStatusArticleIDs() { result in Task { @MainActor in
do {
let pendingArticleIDs = (try await self.database.selectPendingStarredStatusArticleIDs()) ?? Set<String>()
MainActor.assumeIsolated {
@MainActor func process(_ pendingArticleIDs: Set<String>) { @MainActor func process(_ pendingArticleIDs: Set<String>) {
let updatableReaderUnreadArticleIDs = Set(articleIDs).subtracting(pendingArticleIDs) let updatableReaderUnreadArticleIDs = Set(articleIDs).subtracting(pendingArticleIDs)
@ -1197,15 +1200,11 @@ private extension ReaderAPIAccountDelegate {
} }
} }
switch result { process(pendingArticleIDs)
case .success(let pendingArticleIDs):
process(pendingArticleIDs) } catch {
case .failure(let error): os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
}
} }
} }
} }
} }

View File

@ -68,7 +68,7 @@ public actor SyncDatabase {
return syncStatusTable.selectPendingStarredStatusArticleIDs(database: database) return syncStatusTable.selectPendingStarredStatusArticleIDs(database: database)
} }
public func resetAllSelectedForProcessing() throws { public func resetAllSelectedForProcessing() throws {
guard let database else { guard let database else {
throw DatabaseError.suspended throw DatabaseError.suspended
@ -179,33 +179,6 @@ public extension SyncDatabase {
} }
} }
} }
nonisolated func selectPendingStarredStatusArticleIDs(completion: @escaping SyncStatusArticleIDsCompletionBlock) {
Task { @MainActor in
do {
if let articleIDs = try await self.selectPendingStarredStatusArticleIDs() {
completion(.success(articleIDs))
} else {
completion(.success(Set<String>()))
}
} catch {
completion(.failure(DatabaseError.suspended))
}
}
}
nonisolated func resetAllSelectedForProcessing(completion: DatabaseCompletionBlock? = nil) {
Task { @MainActor in
do {
try await self.resetAllSelectedForProcessing()
completion?(nil)
} catch {
completion?(DatabaseError.suspended)
}
}
}
} }
private extension SyncDatabase { private extension SyncDatabase {