Repository navigation
External Resource - #1523
Closed
Hirobreak wants to merge 29 commits into
Closed
External Resource#1523Hirobreak wants to merge 29 commits into
Hirobreak wants to merge 29 commits into
Conversation
GianniCarlo
reviewed
May 6, 2026
Hirobreak
force-pushed
the
external-resource
branch
2 times, most recently
from
May 13, 2026 21:27
3c6c771 to
77e7c44
Compare
Hirobreak
force-pushed
the
external-resource
branch
from
June 15, 2026 19:57
03d0720 to
a2001b7
Compare
…source update tasks
Hirobreak
force-pushed
the
external-resource
branch
from
July 7, 2026 19:20
6121610 to
948be81
Compare
Hirobreak
force-pushed
the
external-resource
branch
from
July 15, 2026 20:09
7498e4f to
e697842
Compare
Collaborator
|
Thanks @Hirobreak for this work! develop moved ~70 commits since this branch's base and the fork has maintainer edits disabled, so we've rebased it ourselves and continued in #1586 — your commits are preserved as the feature commit's author there. Closing this one in favor of #1586. |
GianniCarlo
added a commit
that referenced
this pull request
Sep 6, 2026
The #1523 squash-rebase resurrected two files that develop had deliberately deleted in the connection-flow and integrations reworks: JellyfinConnectionFormViewModel and JellyfinLibraryGridView (with its MockJellyfinLibraryViewModel). Both were git-tracked but never pbxproj-registered, so they never compiled — dead on arrival, including every conformance edit this branch faithfully made to the grid file's mock along the way. A three-signal sweep of all 33 PR-added Swift files (develop deletion history x pbxproj registration x external type references) found exactly these two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp
GianniCarlo
pushed a commit
that referenced
this pull request
Sep 9, 2026
… onto develop) Squash of Hirobreak's PR #1523 (29 commits) replayed onto current develop — the original branch is from a fork with maintainer edits disabled and had diverged 70 commits from its July base, so per-commit rebase was not viable. Conflict-resolution decisions: - The integration services the PR moved into Shared/ (Jellyfin + ABS connection services, IntegrationError) were grafted onto develop's newer store-based rewrites at their new homes — develop's implementations win, with the PR's deltas re-applied: public framework surface, the manualConnection/useConnection pin for the sync engine, deviceIdentifier, and the new fetchItem/updateItemProgress/updateItemsFromJellyfin endpoints (routed through develop's send()/validateAuthenticatedResponse so the session-expiry re-auth mapping applies to them too). - Server-GUID capture (the cross-device hostId identity) now happens on ALL sign-in paths: Jellyfin password AND Quick Connect (getPublicSystemInfo.id), ABS password login (server.id), always preserving a previously captured id. - The browser-SSO stack develop added (WebAuthenticating, OIDC flow, PKCE) is guarded iOS-only — it cannot compile for watchOS and the watch never signs in by itself. Its window lookup goes through a dynamic sharedApplication read because BookPlayerKit compiles extension-safe. - The app-layer helper closure the moved services depend on (IntegrationConnectionStore, IntegrationConnectionPayload, IntegrationHTTPClient, WebAuthenticating, PKCE) moved to Shared/Integrations with framework-target membership (both Kits). - The PR's SPM version bumps (jellyfin-sdk 0.6, Kingfisher 8) were dropped in favor of develop's existing pins — nothing in the PR requires them. - Localizations union-merged; develop's wording wins on duplicated keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp
GianniCarlo
added a commit
that referenced
this pull request
Sep 9, 2026
…contract Review findings from comparing the rebased #1523 against the Android implementation (shipped in android 1.1.3): - New IntegrationHostResolver in Shared/Integrations — the single resolution choke point, mirroring Android's ExternalServiceUtils.serverForResource: stable server GUID match (case-insensitive; Jellyfin reports lowercase hex, ABS uppercase UUIDs) → canonical URL key → nil. The previous per-call-site lookups compared serverId case-sensitively against raw url.absoluteString and then fell back to connections.first — the guess-a-server behavior the Android rework eliminated, which cross-device streams the wrong file or writes progress to the wrong server when provider ids collide. - Applied at every consumer: PlaybackService stream-URL building, SyncService.downloadRemoteFiles, ItemDetailsView host display, ExternalUpdateProgressOperation (progress pushes to an unresolvable host are now DISCARDED, never sent to the active connection), and ItemListViewModel (batch metadata refresh now groups resources by their resolved server; the remote-resume check pins the resource's own server). - hostId writes go through the new stableHostId helper (serverId ?? url.canonicalDedupKey) at all six import sites — raw absoluteString fallbacks break cross-device matching on trailing-slash/ port/case URL variants. - PlayableChapter's Codable now excludes externalUrl/externalHeaders: the headers carry the media server's live Authorization token, and encoded PlayableItems travel through the WatchConnectivity application context, which is persisted to disk on both devices. Both values are per-device and re-resolved locally at load time. - The error alert's Media Servers shortcut now also appears when NO source resolves (no local file, no cloud copy) — previously it was gated on externalUrl != nil, hiding it in exactly the connect-your-server case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp
GianniCarlo
added a commit
that referenced
this pull request
Sep 9, 2026
The #1523 squash-rebase resurrected two files that develop had deliberately deleted in the connection-flow and integrations reworks: JellyfinConnectionFormViewModel and JellyfinLibraryGridView (with its MockJellyfinLibraryViewModel). Both were git-tracked but never pbxproj-registered, so they never compiled — dead on arrival, including every conformance edit this branch faithfully made to the grid file's mock along the way. A three-signal sweep of all 33 PR-added Swift files (develop deletion history x pbxproj registration x external type references) found exactly these two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp
GianniCarlo
pushed a commit
that referenced
this pull request
Oct 5, 2026
Library items can now carry links to the Jellyfin and AudiobookShelf items they came from (Hardcover links move onto the same model), so a book imported from the user's own server streams from it, its link syncs to other devices, and listening progress goes back to the server. This is #1523 replayed onto develop, with the connection services moved into Shared for both Kits. Data model and tiers - CoreData v12 adds ExternalResource (provider name and id, hostId, sync status), cascading from LibraryItem. The v11->v12 mapping runs with inference off; tests pin its checksums, as a stale mapping breaks launch. - SwiftData V3 replaces SyncTasksStorage with one container keyed by queue; v2->v3 replays the old queue in FIFO order and backfills Hardcover links. An incompatible tasks store is moved aside (an older build over a newer schema must not lose queued deletes) instead of crash-looping launch; other failures, including a bug in the custom stage, still trap. - A new lite tier syncs the library without S3. Streaming gates on pro OR lite, pro wins when both are held, and streaming tiers import server books as linked references while other tiers download them. Concurrent sync engine - ConcurrenceService runs one worker per queue over a capped OperationQueue: "sync" stays serial for BookPlayer-server jobs, S3 PUTs get an uploadFile lane, and each provider a lane for progress pushes, so a slow media server never stalls library sync. - Tasks pop only on success and failures retry every 5s forever (Android parity), so permanent failures are consumed: a missing file, a 4xx on a presigned PUT, an unknown host, an expired session, a provider 4xx, and non-finite persisted progress that would crash-loop every pop. - synced:true and the PRO external_set pipe's uploaded:true are confirmed only after a real 2xx PUT, streamed from disk; confirmations retry and are then consumed rather than re-uploading a multi-GB file. - The tier policy is set before workers wake and rechecked per upload. Progress pushes run on every tier (they hit the user's own server); a lapse cancels only the server and upload lanes; logout wipes every queue and cancels in-flight work, down to the Task inside a sync operation, so nothing runs under the next account's token. - Operation state is lock-guarded, and pushes coalesce per providerId. Syncing links and resolving hosts - hostId now survives sync-down; decoding is per element, so one bad element no longer nils the list and feeds deletions; reconciliation drops only links that already synced; lastSyncedAt travels as epoch seconds (a Date crashed JSONSerialization); lookups are provider-scoped, as ids are unique only per provider; linked books key relativePath on the provider item id. - IntegrationHostResolver mirrors Android: server GUID (case-insensitive), then canonical URL key, then nil; never the first connection, which streamed the wrong file or pushed progress to the wrong server. An unresolvable host discards its pushes and fails downloads with a missing-connection error; old hostIds get no backfill by design. Playback and progress - Linked chapters always stream from the external URL with the server's headers, bound books included, never rerouted to S3 on refresh; every currentItem swap rebinds the chapter subscription. - Pushes are debounced and dropped if cancelled; ABS takes a 0-1 fraction (0-100 marked books finished). A resume prompt offers the server's position when it is ahead, whether or not the player is open. The buffering overlay shows only while a chapter streams. Security - PlayableChapter never encodes the external URL or headers: they hold the live Authorization token, and encoded items cross the WatchConnectivity context, which is persisted on both devices. - Custom proxy headers ride along, but Authorization is deduped case-insensitively and wins; presigned PUT URLs log only their path. Tests cover the task engine, host resolution, the resource service, the token never encoding, and the v11->v12 migration. Positional specifiers in sync_invitation_description are fixed in 23 locales (Lokalise needs it too).
GianniCarlo
pushed a commit
that referenced
this pull request
Oct 6, 2026
Library items can now carry links to the Jellyfin and AudiobookShelf items they came from (Hardcover links move onto the same model), so a book imported from the user's own server streams from it, its link syncs to other devices, and listening progress goes back to the server. This is #1523 replayed onto develop, with the connection services moved into Shared for both Kits. Data model and tiers - CoreData v12 adds ExternalResource (provider name and id, hostId, sync status), cascading from LibraryItem. The v11->v12 mapping runs with inference off; tests pin its checksums, as a stale mapping breaks launch. - SwiftData V3 replaces SyncTasksStorage with one container keyed by queue; v2->v3 replays the old queue in FIFO order and backfills Hardcover links. An incompatible tasks store is moved aside (an older build over a newer schema must not lose queued deletes) instead of crash-looping launch; other failures, including a bug in the custom stage, still trap. - A new lite tier syncs the library without S3. Streaming gates on pro OR lite, pro wins when both are held, and streaming tiers import server books as linked references while other tiers download them. Concurrent sync engine - ConcurrenceService runs one worker per queue over a capped OperationQueue: "sync" stays serial for BookPlayer-server jobs, S3 PUTs get an uploadFile lane, and each provider a lane for progress pushes, so a slow media server never stalls library sync. - Tasks pop only on success and failures retry every 5s forever (Android parity), so permanent failures are consumed: a missing file, a 4xx on a presigned PUT, an unknown host, an expired session, a provider 4xx, and non-finite persisted progress that would crash-loop every pop. - synced:true and the PRO external_set pipe's uploaded:true are confirmed only after a real 2xx PUT, streamed from disk; confirmations retry and are then consumed rather than re-uploading a multi-GB file. - The tier policy is set before workers wake and rechecked per upload. Progress pushes run on every tier (they hit the user's own server); a lapse cancels only the server and upload lanes; logout wipes every queue and cancels in-flight work, down to the Task inside a sync operation, so nothing runs under the next account's token. - Operation state is lock-guarded, and pushes coalesce per providerId. Syncing links and resolving hosts - hostId now survives sync-down; decoding is per element, so one bad element no longer nils the list and feeds deletions; reconciliation drops only links that already synced; lastSyncedAt travels as epoch seconds (a Date crashed JSONSerialization); lookups are provider-scoped, as ids are unique only per provider; linked books key relativePath on the provider item id. - IntegrationHostResolver mirrors Android: server GUID (case-insensitive), then canonical URL key, then nil; never the first connection, which streamed the wrong file or pushed progress to the wrong server. An unresolvable host discards its pushes and fails downloads with a missing-connection error; old hostIds get no backfill by design. Playback and progress - Linked chapters always stream from the external URL with the server's headers, bound books included, never rerouted to S3 on refresh; every currentItem swap rebinds the chapter subscription. - Pushes are debounced and dropped if cancelled; ABS takes a 0-1 fraction (0-100 marked books finished). A resume prompt offers the server's position when it is ahead, whether or not the player is open. The buffering overlay shows only while a chapter streams. Security - PlayableChapter never encodes the external URL or headers: they hold the live Authorization token, and encoded items cross the WatchConnectivity context, which is persisted on both devices. - Custom proxy headers ride along, but Authorization is deduped case-insensitively and wins; presigned PUT URLs log only their path. Tests cover the task engine, host resolution, the resource service, the token never encoding, and the v11->v12 migration. Positional specifiers in sync_invitation_description are fixed in 23 locales (Lokalise needs it too).
GianniCarlo
pushed a commit
that referenced
this pull request
Oct 7, 2026
Library items can now carry links to the Jellyfin and AudiobookShelf items they came from (Hardcover links move onto the same model), so a book imported from the user's own server streams from it, its link syncs to other devices, and listening progress goes back to the server. This is #1523 replayed onto develop, with the connection services moved into Shared for both Kits. Data model and tiers - CoreData v12 adds ExternalResource (provider name and id, hostId, sync status), cascading from LibraryItem. The v11->v12 mapping runs with inference off; tests pin its checksums, as a stale mapping breaks launch. - SwiftData V3 replaces SyncTasksStorage with one container keyed by queue; v2->v3 replays the old queue in FIFO order and backfills Hardcover links. An incompatible tasks store is moved aside (an older build over a newer schema must not lose queued deletes) instead of crash-looping launch; other failures, including a bug in the custom stage, still trap. - A new lite tier syncs the library without S3. Streaming gates on pro OR lite, pro wins when both are held, and streaming tiers import server books as linked references while other tiers download them. Concurrent sync engine - ConcurrenceService runs one worker per queue over a capped OperationQueue: "sync" stays serial for BookPlayer-server jobs, S3 PUTs get an uploadFile lane, and each provider a lane for progress pushes, so a slow media server never stalls library sync. - Tasks pop only on success and failures retry every 5s forever (Android parity), so permanent failures are consumed: a missing file, a 4xx on a presigned PUT, an unknown host, an expired session, a provider 4xx, and non-finite persisted progress that would crash-loop every pop. - synced:true and the PRO external_set pipe's uploaded:true are confirmed only after a real 2xx PUT, streamed from disk; confirmations retry and are then consumed rather than re-uploading a multi-GB file. - The tier policy is set before workers wake and rechecked per upload. Progress pushes run on every tier (they hit the user's own server); a lapse cancels only the server and upload lanes; logout wipes every queue and cancels in-flight work, down to the Task inside a sync operation, so nothing runs under the next account's token. - Operation state is lock-guarded, and pushes coalesce per providerId. Syncing links and resolving hosts - hostId now survives sync-down; decoding is per element, so one bad element no longer nils the list and feeds deletions; reconciliation drops only links that already synced; lastSyncedAt travels as epoch seconds (a Date crashed JSONSerialization); lookups are provider-scoped, as ids are unique only per provider; linked books key relativePath on the provider item id. - IntegrationHostResolver mirrors Android: server GUID (case-insensitive), then canonical URL key, then nil; never the first connection, which streamed the wrong file or pushed progress to the wrong server. An unresolvable host discards its pushes and fails downloads with a missing-connection error; old hostIds get no backfill by design. Playback and progress - Linked chapters always stream from the external URL with the server's headers, bound books included, never rerouted to S3 on refresh; every currentItem swap rebinds the chapter subscription. - Pushes are debounced and dropped if cancelled; ABS takes a 0-1 fraction (0-100 marked books finished). A resume prompt offers the server's position when it is ahead, whether or not the player is open. The buffering overlay shows only while a chapter streams. Security - PlayableChapter never encodes the external URL or headers: they hold the live Authorization token, and encoded items cross the WatchConnectivity context, which is persisted on both devices. - Custom proxy headers ride along, but Authorization is deduped case-insensitively and wins; presigned PUT URLs log only their path. Tests cover the task engine, host resolution, the resource service, the token never encoding, and the v11->v12 migration. Positional specifiers in sync_invitation_description are fixed in 23 locales (Lokalise needs it too).
GianniCarlo
pushed a commit
that referenced
this pull request
Oct 10, 2026
Library items can now carry links to the Jellyfin and AudiobookShelf items they came from (Hardcover links move onto the same model), so a book imported from the user's own server streams from it, its link syncs to other devices, and listening progress goes back to the server. This is #1523 replayed onto develop, with the connection services moved into Shared for both Kits. Data model and tiers - CoreData v12 adds ExternalResource (provider name and id, hostId, sync status), cascading from LibraryItem. The v11->v12 mapping runs with inference off; tests pin its checksums, as a stale mapping breaks launch. - SwiftData V3 replaces SyncTasksStorage with one container keyed by queue; v2->v3 replays the old queue in FIFO order and backfills Hardcover links. An incompatible tasks store is moved aside (an older build over a newer schema must not lose queued deletes) instead of crash-looping launch; other failures, including a bug in the custom stage, still trap. - A new lite tier syncs the library without S3. Streaming gates on pro OR lite, pro wins when both are held, and streaming tiers import server books as linked references while other tiers download them. Concurrent sync engine - ConcurrenceService runs one worker per queue over a capped OperationQueue: "sync" stays serial for BookPlayer-server jobs, S3 PUTs get an uploadFile lane, and each provider a lane for progress pushes, so a slow media server never stalls library sync. - Tasks pop only on success and failures retry every 5s forever (Android parity), so permanent failures are consumed: a missing file, a 4xx on a presigned PUT, an unknown host, an expired session, a provider 4xx, and non-finite persisted progress that would crash-loop every pop. - synced:true and the PRO external_set pipe's uploaded:true are confirmed only after a real 2xx PUT, streamed from disk; confirmations retry and are then consumed rather than re-uploading a multi-GB file. - The tier policy is set before workers wake and rechecked per upload. Progress pushes run on every tier (they hit the user's own server); a lapse cancels only the server and upload lanes; logout wipes every queue and cancels in-flight work, down to the Task inside a sync operation, so nothing runs under the next account's token. - Operation state is lock-guarded, and pushes coalesce per providerId. Syncing links and resolving hosts - hostId now survives sync-down; decoding is per element, so one bad element no longer nils the list and feeds deletions; reconciliation drops only links that already synced; lastSyncedAt travels as epoch seconds (a Date crashed JSONSerialization); lookups are provider-scoped, as ids are unique only per provider; linked books key relativePath on the provider item id. - IntegrationHostResolver mirrors Android: server GUID (case-insensitive), then canonical URL key, then nil; never the first connection, which streamed the wrong file or pushed progress to the wrong server. An unresolvable host discards its pushes and fails downloads with a missing-connection error; old hostIds get no backfill by design. Playback and progress - Linked chapters always stream from the external URL with the server's headers, bound books included, never rerouted to S3 on refresh; every currentItem swap rebinds the chapter subscription. - Pushes are debounced and dropped if cancelled; ABS takes a 0-1 fraction (0-100 marked books finished). A resume prompt offers the server's position when it is ahead, whether or not the player is open. The buffering overlay shows only while a chapter streams. Security - PlayableChapter never encodes the external URL or headers: they hold the live Authorization token, and encoded items cross the WatchConnectivity context, which is persisted on both devices. - Custom proxy headers ride along, but Authorization is deduped case-insensitively and wins; presigned PUT URLs log only their path. Tests cover the task engine, host resolution, the resource service, the token never encoding, and the v11->v12 migration. Positional specifiers in sync_invitation_description are fixed in 23 locales (Lokalise needs it too).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Related tasks
Approach
Screenshots