diff --git a/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift b/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift index 95d667cc4..8382fe37d 100644 --- a/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift +++ b/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift @@ -113,6 +113,15 @@ public extension CloudKitZone { return CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID) } + func delay(for seconds: Double) async { + + await withCheckedContinuation { continuation in + self.retryIfPossible(after: seconds) { + continuation.resume() + } + } + } + func retryIfPossible(after: Double, block: @escaping () -> ()) { let delayTime = DispatchTime.now() + after DispatchQueue.main.asyncAfter(deadline: delayTime, execute: { @@ -144,6 +153,21 @@ public extension CloudKitZone { } } + /// Retrieves the zone record for this zone only. If the record isn't found it will be created. + func fetchZoneRecord() async throws -> CKRecordZone? { + + try await withCheckedThrowingContinuation { continuation in + self.fetchZoneRecord { result in + switch result { + case .success(let recordZone): + continuation.resume(returning: recordZone) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Retrieves the zone record for this zone only. If the record isn't found it will be created. func fetchZoneRecord(completion: @escaping (Result) -> Void) { let op = CKFetchRecordZonesOperation(recordZoneIDs: [zoneID]) @@ -185,6 +209,21 @@ public extension CloudKitZone { database?.add(op) } + /// Creates the zone record + func createZoneRecord() async throws { + + try await withCheckedThrowingContinuation { continuation in + self.createZoneRecord { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Creates the zone record func createZoneRecord(completion: @escaping (Result) -> Void) { guard let database = database else { @@ -219,7 +258,22 @@ public extension CloudKitZone { } } } - + + /// Issue a CKQuery and return the resulting CKRecords. + func query(_ ckQuery: CKQuery, desiredKeys: [String]? = nil) async throws -> [CKRecord] { + + try await withCheckedThrowingContinuation { continuation in + self.query(ckQuery, desiredKeys: desiredKeys) { result in + switch result { + case .success(let records): + continuation.resume(returning: records) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Issue a CKQuery and return the resulting CKRecords. func query(_ ckQuery: CKQuery, desiredKeys: [String]? = nil, completion: @escaping (Result<[CKRecord], Error>) -> Void) { var records = [CKRecord]() @@ -280,6 +334,21 @@ public extension CloudKitZone { database?.add(op) } + /// Query CKRecords using a CKQuery Cursor + func query(cursor: CKQueryOperation.Cursor, desiredKeys: [String]? = nil, carriedRecords: [CKRecord]) async throws -> [CKRecord] { + + try await withCheckedThrowingContinuation { continuation in + self.query(cursor: cursor, desiredKeys: desiredKeys, carriedRecords: carriedRecords) { result in + switch result { + case .success(let records): + continuation.resume(returning: records) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Query CKRecords using a CKQuery Cursor func query(cursor: CKQueryOperation.Cursor, desiredKeys: [String]? = nil, carriedRecords: [CKRecord], completion: @escaping (Result<[CKRecord], Error>) -> Void) { var records = carriedRecords @@ -340,6 +409,20 @@ public extension CloudKitZone { database?.add(op) } + /// Fetch a CKRecord by using its externalID + func fetch(externalID: String?) async throws -> CKRecord { + + try await withCheckedThrowingContinuation { continuation in + self.fetch(externalID: externalID) { result in + switch result { + case .success(let record): + continuation.resume(returning: record) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } /// Fetch a CKRecord by using its externalID func fetch(externalID: String?, completion: @escaping (Result) -> Void) { @@ -393,16 +476,61 @@ public extension CloudKitZone { } } + /// Save the CKRecord + func save (_ record: CKRecord) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.save(record) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Save the CKRecord func save(_ record: CKRecord, completion: @escaping (Result) -> Void) { modify(recordsToSave: [record], recordIDsToDelete: [], completion: completion) } + /// Save the CKRecords + func save(_ records: [CKRecord]) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.save(records) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Save the CKRecords func save(_ records: [CKRecord], completion: @escaping (Result) -> Void) { modify(recordsToSave: records, recordIDsToDelete: [], completion: completion) } + /// Saves or modifies the records as long as they are unchanged relative to the local version + func saveIfNew(_ records: [CKRecord]) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.saveIfNew(records) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Saves or modifies the records as long as they are unchanged relative to the local version func saveIfNew(_ records: [CKRecord], completion: @escaping (Result) -> Void) { let op = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: [CKRecord.ID]()) @@ -474,6 +602,21 @@ public extension CloudKitZone { database?.add(op) } + /// Save the CKSubscription + func save(_ subscription: CKSubscription) async throws -> CKSubscription { + + try await withCheckedThrowingContinuation { continuation in + self.save(subscription) { result in + switch result { + case .success(let subscription): + continuation.resume(returning: subscription) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Save the CKSubscription func save(_ subscription: CKSubscription, completion: @escaping (Result) -> Void) { database?.save(subscription) { [weak self] savedSubscription, error in @@ -511,6 +654,21 @@ public extension CloudKitZone { } } + /// Delete CKRecords using a CKQuery + func delete(ckQuery: CKQuery) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(ckQuery: ckQuery) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete CKRecords using a CKQuery func delete(ckQuery: CKQuery, completion: @escaping (Result) -> Void) { @@ -548,6 +706,21 @@ public extension CloudKitZone { database?.add(op) } + /// Delete CKRecords using a CKQuery + func delete(cursor: CKQueryOperation.Cursor, carriedRecords: [CKRecord]) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(cursor: cursor, carriedRecords: carriedRecords) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete CKRecords using a CKQuery func delete(cursor: CKQueryOperation.Cursor, carriedRecords: [CKRecord], completion: @escaping (Result) -> Void) { @@ -579,16 +752,61 @@ public extension CloudKitZone { database?.add(op) } + /// Delete a CKRecord using its recordID + func delete(recordID: CKRecord.ID) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(recordID: recordID) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete a CKRecord using its recordID func delete(recordID: CKRecord.ID, completion: @escaping (Result) -> Void) { modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion) } + /// Delete CKRecords + func delete(recordIDs: [CKRecord.ID]) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(recordIDs: recordIDs) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete CKRecords func delete(recordIDs: [CKRecord.ID], completion: @escaping (Result) -> Void) { modify(recordsToSave: [], recordIDsToDelete: recordIDs, completion: completion) } + /// Delete a CKRecord using its externalID + func delete(externalID: String?) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(externalID: externalID) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete a CKRecord using its externalID func delete(externalID: String?, completion: @escaping (Result) -> Void) { guard let externalID = externalID else { @@ -600,6 +818,21 @@ public extension CloudKitZone { modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion) } + /// Delete a CKSubscription + func delete(subscriptionID: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.delete(subscriptionID: subscriptionID) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Delete a CKSubscription func delete(subscriptionID: String, completion: @escaping (Result) -> Void) { database?.delete(withSubscriptionID: subscriptionID) { [weak self] _, error in @@ -626,6 +859,21 @@ public extension CloudKitZone { } } + /// Modify and delete the supplied CKRecords and CKRecord.IDs + func modify(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecord.ID]) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.modify(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Modify and delete the supplied CKRecords and CKRecord.IDs func modify(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecord.ID], completion: @escaping (Result) -> Void) { guard !(recordsToSave.isEmpty && recordIDsToDelete.isEmpty) else { @@ -735,6 +983,21 @@ public extension CloudKitZone { database?.add(op) } + /// Fetch all the changes in the CKZone since the last time we checked + @MainActor func fetchChangesInZone() async throws { + + try await withCheckedThrowingContinuation { continuation in + self.fetchChangesInZone { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + /// Fetch all the changes in the CKZone since the last time we checked @MainActor func fetchChangesInZone(completion: @escaping (Result) -> Void) { @@ -819,5 +1082,4 @@ public extension CloudKitZone { database?.add(op) } - }