mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-06-18 10:19:03 -04:00
297 lines
14 KiB
Markdown
297 lines
14 KiB
Markdown
# Drift Detection
|
|
|
|
**Source:** `src/lib/server/jobs/handlers/arrDrift.ts`,
|
|
`src/lib/server/jobs/handlers/arrSync.ts`,
|
|
`src/lib/server/db/queries/arrDriftSettings.ts`,
|
|
`src/lib/server/db/queries/arrDriftStatus.ts`,
|
|
`src/lib/server/drift/customFormats.ts`,
|
|
`src/lib/server/drift/qualityProfiles.ts`,
|
|
`src/lib/server/drift/delayProfiles.ts`,
|
|
`src/lib/server/drift/mediaManagement.ts`,
|
|
`src/lib/server/drift/display.ts`,
|
|
`src/routes/arr/[id]/drift/+page.svelte`,
|
|
`src/routes/arr/[id]/drift/+page.server.ts`,
|
|
`src/routes/arr/[id]/drift/components/DriftFieldDiffTable.svelte`,
|
|
`src/routes/arr/[id]/sync/+page.server.ts`,
|
|
`src/routes/arr/[id]/sync/components/QualityProfiles.svelte`,
|
|
`src/routes/arr/[id]/sync/components/DelayProfiles.svelte`
|
|
|
|
Drift detection checks whether an Arr instance still matches the configuration
|
|
Profilarr would sync now. It is observational: it does not write to Arr, repair
|
|
config, delete stale items, or replace cleanup.
|
|
|
|
Drift detection currently covers custom formats, quality profiles, the default
|
|
delay profile, and media management media settings, naming, and quality
|
|
definitions.
|
|
|
|
## Job
|
|
|
|
The scheduled job type is `arr.drift` with payload `{ instanceId }`.
|
|
|
|
Scheduling is per Arr instance and uses `arr_drift_settings`:
|
|
|
|
| Field | Purpose |
|
|
| ------------- | ------------------------------------- |
|
|
| `enabled` | Master switch for drift detection |
|
|
| `cron` | Cron expression for scheduled checks |
|
|
| `next_run_at` | Next scheduled run stored as UTC text |
|
|
|
|
The job queue uses dedupe key `arr.drift:{instanceId}` so each instance has at
|
|
most one scheduled drift job.
|
|
|
|
Current handler behavior:
|
|
|
|
- invalid instance ids fail
|
|
- missing instances fail
|
|
- missing or disabled settings cancel the job
|
|
- unsupported Arr types are skipped
|
|
- enabled jobs compare custom formats, quality profiles, delay profiles, media
|
|
management media settings, media management naming, and media management
|
|
quality definitions, store the latest result, and return success
|
|
- drift-detected and failed runs notify subscribed services when the current
|
|
drift or error hash has not already been notified
|
|
- scheduled jobs calculate and store the next run before returning
|
|
|
|
## Latest Status
|
|
|
|
Drift stores only the latest result per Arr instance in `arr_drift_status`.
|
|
Job run history remains the operational history.
|
|
|
|
| Field | Purpose |
|
|
| -------------------------- | ---------------------------------------------------- |
|
|
| `status` | `never_checked`, `clean`, `drift_detected`, `failed` |
|
|
| `last_checked_at` | Last completed check time |
|
|
| `counts_json` | Count summary by drift section |
|
|
| `diff_json` | Structured latest drift result |
|
|
| `diff_hash` | Stable hash of the structured drift result |
|
|
| `last_notified_hash` | Last drift hash sent as a notification |
|
|
| `last_notified_at` | Last drift notification time |
|
|
| `last_error` | Latest failure detail |
|
|
| `error_hash` | Stable hash of the latest failure detail |
|
|
| `last_notified_error_hash` | Last failure hash sent as a notification |
|
|
|
|
## Notifications
|
|
|
|
Drift emits notification events through the shared notification manager:
|
|
|
|
| Event | Trigger | Severity |
|
|
| -------------------- | ------------------------------------- | --------- |
|
|
| `arr.drift.detected` | Latest drift hash has not been sent | `warning` |
|
|
| `arr.drift.failed` | Latest failure hash has not been sent | `error` |
|
|
|
|
Detected notifications emit one section block per drift category (Custom
|
|
Format, Quality Profile, Delay Profile, Media Management) listing up to 15
|
|
displayable drift entities total. If more entities exist, an additional `More`
|
|
block summarises the remainder as `+N more`. Discord renders each section as a
|
|
code-block field. Webhook receives the full payload. Summary-tier services such
|
|
as Ntfy and Telegram show only the title because section blocks are omitted.
|
|
|
|
Failed notifications use the first error line as the message and include the
|
|
full error text in an `Error` section.
|
|
|
|
## Custom Formats
|
|
|
|
Custom format drift compares Profilarr-managed custom formats referenced by
|
|
synced quality profile selections.
|
|
|
|
Comparison rules:
|
|
|
|
- build expected custom formats with the same transformer used by sync
|
|
- fetch actual custom formats from Arr
|
|
- match by custom format name
|
|
- ignore Arr ids
|
|
- compare `includeCustomFormatWhenRenaming`
|
|
- compare normalized specifications and fields
|
|
- ignore unmanaged extra Arr custom formats
|
|
|
|
## Quality Profiles
|
|
|
|
Quality profile drift compares selected Profilarr-managed profiles by name.
|
|
Expected profiles are built through the same quality profile transformer used
|
|
by sync, with actual Arr custom format ids used to resolve scoring rows.
|
|
|
|
Comparison rules:
|
|
|
|
- report selected quality profiles missing from Arr
|
|
- ignore Arr profile ids
|
|
- compare `upgradeAllowed`, `cutoff`, `minFormatScore`,
|
|
`cutoffFormatScore`, and `minUpgradeFormatScore`
|
|
- compare Radarr language; ignore language for Sonarr
|
|
- compare normalized quality item order, grouping, and allowed flags
|
|
- compare custom format scores for custom formats managed by that profile,
|
|
including explicit zero scores
|
|
- report an expected managed custom format as missing from scoring if the custom
|
|
format is missing from Arr
|
|
- ignore unmanaged custom format scoring rows with score 0
|
|
- report unmanaged custom format scoring rows with nonzero scores because they
|
|
affect profile behavior
|
|
|
|
## Delay Profiles
|
|
|
|
Delay profile drift compares the selected Profilarr delay profile against Arr's
|
|
default delay profile (`id=1`). This mirrors sync, which always overwrites the
|
|
default Arr profile instead of creating or matching by name.
|
|
|
|
Comparison rules:
|
|
|
|
- build expected delay profile with the same transformer used by sync
|
|
- no selected delay profile is clean
|
|
- report the selected delay profile as missing if Arr does not return `id=1`
|
|
- ignore non-default extra Arr delay profiles
|
|
- compare id, derived protocol, delays, bypass fields, minimum custom format
|
|
score, order, and tags
|
|
|
|
## Media Management
|
|
|
|
Media management drift compares configured media settings, naming, and quality
|
|
definition selections against Arr's media management, naming, and quality
|
|
definition configs.
|
|
|
|
Comparison rules:
|
|
|
|
- build expected media settings, naming, and quality definitions with the same
|
|
transformers used by sync
|
|
- no selected media settings, naming, or quality definitions config is clean
|
|
- missing selected PCD cache or config fails the drift check
|
|
- compare `downloadPropersAndRepacks` and `enableMediaInfo`
|
|
- compare Radarr naming fields: rename, illegal-character replacement, colon
|
|
replacement, movie format, and movie folder format
|
|
- compare Sonarr naming fields: rename, illegal-character replacement, colon
|
|
replacement, custom colon replacement, multi-episode style, episode formats,
|
|
series folder format, and season folder format
|
|
- normalize Sonarr Arr enum integers back to semantic strings before comparing
|
|
- compare quality definition `minSize`, `maxSize`, and `preferredSize`
|
|
- normalize PCD quality definition `0` size values to Arr `null` for unlimited
|
|
maximum and preferred sizes
|
|
- ignore unmapped PCD quality definitions, mapped definitions missing in Arr,
|
|
extra Arr quality definitions, and unmanaged Arr fields
|
|
|
|
## Display Formatter
|
|
|
|
`src/lib/server/drift/display.ts` maps the raw `diff_json` stored in
|
|
`arr_drift_status` into a typed list of `DriftDisplayEntity` objects consumed
|
|
by the page. One entity is one drifted managed item (e.g. one custom format).
|
|
Each entity carries:
|
|
|
|
- `section` and `sectionLabel` (e.g. `custom_formats` / `Custom Format`)
|
|
- `state` and `stateLabel` (`missing` / `modified` / `extra`)
|
|
- `tone` for badge color signaling
|
|
- `summary` (one-line description, e.g. `3 changes detected`)
|
|
- `changes[]`: per-field `DriftDisplayChange` rows with `label`, optional
|
|
`detail`, and `expected` / `actual` `DriftDisplayValue`s. Values carry
|
|
`text`, optional `mono`, and optional `tone`.
|
|
|
|
For custom formats the formatter resolves Arr enum ids back to friendly names
|
|
(sources, resolutions, indexer flags, languages, release types, quality
|
|
modifiers), formats sizes in human-readable bytes, and turns specification
|
|
paths into labeled changes (negate / required / per-field changes / missing
|
|
condition / extra condition).
|
|
|
|
For quality profiles the formatter turns settings, language, qualities, and
|
|
custom format score paths into labeled changes. Quality profile score rows show
|
|
expected and actual score values, missing custom formats, missing score rows,
|
|
and unmanaged nonzero score rows.
|
|
|
|
For delay profiles the formatter turns the derived protocol, delays, bypass
|
|
flags, minimum score, order, and tags into friendly field rows.
|
|
|
|
For media management the formatter turns media settings, naming, and quality
|
|
definition changes into friendly field rows.
|
|
|
|
Display types live in `src/lib/shared/drift.ts`.
|
|
|
|
## Arr Drift Page
|
|
|
|
Route: `/arr/[id]/drift`. Source: `src/routes/arr/[id]/drift/+page.svelte`
|
|
and `+page.server.ts`.
|
|
|
|
Layout:
|
|
|
|
- Sticky header with `Run Now` (queues a manual `arr.drift` job) and `Save`
|
|
(persists the schedule and enabled state).
|
|
- Settings bar (borderless, full-width, with a bottom rule): `Detection`
|
|
toggle, `Schedule` `CronInput`, and timing pills aligned right
|
|
(`Paused` / `Ready` / `Next ...` / `Last ...`).
|
|
- Entities section: an `ExpandableTable` with `Name` / `Entity` / `State`
|
|
columns, mirroring the dev changes-page diff idiom. Each drifted entity is
|
|
one row; expanding shows a `DriftFieldDiffTable` with `Field` / `Profilarr`
|
|
/ `<Arr type> - <Arr name>` columns rendering the entity's `changes[]`.
|
|
|
|
State rendering inside the entities section:
|
|
|
|
| Latest status | Rendered as |
|
|
| -------------------------------------- | ----------------------------------------------------------------------- |
|
|
| Detection disabled | Empty `ExpandableTable` chrome with a disabled message in the empty row |
|
|
| `never_checked` | Neutral message box |
|
|
| `clean` | Success message box |
|
|
| `failed` | Error message box with `last_error` |
|
|
| `drift_detected`, displayable items | Populated `ExpandableTable` |
|
|
| `drift_detected`, no displayable items | Amber message box (formatter produced nothing for the stored diff) |
|
|
|
|
The page is read-only for drift results. It never writes to Arr, repairs
|
|
configuration, or triggers sync.
|
|
|
|
## Sync Page Progress
|
|
|
|
Route: `/arr/[id]/sync`. Source: `src/routes/arr/[id]/sync/+page.server.ts`,
|
|
`src/routes/arr/[id]/sync/components/QualityProfiles.svelte`,
|
|
`src/routes/arr/[id]/sync/components/DelayProfiles.svelte`,
|
|
`src/routes/arr/[id]/sync/components/MediaManagement.svelte`.
|
|
|
|
Per-section drift progress is rendered as `ProgressIndicator` chips in each
|
|
sync section header (right-aligned on tablet+, stacked below the title on
|
|
mobile). The Quality Profiles header carries two chips: one for QPs themselves
|
|
and one for the custom formats referenced by those QPs. The Delay Profiles
|
|
header carries one chip. The Media Management header carries up to three chips
|
|
— one each for Naming, Quality Definitions, and Media Settings — corresponding
|
|
to the three sub-configs the user selects in that section.
|
|
|
|
Each chip shows `current / total` where:
|
|
|
|
- `total`: managed items Profilarr would sync. Selected QP count for the QP
|
|
chip, expected CF count from `buildExpectedCustomFormats` for the CF chip,
|
|
`0` or `1` for the delay profile chip and each of the three Media Management
|
|
chips (Naming, Quality Definitions, Media Settings).
|
|
- `current`: `total - drifted`. For the QP chip, `drifted` counts only QPs
|
|
the drift comparison flagged directly. QPs that are only "transitively"
|
|
affected (their scoring rows reference a CF that has been deleted from
|
|
Arr) are not subtracted from the QP chip; the user-visible impact is
|
|
surfaced via the CF chip's tooltip instead.
|
|
|
|
Chips render with `colorMode="completion"`: green check when `met`, yellow
|
|
bar otherwise. Anything below 100% is yellow regardless of how close to
|
|
completion, because for drift "almost in sync" is still actionable.
|
|
|
|
Each chip has a tooltip listing up to three affected entity names with a
|
|
`+N more` suffix when the list overflows. Examples:
|
|
|
|
- `"HD Movies, 4K Movies drifted."`
|
|
- `"HD Movies affected by drifted custom formats."`
|
|
- `"Streaming Tier drifted, used in HD Movies, TV."`
|
|
- `"Standard Delay drifted."`
|
|
|
|
Chips are hidden entirely when any of these conditions hold:
|
|
|
|
- `FEATURES.drift` is off
|
|
- per-instance `arr_drift_settings.enabled` is false
|
|
- drift status is `never_checked` or `failed` (or no row exists)
|
|
- the section's denominator is zero (e.g. no selected QPs)
|
|
|
|
Failures (`status === 'failed'`) surface only on the dedicated drift page.
|
|
|
|
### Post-sync drift refresh
|
|
|
|
The arr sync handler (`src/lib/server/jobs/handlers/arrSync.ts`) enqueues an
|
|
`arr.drift` job after a successful sync that touched Arr (any section ran).
|
|
This keeps the sync page chips fresh after the user fixes drift by syncing,
|
|
instead of waiting for the next scheduled drift run. The chain is gated on
|
|
`FEATURES.drift` and per-instance `arr_drift_settings.enabled`.
|
|
|
|
The sync page subscribes to job-finished events via
|
|
`jobStatus.onJobFinished` and calls `invalidateAll()` whenever an `arr.drift`
|
|
job completes. The raw hook is used (not the store's state machine) because
|
|
the state machine drops finished events for jobs it is not actively tracking,
|
|
including a drift job chained right after a sync's completion holdoff window.
|
|
SSE is opened on demand when the user triggers a sync and auto-closes after
|
|
the post-completion idle window, so this does not hold a persistent connection.
|