Persistence¶
Labstream uses separate storage layers for secrets, preferences, and offline media.
flowchart TD
accTitle: Persistence boundaries
accDescr: Credentials and client identity live in Keychain, preferences in UserDefaults, and offline media including durable held range bodies plus bounded diagnostic artifacts in Application Support.
Keychain[Keychain] --> Sessions[Credentials, client identity, backend session selection]
Defaults[UserDefaults] --> Prefs[Settings and lightweight state]
Support[Application Support] --> Downloads[Offline files, index, resume blobs, held segments, side assets]
Support --> Diagnostics[Bounded opt-in diagnostic files]
Keychain¶
KeychainStore uses generic-password items with after-first-unlock accessibility. It stores:
- the Plex account token;
- the per-install
clientIdentifier; - selected backend and selected Plex server ID;
- Jellyfin/Emby server URL, access token, user ID, and server ID.
Not every one of those values is secret, but keeping each backend's session as one device-local credential set avoids splitting restoration state between storage systems.
Exactly one item is iCloud-synchronizable: the Plex account token. The per-install client identifier, backend/server selection, and Jellyfin/Emby session values remain device-local. When a synchronized Plex token is observed, any legacy local copy is retired; deleting the token removes both sync domains so a stale local token cannot be promoted after sign-out.
Do not write tokens to logs, diagnostics, issue templates, UserDefaults, or JSON profile indexes.
Credential and selected-backend/session writes fail closed if Keychain persistence fails. The non-secret Plex client identifier may fall back to a process-local value for one launch so background events can still drain; a later launch retries durable storage. A protected, backup-excluded secret-file fallback is allowed only for DEBUG simulator workflows. Native macOS development apps with non-canonical, per-worktree keychain service identities may opt into the same development file storage to avoid repeated prompts; the canonical shipping service continues to use Keychain and Plex-token sync.
UserDefaults¶
UserDefaults is for non-secret settings and lightweight state, such as:
- home/remote quality, adaptive bitrate, and download preferences;
- audio/subtitle language and playback-speed preferences;
- Up Next, skip, and Cinema placement preferences;
- feature toggles;
- diagnostic logging enabled/disabled and privacy-reduced MetricKit summaries;
- queue-paused and display state.
Selected backend is not a UserDefaults value; it is restored from the device-local Keychain item described above.
Use token-free, hashed, or backend-scoped identifiers when preferences need to be tied to a server/session.
App container¶
Application Support/Labstream stores:
Downloads/index.json, downloaded media, durable partials, protected URLSession resume blobs, completed held range bodies (*.range-held-*files manifested asheldRangeSegments), and cached posters/subtitles/trick-play/chapter assets;- bounded, rotated, already-redacted diagnostic JSONL files when diagnostic logging is enabled.
Split in-flight staging from durable held bodies. The app container's temporary directory
holds short-lived vp-range-body-* staging files for in-flight range responses; those
intermediates are consumed or swept and are not relaunch-authoritative. Completed
out-of-order segments are written under Downloads as *.range-held-* files and persisted
on the row as heldRangeSegments. They may be rehydrated across relaunch only when their
exact attempt, length, and validator checks still match; mismatches fail closed and
re-fetch. Do not treat held bodies as disposable temp files.
The download index is currently a schema-v4 versioned envelope. Schemas 1–3 are no longer
migrated: startup probes without mutating the root, drains every old OS task, moves the opaque
root to a durable quarantine, installs a protected empty schema-v4 root, and only then opens
admission. Unreadable or malformed current data fails closed rather than being guessed or reset.
Active rows carry a typed
DownloadAttemptID; asynchronous tasks, artifact reservations, and cleanup compare the
full rating-key/attempt key so stale work cannot mutate a retry or re-download of the same
item. Media and side-asset paths are persisted as validated one-level paths
relative to the Downloads directory, then re-hydrated against the live container. Never
persist an absolute sandbox path; the container location can change across installs.
The index contains no access tokens, but it is not anonymous: retry and cleanup require backend/server identity, a server base URL, MediaBrowser user ID where applicable, media/play-session IDs, source selection, route, validators, and server-prep job metadata. Treat it as private app data. Cached Jellyfin trick-play playlists are rewritten to local filenames so token-bearing server URLs are not retained.
Index writes are revisioned and serialized; filesystem publication/checkpoint/resume/delete
work is registered before it starts and reaches a terminal index outcome before its
lifecycle ticket is released. Required Jellyfin/Emby active-encoding or Emby Convert cleanup
also has an independent download-cleanup-intents.json journal containing an exact attempt,
credential-free server identity, and cleanup operation. The journal deliberately does not
share the versioned Downloads root: it lives in a protected sibling authority directory, so row
deletion or destructive schema reset cannot erase the only cleanup authority. Quarantined unsupported
roots are reclaimed on a utility queue after the new current root is durable and again on relaunch.
The compatibility emby-convert-cleanup.json queue has its own
EmbyConvertCleanupJournal persistence owner and private lock in that same authority directory;
its read-modify-write file I/O does not run while the broader DownloadStore index lock is held.
Persistence consumers name their contract explicitly: download/background-completion barriers, recoverable checkpoints, best-effort buffered diagnostics, or ephemeral state. The diagnostic JSONL sink batches on one serial queue, flushes on true aggregate inactivity, and rotates before an incoming complete line would cross the live-file cap; it never claims checkpoint or barrier durability.
The Downloads directory, resume blobs, and development credential artifacts are excluded
from backup. PMSKit's CredentialArtifactStorage applies appropriate file protection on
iOS/visionOS; macOS host paths do not claim iOS file-protection semantics.
Container paths are implementation details and should not appear in user-submitted diagnostic reports.
Publication identity boundary¶
The coordinated 1.6.1 publication uses the neutral org.labstream.Labstream identity throughout
the bundle, Keychain service, background sessions, diagnostics, and Spotlight domains. It does not
adopt persisted state from the retired private-TestFlight identity. Installing the neutral build is
therefore a new app installation: users sign in again, and prior downloads, background sessions,
Spotlight entries, and device-local preferences do not migrate.