Skip to content

Library UUID#1469

Open
Hirobreak wants to merge 14 commits intoTortugaPower:developfrom
Hirobreak:library-uuid
Open

Library UUID#1469
Hirobreak wants to merge 14 commits intoTortugaPower:developfrom
Hirobreak:library-uuid

Conversation

@Hirobreak
Copy link
Copy Markdown
Contributor

Purpose

  • Assign uuids to library items, this way it is easier to map actions independently of where the items are

Approach

  • Generate optional uuids to avoid breaking current db status
  • Sync batches of library items, reporting their local uuid or reassigning the existing server-side uuid
  • Newer actions will attach its uuid

Things to be aware of / Things to focus on

  • Persisting both relativePath and uuid for existing actions

@Hirobreak Hirobreak force-pushed the library-uuid branch 3 times, most recently from 0d3a5f2 to adc160e Compare March 14, 2026 00:54
@GianniCarlo GianniCarlo marked this pull request as ready for review March 20, 2026 03:35
@GianniCarlo GianniCarlo requested a review from Copilot March 20, 2026 03:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .matchUuid job 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

  • PlayableItem decoding now requires a uuid key (decode(String.self, forKey: .uuid)), but existing persisted/archived PlayableItem payloads may not contain this field. That will throw and break decoding on upgrade. Use decodeIfPresent (and keep uuid nil/defaulted) to preserve backwards compatibility.
    Shared/Services/LibraryService+Sync.swift:320
  • parseSyncableItems now hard-requires a uuid in 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.

Comment thread Shared/Services/Sync/LibraryItemSyncOperation.swift
Comment thread Shared/Services/Sync/SyncJobScheduler.swift
Comment thread Shared/Services/Sync/SyncJobScheduler.swift
Comment thread Shared/SwiftData/MigrationPlan.swift
Comment thread Shared/CoreData/Backed-Models/LibraryItem+CoreDataProperties.swift
Comment thread Shared/Services/Sync/SyncJobScheduler.swift Outdated
Comment thread Shared/Services/Sync/SyncJobScheduler.swift
Comment thread Shared/Services/LibraryService+Sync.swift Outdated
Comment thread BookPlayer/Profile/Profile/QueuedSyncTasksView.swift Outdated
Comment thread Shared/SwiftData/MigrationPlan.swift
Copy link
Copy Markdown
Collaborator

@GianniCarlo GianniCarlo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI feedback

Comment thread Shared/CoreData/Backed-Models/LibraryItem+CoreDataProperties.swift
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),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Shared/Services/Sync/SyncTasksStorage.swift
Comment thread Shared/Services/Sync/SyncJobScheduler.swift

let modelContext = ModelContext(tasksDataManager.container)
modelContext.autosaveEnabled = true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.perform block uses unowned self which can crash if SyncJobScheduler is 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)

Comment thread Shared/CoreData/Migrations/ManualOrderMigrationUtils.swift Outdated
Comment thread BookPlayer/Library/ItemList/Models/LibraryNode.swift
Comment thread Shared/CoreData/CoreDataStack.swift Outdated
Comment thread BookPlayer/Library/ItemList/ItemListViewModel.swift Outdated
Comment thread Shared/CoreData/Lightweight-Models/PathUuidPair.swift Outdated
@Hirobreak Hirobreak force-pushed the library-uuid branch 2 times, most recently from 2cd9fce to 25e20bf Compare April 8, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants