@@ -631,48 +631,40 @@ public class CloudKitRomsSyncer: NSObject, RomsSyncing {
631631 }
632632
633633 return try await runOnQueue { [ self ] in
634+ let desiredKeys = [
635+ CloudKitSchema . ROMFields. md5,
636+ CloudKitSchema . ROMFields. title,
637+ CloudKitSchema . ROMFields. systemIdentifier,
638+ CloudKitSchema . ROMFields. fileSize,
639+ CloudKitSchema . ROMFields. originalFilename,
640+ CloudKitSchema . ROMFields. originalArtworkURL,
641+ CloudKitSchema . ROMFields. customArtworkURL,
642+ CloudKitSchema . ROMFields. isDeleted,
643+ CloudKitSchema . ROMFields. fileData,
644+ CloudKitSchema . ROMFields. isArchive,
645+ CloudKitSchema . ROMFields. relatedFilenames,
646+ CloudKitSchema . ROMFields. customArtworkAsset
647+ ]
648+
634649 let downloadStartTime = Date ( )
635650 var lastProgressUpdate = Date ( )
636651 var lastProgress : Double = 0
637652
638- return try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < CKRecord ? , Error > ) in
639- let op = CKFetchRecordsOperation ( recordIDs: [ recordID] )
640-
641- // Include all fields including assets
642- op. desiredKeys = [
643- CloudKitSchema . ROMFields. md5,
644- CloudKitSchema . ROMFields. title,
645- CloudKitSchema . ROMFields. systemIdentifier,
646- CloudKitSchema . ROMFields. fileSize,
647- CloudKitSchema . ROMFields. originalFilename,
648- CloudKitSchema . ROMFields. originalArtworkURL,
649- CloudKitSchema . ROMFields. customArtworkURL,
650- CloudKitSchema . ROMFields. isDeleted,
651- CloudKitSchema . ROMFields. fileData,
652- CloudKitSchema . ROMFields. isArchive,
653- CloudKitSchema . ROMFields. relatedFilenames,
654- CloudKitSchema . ROMFields. customArtworkAsset
655- ]
656-
657- // Track download progress with speed calculation
658- op. perRecordProgressBlock = { [ expectedSize] _, progress in
653+ let makeProgressBlock : ( ) -> ( ( CKRecord . ID , Double ) -> Void ) = {
654+ return { [ expectedSize] _, progress in
659655 let now = Date ( )
660656 let elapsed = now. timeIntervalSince ( downloadStartTime)
661657
662- // Calculate speed string
663658 var speedString : String ? = nil
664659 if elapsed > 0.5 && progress > 0.01 {
665660 if let totalSize = expectedSize, totalSize > 0 {
666- // Calculate based on known file size
667661 let downloadedBytes = Double ( totalSize) * progress
668662 let bytesPerSecond = downloadedBytes / elapsed
669663 speedString = Self . formatSpeed ( bytesPerSecond)
670664 } else {
671- // Estimate based on progress rate
672665 let progressDelta = progress - lastProgress
673666 let timeDelta = now. timeIntervalSince ( lastProgressUpdate)
674667 if timeDelta > 0.1 && progressDelta > 0 {
675- // Rough estimate assuming ~10MB average ROM
676668 let estimatedBytesPerSecond = ( progressDelta / timeDelta) * 10_000_000
677669 speedString = Self . formatSpeed ( estimatedBytesPerSecond)
678670 }
@@ -685,38 +677,73 @@ public class CloudKitRomsSyncer: NSObject, RomsSyncing {
685677 DLOG ( " [SYNC] Download progress: \( Int ( progress * 100 ) ) % \( speedString ?? " " ) " )
686678 progressHandler ? ( progress, speedString)
687679 }
680+ }
688681
689- var fetched : CKRecord ?
690- op. perRecordResultBlock = { _, result in
691- if case let . success( r) = result { fetched = r }
692- }
682+ /// Fetch from a specific database, returning nil for "not found" errors.
683+ func fetchFrom( _ db: CKDatabase ) async throws -> CKRecord ? {
684+ try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < CKRecord ? , Error > ) in
685+ let op = CKFetchRecordsOperation ( recordIDs: [ recordID] )
686+ op. desiredKeys = desiredKeys
687+ op. perRecordProgressBlock = makeProgressBlock ( )
693688
694- op. fetchRecordsCompletionBlock = { _, error in
695- if let error = error as? CKError {
696- if error. code == . unknownItem {
697- continuation. resume ( returning: nil )
698- } else if error. code == . partialFailure,
699- let partialErrors = error. partialErrorsByItemID,
700- partialErrors. count == 1 ,
701- let ( _, partialError) = partialErrors. first,
702- let partialCKError = partialError as? CKError ,
703- partialCKError. code == . unknownItem {
704- continuation. resume ( returning: nil )
705- } else {
689+ var fetched : CKRecord ?
690+ op. perRecordResultBlock = { _, result in
691+ if case let . success( r) = result { fetched = r }
692+ }
693+
694+ op. fetchRecordsCompletionBlock = { _, error in
695+ if let error = error as? CKError {
696+ if error. code == . unknownItem {
697+ continuation. resume ( returning: nil )
698+ } else if error. code == . partialFailure,
699+ let partialErrors = error. partialErrorsByItemID,
700+ partialErrors. count == 1 ,
701+ let ( _, partialError) = partialErrors. first,
702+ let partialCKError = partialError as? CKError ,
703+ partialCKError. code == . unknownItem {
704+ continuation. resume ( returning: nil )
705+ } else {
706+ continuation. resume ( throwing: CloudSyncError . cloudKitError ( error) )
707+ }
708+ } else if let error = error {
706709 continuation. resume ( throwing: CloudSyncError . cloudKitError ( error) )
710+ } else {
711+ continuation. resume ( returning: fetched)
707712 }
708- } else if let error = error {
709- continuation. resume ( throwing: CloudSyncError . cloudKitError ( error) )
710- } else {
711- continuation. resume ( returning: fetched)
712713 }
714+
715+ op. qualityOfService = . userInitiated
716+ db. add ( op)
713717 }
718+ }
714719
715- // Set quality of service for faster downloads
716- op. qualityOfService = . userInitiated
720+ // Try primary database first
721+ if let record = try await fetchFrom ( self . database) {
722+ return record
723+ }
717724
718- self . database. add ( op)
725+ // Try fallback databases (e.g. dev container in production/TestFlight builds)
726+ for fallbackDB in fallbackDatabases {
727+ do {
728+ if let record = try await fetchFrom ( fallbackDB) {
729+ DLOG ( " Fetched record with progress from fallback container: \( record. recordID. recordName) " )
730+ return record
731+ }
732+ } catch let syncError as CloudSyncError {
733+ // Unwrap CloudSyncError to check for badContainer
734+ if case . cloudKitError( let inner) = syncError,
735+ let ckError = inner as? CKError , ckError. code == . badContainer {
736+ DLOG ( " Fallback container not accessible (badContainer) — disabling for session " )
737+ iCloudConstants. invalidateFallbackContainers ( )
738+ break
739+ }
740+ DLOG ( " Fallback fetch with progress failed: \( syncError. localizedDescription) " )
741+ } catch {
742+ DLOG ( " Fallback fetch with progress failed: \( error. localizedDescription) " )
743+ }
719744 }
745+
746+ return nil
720747 }
721748 }
722749
0 commit comments