Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions Sources/SharingGRDBCore/CloudKit/Internal/MockCloudDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ package final class MockCloudDatabase: CloudDatabase {
case .ifServerRecordUnchanged:
for recordToSave in recordsToSave {
if let share = recordToSave as? CKShare {
let isSavingRootRecord = recordsToSave.contains(where: { $0.share?.recordID == share.recordID })
let isSavingRootRecord = recordsToSave.contains(where: {
$0.share?.recordID == share.recordID
})
let shareWasPreviouslySaved = storage[share.recordID.zoneID]?[share.recordID] != nil
guard shareWasPreviouslySaved || isSavingRootRecord
else {
Expand All @@ -102,7 +104,7 @@ package final class MockCloudDatabase: CloudDatabase {
continue
}
}

guard storage[recordToSave.recordID.zoneID] != nil
else {
saveResults[recordToSave.recordID] = .failure(CKError(.zoneNotFound))
Expand All @@ -124,6 +126,33 @@ package final class MockCloudDatabase: CloudDatabase {
return
}

func root(of record: CKRecord) -> CKRecord {
guard let parent = record.parent
else { return record }
return (storage[parent.recordID.zoneID]?[parent.recordID]).map(root) ?? record
}
func share(for rootRecord: CKRecord) -> CKShare? {
for (_, record) in storage[rootRecord.recordID.zoneID] ?? [:] {
guard record.recordID == rootRecord.share?.recordID
else { continue }
return record as? CKShare
}
return nil
}
let rootRecord = root(of: recordToSave)
let share = share(for: rootRecord)
let isSavingShare = recordsToSave.contains { $0.recordID == share?.recordID }
if
!isSavingShare,
!(recordToSave is CKShare),
let share,
!(share.publicPermission == .readWrite
|| share.currentUserParticipant?.permission == .readWrite)
{
saveResults[recordToSave.recordID] = .failure(CKError(.permissionFailure))
return
}

guard let copy = recordToSave.copy() as? CKRecord
else { fatalError("Could not copy CKRecord.") }
copy._recordChangeTag = UUID().uuidString
Expand All @@ -137,7 +166,7 @@ package final class MockCloudDatabase: CloudDatabase {
}
}

// TODO: this should merge copy's values into storage but not sure how right now.
// TODO: this should merge copy's values into storage but not sure how right now.
storage[recordToSave.recordID.zoneID]?[recordToSave.recordID] = copy
saveResults[recordToSave.recordID] = .success(copy)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ package final class MockSyncEngine: SyncEngineProtocol {
}
}

state.remove(pendingRecordZoneChanges: recordIDsSkipped.map { .saveRecord($0) })
state.remove(pendingRecordZoneChanges: recordsToSave.map { .saveRecord($0.recordID) })

return CKSyncEngine.RecordZoneChangeBatch(
recordsToSave: recordsToSave,
Expand Down
2 changes: 1 addition & 1 deletion Sources/SharingGRDBCore/CloudKit/Metadatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func defaultMetadatabase(
)
.execute(db)
}
migrator.registerMigration("Create PendingRecodZoneChanges Table") { db in
migrator.registerMigration("Create PendingRecordZoneChanges Table") { db in
try SQLQueryExpression("""
CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_pendingRecordZoneChanges" (
"pendingRecordZoneChange" BLOB NOT NULL
Expand Down
33 changes: 29 additions & 4 deletions Sources/SharingGRDBCore/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@
.select(\.pendingRecordZoneChange)
.fetchAll(db)
}
let changesByIsPrivate = Dictionary.init(grouping: pendingRecordZoneChanges) {
let changesByIsPrivate = Dictionary(grouping: pendingRecordZoneChanges) {
switch $0 {
case .deleteRecord(let recordID), .saveRecord(let recordID):
recordID.zoneID.ownerName == CKCurrentUserDefaultName
Expand Down Expand Up @@ -1265,10 +1265,32 @@
try open(table)
}

case .permissionFailure:
guard
let recordPrimaryKey = failedRecord.recordID.recordPrimaryKey,
let table = tablesByName[failedRecord.recordType]
else { continue }
func open<T: PrimaryKeyedTable>(_: T.Type) async throws {
do {
let serverRecord = try await container.sharedCloudDatabase.record(for: failedRecord.recordID)
upsertFromServerRecord(serverRecord, force: true)
} catch let error as CKError where error.code == .unknownItem {
try await userDatabase.write { db in
try T
.where { SQLQueryExpression("\($0.primaryKey) = \(bind: recordPrimaryKey)") }
.delete()
.execute(db)
}
}
}
await withErrorReporting(.sqliteDataCloudKitFailure) {
try await open(table)
}

case .networkFailure, .networkUnavailable, .zoneBusy, .serviceUnavailable,
.notAuthenticated, .operationCancelled, .batchRequestFailed,
.internalError, .partialFailure, .badContainer, .requestRateLimited, .missingEntitlement,
.permissionFailure, .invalidArguments, .resultsTruncated, .assetFileNotFound,
.invalidArguments, .resultsTruncated, .assetFileNotFound,
.assetFileModified, .incompatibleVersion, .constraintViolation, .changeTokenExpired,
.badDatabase, .quotaExceeded, .limitExceeded, .userDeletedZone, .tooManyParticipants,
.alreadyShared, .managedAccountRestricted, .participantMayNeedVerification,
Expand Down Expand Up @@ -1338,7 +1360,10 @@
}
}

private func upsertFromServerRecord(_ serverRecord: CKRecord) {
private func upsertFromServerRecord(
_ serverRecord: CKRecord,
force: Bool = false
) {
withErrorReporting(.sqliteDataCloudKitFailure) {
guard let table = tablesByName[serverRecord.recordType]
else {
Expand Down Expand Up @@ -1376,7 +1401,7 @@

func open<T: PrimaryKeyedTable>(_: T.Type) throws {
var columnNames = T.TableColumns.writableColumns.map(\.name)
if let metadata, let allFields = metadata._lastKnownServerRecordAllFields {
if !force, let metadata, let allFields = metadata._lastKnownServerRecordAllFields {
let row = try userDatabase.read { db in
try T.find(SQLQueryExpression("\(bind: metadata.recordPrimaryKey)")).fetchOne(db)
}
Expand Down
Loading
Loading