Conversation
0d3a5f2 to
adc160e
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces UUIDs for library items and sync tasks to make sync actions resilient to path changes, and adds a “match UUIDs” job to reconcile/assign UUIDs between local and server state.
Changes:
- Add UUID fields across CoreData models, lightweight models, playback models, and sync task models.
- Update sync scheduling/storage/execution to carry UUIDs (and add a new
.matchUuidjob type + API endpoint). - Introduce CoreData v11 and a SwiftData (sync-tasks) schema v2 with a migration plan.
Reviewed changes
Copilot reviewed 60 out of 61 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| Shared/SwiftData/TasksDataManager.swift | Adds migration plan + new task model types |
| Shared/SwiftData/SwiftDataModels+Extensions.swift | Adds uuid/uuids payload fields for task serialization |
| Shared/SwiftData/Models/SyncTasksModels.swift | Switches to SchemaV2 typealiases |
| Shared/SwiftData/Models/SyncJobType.swift | Adds .matchUuid job type |
| Shared/SwiftData/Models/SchemaV2SyncTasksModels.swift | Defines v2 SwiftData task models with uuid support |
| Shared/SwiftData/Models/SchemaV1SyncTasksModels.swift | Preserves v1 SwiftData task models for migration |
| Shared/SwiftData/MigrationPlan.swift | Adds custom SwiftData migration stage and UUID match task creation |
| Shared/Services/Sync/SyncTasksStorage.swift | Requires uuid in stored tasks; uses uuid for update coalescing/progress |
| Shared/Services/Sync/SyncService.swift | Expands API to accept uuid and schedules match-uuid jobs |
| Shared/Services/Sync/SyncJobScheduler.swift | Persists uuid with tasks; adds match-uuid job + response handling |
| Shared/Services/Sync/Model/SyncTask.swift | Adds uuid to SyncTask / SyncTaskReference / SyncErrorInfo |
| Shared/Services/Sync/Model/SyncableItem.swift | Adds uuid decoding/storage + copy(uuid:) helper |
| Shared/Services/Sync/Model/SyncableBookmark.swift | Adds optional uuid to bookmark sync model |
| Shared/Services/Sync/LibraryItemSyncOperation.swift | Passes uuid to API calls; adds match-uuid operation |
| Shared/Services/Sync/LibraryAPI.swift | Adds uuid params to endpoints + new /uuids endpoint |
| Shared/Services/Sync/ApiResponse.swift | Introduces match-uuid response models |
| Shared/Services/PlaybackService.swift | Propagates uuid into playback items |
| Shared/Services/LibraryService+Sync.swift | Generates UUID batches; stores server-provided UUIDs |
| Shared/Services/LibraryService.swift | Adds uuid to fetch parsing, move APIs, folder/book creation, publishers |
| Shared/CoreData/Migrations/Migration Policies/UUIDMigrationPolicy.swift | Adds CoreData migration policy to populate uuid |
| Shared/CoreData/Migrations/ManualOrderMigrationUtils.swift | Adds utility to populate uuids (currently unused) |
| Shared/CoreData/Migrations/DBVersion.swift | Adds DB v11 mapping model name |
| Shared/CoreData/Migrations/DataMigrationManager.swift | Adjusts post-migration extra work gating |
| Shared/CoreData/Lightweight-Models/SimpleLibraryItem.swift | Adds uuid to lightweight library item |
| Shared/CoreData/Lightweight-Models/SimpleBookmark.swift | Adds uuid to lightweight bookmark |
| Shared/CoreData/Lightweight-Models/PlayableItem.swift | Adds uuid to Codable PlayableItem |
| Shared/CoreData/Lightweight-Models/PathUuidPair.swift | Adds path+uuid pair type for moves |
| Shared/CoreData/CoreDataStack.swift | Enables inferred mapping model automatically |
| Shared/CoreData/BookPlayer.xcdatamodeld/Audiobook Player 11.xcdatamodel/contents | Adds optional uuid attribute to LibraryItem entity |
| Shared/CoreData/BookPlayer.xcdatamodeld/.xccurrentversion | Bumps current CoreData model to v11 |
| Shared/CoreData/Backed-Models/LibraryItem+CoreDataProperties.swift | Adds uuid property to LibraryItem |
| Shared/CoreData/Backed-Models/Folder+CoreDataClass.swift | Assigns uuid to new folders; sync update writes uuid |
| Shared/CoreData/Backed-Models/Book+CoreDataClass.swift | Assigns uuid to decoded books; sync update writes uuid |
| BookPlayerWidgets/Phone/RecentBooks/RecentBooksWidgetView.swift | Updates preview init to include uuid |
| BookPlayerWatch/LocalPlayback/Player/PlayerManager.swift | Passes uuid to remote URL fetch + bookmark creation |
| BookPlayerWatch/LocalPlayback/Bookmarks/BookmarksViewModel.swift | Passes uuid through bookmark creation/scheduling |
| BookPlayerWatch/ItemList/ItemCellView.swift | Updates preview init to include uuid |
| BookPlayerWatch/ExtensionDelegate.swift | Injects dataManager into SyncService setup |
| BookPlayer/Settings/Storage/StorageViewModel.swift | Updates moveItems calls to pass PathUuidPair |
| BookPlayer/Settings/Sections/DebugFileTransferable.swift | Updates debug dump to show uuid (not path) |
| BookPlayer/Services/CarPlayManager.swift | Passes uuid through bookmark creation/scheduling |
| BookPlayer/Profile/Profile/QueuedSyncTasksView.swift | Displays queued jobs; switches progress keying to uuid |
| BookPlayer/Profile/Profile/ProfileSyncTasksSectionView.swift | Updates SyncService setup signature |
| BookPlayer/Player/Views/ButtonFree/ButtonFreeViewModel.swift | Passes uuid through bookmark creation/scheduling |
| BookPlayer/Player/Views/Bookmarks/BookmarksViewModel.swift | Schedules bookmark updates with uuid |
| BookPlayer/Player/Views/Bookmarks/BookmarksView.swift | Updates previews to include uuid |
| BookPlayer/Player/ViewModels/PlayerViewModel.swift | Passes uuid through bookmark creation/scheduling |
| BookPlayer/Player/PlayerManager.swift | Passes uuid to remote URL fetch + bookmark creation |
| BookPlayer/Library/ItemList/Views/FolderView.swift | Adds uuid into navigation node |
| BookPlayer/Library/ItemList/Views/DynamicAccessibilityLabel.swift | Preserves uuid when rebuilding accessibility label items |
| BookPlayer/Library/ItemList/Views/BookView.swift | Updates preview init + SyncService setup signature |
| BookPlayer/Library/ItemList/Models/LibraryNode.swift | Adds uuid to navigation node model |
| BookPlayer/Library/ItemList/Models/ImportOperationState.swift | Switches identifiers to PathUuidPair |
| BookPlayer/Library/ItemList/LibraryRootView.swift | Tracks/imports PathUuidPair; schedules moves with uuid |
| BookPlayer/Library/ItemList/ItemListViewModel.swift | Uses PathUuidPair for move flows; adds syncUuids trigger |
| BookPlayer/Library/ItemList/ItemListView+Alerts.swift | Adapts alerts to new PathUuidPair identifiers |
| BookPlayer/Library/ItemDetails/ItemDetailsViewModel.swift | Passes uuid in rename/artwork upload scheduling |
| BookPlayer/AppIntents/CreateBookmarkIntent.swift | Passes uuid through bookmark creation/scheduling |
| BookPlayer/AppDelegate.swift | Injects CoreData context for SwiftData migration; updates SyncService wiring |
| BookPlayer.xcodeproj/project.pbxproj | Adds new files/resources (models, mapping, migration, responses) |
Comments suppressed due to low confidence (2)
Shared/CoreData/Lightweight-Models/PlayableItem.swift:231
PlayableItemdecoding now requires auuidkey (decode(String.self, forKey: .uuid)), but existing persisted/archivedPlayableItempayloads may not contain this field. That will throw and break decoding on upgrade. UsedecodeIfPresent(and keepuuidnil/defaulted) to preserve backwards compatibility.
Shared/Services/LibraryService+Sync.swift:320parseSyncableItemsnow hard-requires auuidin the fetched dictionary (guard let uuid = ...). If the server or local store returns items without a uuid (which the PR description suggests should be tolerated), those items will be dropped from sync processing. Consider making uuid optional here and defaulting to an empty string (or generating one) instead of returning nil.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| currentTime: try values.decode(TimeInterval.self, forKey: .currentTime), | ||
| duration: try values.decode(TimeInterval.self, forKey: .duration), | ||
| relativePath: try values.decode(String.self, forKey: .relativePath), | ||
| uuid: try values.decode(String.self, forKey: .uuid), |
There was a problem hiding this comment.
CRITICAL: Codable decode will crash on old persisted data
try values.decode(String.self, forKey: .uuid) will throw if the key is missing. Old PlayableItem data encoded before this PR (e.g., via WatchConnectivity or UserDefaults) won't have a "uuid" key. Since the property type is String? (optional), this should use:
uuid: try values.decodeIfPresent(String.self, forKey: .uuid),to maintain backward compatibility.
|
|
||
| let modelContext = ModelContext(tasksDataManager.container) | ||
| modelContext.autosaveEnabled = true | ||
|
|
There was a problem hiding this comment.
CRITICAL: Threading issues with ModelContext outside actor isolation
This ModelContext is created from operationTask.completionBlock which runs on an arbitrary thread. ModelContext is not thread-safe and should respect SwiftData's actor isolation.
Additional concerns in this method:
- The CoreData
context.performblock usesunowned selfwhich can crash ifSyncJobScheduleris deallocated while the operation is in flight try? context.save()silently swallows CoreData save errors- The CoreData and SwiftData saves are not coordinated — if one succeeds and the other fails, state becomes inconsistent (items have updated UUIDs in CoreData but SwiftData tasks still reference old ones, or vice versa)
2cd9fce to
25e20bf
Compare
Purpose
Approach
Things to be aware of / Things to focus on