Skip to content

feat(library): managed library integrity check (#92)#110

Open
thedavidweng wants to merge 19 commits into
feat/artwork-derivatives-91from
feat/library-integrity-92
Open

feat(library): managed library integrity check (#92)#110
thedavidweng wants to merge 19 commits into
feat/artwork-derivatives-91from
feat/library-integrity-92

Conversation

@thedavidweng

@thedavidweng thedavidweng commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a "Check library integrity" action in Settings that audits the active local library for missing/empty referenced files (primary media, CDG, stems, artwork) and unreferenced managed files (orphans)
  • Present a deterministic, sorted report in a modal with five sections; missing/empty primary entries have checkboxes selected by default
  • After two-step confirmation, remove selected database entries only when their primary media is still missing or empty at mutation time (revalidated in a single transaction); filesystem orphans remain report-only
  • Reconcile playback, queue, and library selection state from returned/backend authority after cleanup
  • Update IPC contract docs (docs/references/contracts/library.md) and CHANGELOG

Closes #92.

Test plan

  • Rust unit tests pass (cargo test)
  • TypeScript unit tests pass (pnpm test) — 1336 tests
  • Patch coverage at 95% (target 80%)
  • Lint, format, i18n, and IPC contract checks pass
  • pnpm build succeeds

Open in Devin Review

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.6%. Comparing base (e1efd8e) to head (8645a8f).
⚠️ Report is 60 commits behind head on feat/artwork-derivatives-91.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@                      Coverage Diff                      @@
##           feat/artwork-derivatives-91    #110     +/-   ##
=============================================================
+ Coverage                         79.1%   79.6%   +0.4%     
=============================================================
  Files                              129     131      +2     
  Lines                             6772    6899    +127     
  Branches                          2109    2107      -2     
=============================================================
+ Hits                              5360    5493    +133     
+ Misses                            1369    1363      -6     
  Partials                            43      43             
Flag Coverage Δ
frontend 79.6% <100.0%> (+0.4%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
frontend 79.6% <100.0%> (+0.4%) ⬆️
rust ∅ <ø> (∅)
Files with missing lines Coverage Δ
src/components/Settings/IntegrityReportModal.tsx 100.0% <100.0%> (ø)
src/components/Settings/SettingsDialogHost.tsx 38.7% <100.0%> (+9.0%) ⬆️
src/components/Settings/SettingsLibrarySection.tsx 51.5% <100.0%> (+5.4%) ⬆️
src/components/Settings/SettingsOverlay.context.ts 45.0% <100.0%> (+5.9%) ⬆️
src/components/Settings/SettingsOverlay.state.ts 88.8% <ø> (ø)
...nts/Settings/settings-overlay.integrity-actions.ts 100.0% <100.0%> (ø)
src/locales/en.json 100.0% <ø> (ø)
src/locales/zh-CN.json 100.0% <ø> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a managed-library integrity workflow for finding and cleaning up broken local library entries. The main changes are:

  • New Rust audit and cleanup logic for missing, empty, and orphaned managed files.
  • New Tauri IPC commands and TypeScript wrappers for integrity checks and cleanup.
  • A Settings modal that shows the sorted integrity report and selected primary-media entries.
  • Cleanup reconciliation for library state, queue state, playback state, and selection state.
  • Updated IPC contract docs, schema docs, changelog, and related tests.

Confidence Score: 5/5

Safe to merge with low risk.

No actionable functional or security issues were found in the audit, cleanup, IPC, UI, or reconciliation paths. The earlier remote artwork gap is addressed by classifying remote artwork paths through optional-asset handling.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • The initial targeted Vitest run for settings integrity completed with exit code 0, reporting 4 files passed and 41 tests passed.
  • A confirming targeted Vitest run for settings integrity completed with exit code 0, again reporting 4 files passed and 41 tests passed.
  • The artifact listing and contents were reviewed to verify the captured logs and the artifact directory summary from the runs.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src-tauri/src/library/integrity.rs Adds integrity audit and cleanup domain logic with safe managed-path resolution, orphan scanning, transaction-time revalidation, and tests.
src-tauri/src/commands/integrity.rs Adds Tauri IPC adapters for integrity check and cleanup, including post-commit playback invalidation.
src/components/Settings/IntegrityReportModal.tsx Adds the integrity report modal UI with selectable primary-media issues and read-only optional and orphan sections.
src/components/Settings/settings-overlay.integrity-actions.ts Adds settings overlay actions for integrity check, default selection, confirmed cleanup, and frontend state reconciliation.
src/lib/tauri/library.ts Adds frontend IPC wrappers for integrity audit and cleanup commands.
src/types/ipc.ts Adds TypeScript IPC contract types for integrity reports and cleanup results.
src-tauri/src/audio/coordinator.rs Extends playback coordinator with deleted-song invalidation so cleanup clears stale playback and CDG state.
src/stores/queue-store.ts Adds queue removal by song id to reconcile deleted library entries.
src/stores/player-store.ts Exposes playback state reload support used after integrity cleanup reconciliation.
docs/references/contracts/library.md Updates the library IPC contract documentation for new integrity commands and payloads.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant SettingsUI as Settings UI
participant IPC as Tauri IPC
participant Audit as library::integrity
participant DB as SQLite library DB
participant FS as Managed library files
participant Playback as PlaybackCoordinator

User->>SettingsUI: Check library integrity
SettingsUI->>IPC: check_library_integrity()
IPC->>Audit: run audit for active LibraryRoot
Audit->>DB: Load songs, stems, and artwork paths
Audit->>FS: Classify referenced files and scan managed dirs
Audit-->>IPC: IntegrityReport
IPC-->>SettingsUI: Sorted report with default primary selection
User->>SettingsUI: Confirm cleanup
SettingsUI->>IPC: remove_missing_library_entries(hashes)
IPC->>Audit: Revalidate in transaction
Audit->>DB: Delete still-missing or empty local songs
Audit-->>IPC: CleanupResult
IPC->>Playback: InvalidateDeletedSongs(deleted hashes)
Playback-->>SettingsUI: Authoritative playback state
SettingsUI->>SettingsUI: Reload library, queue, and selection state
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant SettingsUI as Settings UI
participant IPC as Tauri IPC
participant Audit as library::integrity
participant DB as SQLite library DB
participant FS as Managed library files
participant Playback as PlaybackCoordinator

User->>SettingsUI: Check library integrity
SettingsUI->>IPC: check_library_integrity()
IPC->>Audit: run audit for active LibraryRoot
Audit->>DB: Load songs, stems, and artwork paths
Audit->>FS: Classify referenced files and scan managed dirs
Audit-->>IPC: IntegrityReport
IPC-->>SettingsUI: Sorted report with default primary selection
User->>SettingsUI: Confirm cleanup
SettingsUI->>IPC: remove_missing_library_entries(hashes)
IPC->>Audit: Revalidate in transaction
Audit->>DB: Delete still-missing or empty local songs
Audit-->>IPC: CleanupResult
IPC->>Playback: InvalidateDeletedSongs(deleted hashes)
Playback-->>SettingsUI: Authoritative playback state
SettingsUI->>SettingsUI: Reload library, queue, and selection state
Loading

Reviews (2): Last reviewed commit: "test(integrity): add EQ and crossfade fi..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 79.68% (🎯 65%) 4005 / 5026
🔵 Statements 79.12% (🎯 65%) 4241 / 5360
🔵 Functions 70.72% (🎯 60%) 1063 / 1503
🔵 Branches 71.59% (🎯 60%) 2238 / 3126
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/Settings/IntegrityReportModal.tsx 100% 100% 100% 100%
src/components/Settings/SettingsDialogHost.tsx 47.05% 20% 28.57% 47.05% 12-53, 64-75
src/components/Settings/SettingsLibrarySection.tsx 36.95% 59.18% 21.05% 36.95% 54-65, 93-99, 103-109, 133-203, 243-361
src/components/Settings/SettingsOverlay.context.ts 100% 100% 31.7% 100%
src/components/Settings/SettingsOverlay.state.ts 88.72% 77.77% 88% 88.37% 184-185, 190-203, 208-214, 225, 293
src/components/Settings/settings-overlay.integrity-actions.ts 100% 91.66% 100% 100%
src/locales/en.json 100% 100% 100% 100%
src/locales/zh-CN.json 100% 100% 100% 100%
Generated in workflow #890 for commit 8645a8f by the Vitest Coverage Report Action

Copy link
Copy Markdown
Owner Author

Maintainer acceptance — merge slot 12, strictly after #107

BLOCKED — changes required.

Blocking backend safety/state issues

  1. remove_missing_library_entries only deletes database rows and later asks the frontend to reload player state. It does not invalidate/cancel authoritative current, loading, background, or prepared playback in the coordinator, nor CDG/AirPlay state. A second WebView or the realtime callback can keep using a deleted song. feat: add managed library integrity check #92 requires backend-authoritative invalidation; frontend reload is not a substitute.

  2. scan_for_orphans calls read_dir directly on each top-level managed directory. If media, stems, artwork, or media-g itself is replaced with a symlink, the scan follows it before it ever checks entry metadata. That violates the explicit “never follow links” safety contract and can traverse outside the library root. Validate each top-level path with symlink_metadata before read_dir, report/reject it safely, and add a regression test.

Rebase after #107 so artwork derivatives and waveform cascade behavior are present, then rerun CI and destructive-flow manual acceptance.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Owner Author

Re-review: blocking filesystem-boundary and playback-authority fixes required

This PR is stacked after #107. Its frontend reconciliation is not sufficient to make destructive cleanup safe. Two backend issues remain.

1. Never traverse a managed-root symlink

scan_for_orphans checks existence and then calls read_dir on managed roots. A symlinked top-level managed directory can therefore lead the scan outside the library root before child-entry symlink checks run.

Implement this boundary policy:

  1. Before opening each configured managed root, use symlink_metadata. If it is a symlink, do not call read_dir or recurse through it; report/skip it deterministically according to the integrity-report model.
  2. Apply the same no-follow rule to nested entries. Do not rely on metadata, which follows links.
  3. Keep all canonical/relative-path checks defensive so an error or malformed path cannot escape the library root.
  4. Add tests with a top-level managed-directory symlink and a nested symlink that point outside a temporary library. Prove the outside files are neither enumerated nor classified/deleted. Make platform-specific symlink setup explicit where OS permissions require it.

2. Reconcile playback from backend authority after deletion

remove_missing_library_entries currently removes database rows without first-class coordinator invalidation for current/loading/prepared/background playback state. Add one authoritative post-transaction reconciliation path:

  1. Revalidate and commit the database mutation atomically first; only after success, ask PlaybackCoordinator to reconcile the deleted song IDs.
  2. If a deleted song is current, loading, prepared-next, background, CDG, or AirPlay-related, cancel/stop/invalidate the exact affected state before it can render or transition. Queue/history/selection must converge from the coordinator's resulting snapshot/event, not by a frontend guess.
  3. Ensure a failed/rejected transaction makes no playback-state change.
  4. Add backend/coordinator tests for deleting the current item, a prepared next item, and a background/CDG/AirPlay-relevant item; assert no stale playback resumes or emits a late transition.

After #107 merges, rebase onto the resulting main. Run the full Rust/frontend suite and show both symlink and playback-reconciliation regression results before requesting review.

@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from 7d52bbb to b7d29f5 Compare July 15, 2026 09:03

Copy link
Copy Markdown
Owner Author

Re-review — still blocked: cleanup does not invalidate late playback work or AirPlay authority

Reviewed at b70ee60. The symlink scan fix is good, but the destructive cleanup path remains unsafe.

InvalidateDeletedSongs clears only current_track / loading_song_id. It leaves latest_request_id unchanged, so a decode already in flight can later send InstallReady with the still-current request id and reinstall a song whose database row was deleted. It also does not bump the AirPlay epoch/generation, and it has no reconciliation for the prepared/background states that this PR must handle after the audio-pipeline stack lands.

Make the post-commit coordinator path authoritative:

  1. On a successful cleanup that changes matching playback state, reserve/increment the same monotonic request generation used by load commands before releasing the playback lock. This must make late InstallReady, FailLoad, and stem-attach work for deleted songs stale/no-ops.
  2. Clear matching current/loading/prepared/background state, clear matching CDG state, and call the existing bump_airplay_epoch_and_generation(&runtime.airplay) so stale AirPlay audio/control work cannot continue.
  3. Rebase this PR onto the accepted playback pipeline before merge, then cover its prepared/background state rather than assuming the older base has no such state.
  4. Add coordinator-level regressions for: delete during load followed by delayed InstallReady; deleting the current track; deleting a prepared/background candidate; CDG/AirPlay invalidation; unrelated deletion; and failed DB transaction (no playback generation/state change).

Frontend reload is not a substitute for invalidating the backend work that can re-install or continue rendering deleted media.

@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from b70ee60 to 8fe18db Compare July 16, 2026 03:48
devin-ai-integration[bot]

This comment was marked as resolved.

@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from 7c2c708 to b6a7e80 Compare July 16, 2026 04:18

Copy link
Copy Markdown
Owner Author

Re-review — blocked: current Rust CI is red, and the temp-artwork filter does not match the writer

Reviewed at b6a7e80.

  1. The current Rust suite has three real failures: audit_remote_song_detects_missing_artwork, audit_remote_song_detects_empty_artwork, and audit_remote_song_valid_artwork_no_issues. Each panics with duplicate column name: artwork_thumb_path. create_test_library() now applies the production artwork-column migration, so the test-only add_artwork_columns helper is stale. Remove/replace those duplicate ALTER calls; do not remove the production migration guard.

  2. is_temp_artwork_file accepts only .tmp-*, *-tmp.*, or tmp-*, but unique_temp_path writes names such as artwork/.thumb_<digest>_80.webp.<pid>.<counter>.tmp. A recent leftover from an interrupted artwork write is therefore reported as an orphan instead of receiving the documented 24-hour grace period.

Make the audit recognise the exact temp-file convention used by unique_temp_path (under artwork/, hidden generated name ending in .tmp, or an equally precise shared predicate). Add a regression using a real generated-style filename, plus the existing age boundary behavior.

Please re-run Rust fmt, clippy, and the full Rust test suite. This PR cannot be approved while its CI reports 596/599 tests passing.

Copy link
Copy Markdown
Owner Author

Re-review — still blocked: the integrity audit still fails to recognise the writer's real temporary artwork names

Reviewed at b77a1e2. The duplicate-ALTER TABLE fix is correct and CI is now green, but it resolves only the first part of the prior review.

unique_temp_path() writes files such as:

artwork/.thumb_<digest>_80.webp.<pid>.<counter>.tmp

The audit predicate still accepts only .tmp-*, *-tmp.*, or tmp-*. The real generated filename matches none of those, so an interrupted recent derivative write is immediately reported as an orphan instead of receiving the documented 24-hour grace period.

Please share an exact predicate between writer and audit, or precisely recognise the generated convention under artwork/ (hidden derivative temp name ending in .tmp). Do not broadly exempt arbitrary files outside that convention.

Add regression coverage using a real generated-style path for both sides of the 24-hour boundary: <24h is excluded; >=24h is reported. No LGTM yet.

@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from b77a1e2 to d7240ae Compare July 16, 2026 08:06
thedavidweng added a commit that referenced this pull request Jul 16, 2026
…ion (#110)

The orphan scanner is_temp_artwork_file checked for patterns like
.tmp-* and *-tmp.*, but unique_temp_path generates temp files as
.{name}.{pid}.{counter}.tmp. The filter now matches any hidden file
(starts with .) ending in .tmp under artwork/. Added 24h boundary
regression tests: a temp file at 23h59m is excluded, while one at 25h
is reported as a stale orphan.
devin-ai-integration[bot]

This comment was marked as resolved.

@thedavidweng

Copy link
Copy Markdown
Owner Author

@greptile review

Copy link
Copy Markdown
Owner Author

Re-review of head ce65e018969136addcba38678ff88b3923bce9f4: not merge-ready.

Two blockers remain:

  1. PR scope/history is contaminated. The PR is based on main but currently contains 66 commits, 107 changed files, and ~15.5k additions, including EQ, peaks, gapless, crossfade, and other unrelated features. Merging feat(library): managed library integrity check (#92) #110 now would implicitly merge multiple PRs that have not completed their own acceptance. Rebuild this branch from the current target base with only feat(library): managed library integrity check (#92) #110's integrity-check commits (or wait for dependencies to merge, then rebase/retarget and verify the final diff contains only feat(library): managed library integrity check (#92) #110 scope).

  2. The artwork temp predicate is still duplicated and too broad. integrity.rs::is_temp_artwork_file accepts any hidden artwork/**/.tmp filename, while the writer in artwork.rs::unique_temp_path emits exactly .{final_name}.{pid}.{counter}.tmp. The audit therefore hides unrelated recent files such as artwork/.notes.tmp or nested hidden temp files for 24 hours, and the reader/writer conventions can drift again.

Required implementation:

  • Put the canonical recognition logic beside the writer in artwork.rs as a pub(crate) pure helper (prefer accepting a relative path or filename without filesystem access).
  • Match the actual writer format exactly: direct child of artwork/, known derivative final filename shape, numeric PID, numeric counter, final .tmp; reject nested paths and arbitrary hidden .tmp files.
  • Import and use that helper from integrity.rs; remove the duplicated broad matcher.
  • Add table-driven predicate tests for valid thumb/preview writer names and invalid arbitrary/nested/non-numeric variants.
  • Keep integration tests proving a matching file younger than 24h is excluded and one at/older than 24h is reported; add a recent arbitrary artwork/.notes.tmp case proving it is reported.
  • After cleaning the branch scope, rerun full Rust/TS/coverage/packaging CI on the final head.

Because this feature can delete library database entries, manual destructive-safety acceptance is still required after the code blockers are cleared.

thedavidweng added a commit that referenced this pull request Jul 17, 2026
…ion (#110)

The orphan scanner is_temp_artwork_file checked for patterns like
.tmp-* and *-tmp.*, but unique_temp_path generates temp files as
.{name}.{pid}.{counter}.tmp. The filter now matches any hidden file
(starts with .) ending in .tmp under artwork/. Added 24h boundary
regression tests: a temp file at 23h59m is excluded, while one at 25h
is reported as a stale orphan.
thedavidweng added a commit that referenced this pull request Jul 17, 2026
The orphan scanner's is_temp_artwork_file used a broad pattern (any
hidden file ending in .tmp under artwork/), which could exclude
unrelated temp-named files that don't follow the unique_temp_path
convention. The filter now matches the exact .{name}.{pid}.{counter}.tmp
shape (numeric pid and counter) and is a shared pub fn so the writer
and scanner can never diverge. Added regression tests: broad patterns
like .foo.tmp are reported as orphans, while exact convention files
under 24h are excluded.
@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from ce65e01 to d91a156 Compare July 17, 2026 00:22
thedavidweng added a commit that referenced this pull request Jul 17, 2026
…ion (#110)

The orphan scanner is_temp_artwork_file checked for patterns like
.tmp-* and *-tmp.*, but unique_temp_path generates temp files as
.{name}.{pid}.{counter}.tmp. The filter now matches any hidden file
(starts with .) ending in .tmp under artwork/. Added 24h boundary
regression tests: a temp file at 23h59m is excluded, while one at 25h
is reported as a stale orphan.
thedavidweng added a commit that referenced this pull request Jul 17, 2026
The orphan scanner's is_temp_artwork_file used a broad pattern (any
hidden file ending in .tmp under artwork/), which could exclude
unrelated temp-named files that don't follow the unique_temp_path
convention. The filter now matches the exact .{name}.{pid}.{counter}.tmp
shape (numeric pid and counter) and is a shared pub fn so the writer
and scanner can never diverge. Added regression tests: broad patterns
like .foo.tmp are reported as orphans, while exact convention files
under 24h are excluded.
@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from 5f1d52b to a29c78a Compare July 17, 2026 05:03
devin-ai-integration[bot]

This comment was marked as resolved.

thedavidweng added a commit that referenced this pull request Jul 17, 2026
…ries (#110)

remove_missing_library_entries previously only cleaned up the stem
directory after the DB transaction committed, leaving the deleted
song's artwork/thumb_* and artwork/preview_* derivative files behind
on disk forever.

Collect each deleted song's recorded artwork derivative paths before
its DB rows are dropped (mirroring delete_song_from_library, which
already does this), and best-effort delete them afterwards via
delete_artwork_derivative_if_unreferenced. That helper skips any
derivative still referenced by another song sharing the same cover-art
digest, so shared artwork is preserved.

Make delete::collect_artwork_derivative_paths pub so the integrity
cleanup can reuse the same collection logic as the normal song delete.

Add regression tests covering both the deletion and the shared-
derivative preservation case.
@thedavidweng
thedavidweng changed the base branch from main to feat/artwork-derivatives-91 July 17, 2026 10:56
Audit the active managed library for missing/empty referenced files and
unreferenced managed files. Present a deterministic report in Settings.
After explicit confirmation, remove selected database entries only when
their primary media is still missing or empty at mutation time, then
reconcile playback, queue and library state.
Add unit tests for integrity actions (check, toggle, confirm, close),
IntegrityReportModal rendering paths, and the cleanup confirmation
dialog in SettingsDialogHost. Patch coverage now at 95%.
Remove the unused canonical_root parameter from scan_directory
(clippy::only_used_in_recursion under Rust 1.97). The parameter was
only forwarded to the recursive call and never read in the function
body; the caller canonicalize() was also dead work.

Update createHarness in the integrity actions test to accept a
partial state override (Partial<SettingsOverlayState>) instead of
requiring a full SettingsOverlayState. The previous signature
caused TS2740 in 14 test cases that only set integrity fields.
Call toggleIntegritySelection with a single hash argument, exercise
cleanup-in-progress labels, and disable the integrity check while cleanup
runs so patch coverage can hit every coverable line.
…idation

Managed roots and nested paths use symlink_metadata before traversal so
library audits cannot escape via symlink. Successful remove_missing commits
invalidate matching current/loading tracks and clear CDG through the
playback coordinator.
The add_artwork_columns test helper unconditionally ran ALTER TABLE
songs ADD COLUMN artwork_thumb_path / artwork_preview_path, but
cache::open_database already adds these columns via apply_migrations.
The duplicate ALTER TABLE failed with 'duplicate column name:
artwork_thumb_path' in 3 integrity tests. The helper now checks
column_exists before adding, making it a no-op when migrations have
already applied the columns.
…ion (#110)

The orphan scanner is_temp_artwork_file checked for patterns like
.tmp-* and *-tmp.*, but unique_temp_path generates temp files as
.{name}.{pid}.{counter}.tmp. The filter now matches any hidden file
(starts with .) ending in .tmp under artwork/. Added 24h boundary
regression tests: a temp file at 23h59m is excluded, while one at 25h
is reported as a stale orphan.
The orphan scanner's is_temp_artwork_file used a broad pattern (any
hidden file ending in .tmp under artwork/), which could exclude
unrelated temp-named files that don't follow the unique_temp_path
convention. The filter now matches the exact .{name}.{pid}.{counter}.tmp
shape (numeric pid and counter) and is a shared pub fn so the writer
and scanner can never diverge. Added regression tests: broad patterns
like .foo.tmp are reported as orphans, while exact convention files
under 24h are excluded.
The temp artwork predicate accepted any path starting with artwork/
and checked only the filename component, so a nested path like
artwork/sub/.foo.123.0.tmp would match and receive the 24-hour grace
period. The writer (unique_temp_path in artwork.rs) only places temp
files as direct children of artwork/, never in subdirectories.

Require the path to be exactly artwork/<filename> with no
subdirectory components. Add regression tests for nested paths and
arbitrary hidden .tmp files being reported as orphans.
Per maintainer review: the canonical recognition logic for temp artwork
files now lives beside the writer (unique_temp_path) in artwork.rs as
pub(crate) pure helpers, imported by integrity.rs. This ensures the
writer and scanner can never diverge. Added table-driven predicate tests
in artwork.rs and an integration test proving artwork/.notes.tmp is
reported as an orphan.
…PRAGMA

The table name was interpolated via format! without quoting or validation.
While current callers only pass the hardcoded "songs" literal, add
identifier validation and double-quote quoting as defense-in-depth against
future untrusted input.
…ries (#110)

remove_missing_library_entries previously only cleaned up the stem
directory after the DB transaction committed, leaving the deleted
song's artwork/thumb_* and artwork/preview_* derivative files behind
on disk forever.

Collect each deleted song's recorded artwork derivative paths before
its DB rows are dropped (mirroring delete_song_from_library, which
already does this), and best-effort delete them afterwards via
delete_artwork_derivative_if_unreferenced. That helper skips any
derivative still referenced by another song sharing the same cover-art
digest, so shared artwork is preserved.

Make delete::collect_artwork_derivative_paths pub so the integrity
cleanup can reuse the same collection logic as the normal song delete.

Add regression tests covering both the deletion and the shared-
derivative preservation case.
@thedavidweng
thedavidweng force-pushed the feat/library-integrity-92 branch from 0069894 to 22ac8fb Compare July 17, 2026 11:06

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread src-tauri/src/audio/coordinator.rs
Comment thread docs/references/contracts/library.md Outdated
Comment thread CHANGELOG.md Outdated
Comment on lines 40 to 43
- Library integrity audit never follows managed-root or nested symlinks outside the library ([#110](https://github.com/thedavidweng/OpenKara/issues/110)): top-level managed dirs (`media/`, `stems/`, …) are checked with `symlink_metadata` before `read_dir`; a root that is itself a symlink is reported as an orphan relative path and never opened. Nested symlinks are reported the same way. After a successful `remove_missing_library_entries` commit, the playback coordinator invalidates any current/loading track matching deleted song hashes and clears CDG so realtime playback cannot keep serving deleted media.- Enforce platform-scoped execution-provider choices ([#115](https://github.com/thedavidweng/OpenKara/issues/115)): the Hardware Acceleration settings section now exposes only providers valid for the running binary. macOS and Linux show CPU and XNNPACK; Windows shows CPU, XNNPACK, and DirectML. A single Rust policy table drives the available list, default, stale-config normalization, and write validation. `set_execution_provider` rejects known-but-unavailable providers (e.g. `directml` on macOS) before any config mutation, and `get_settings` normalizes a stale persisted cross-platform value to the platform default without writing to disk. The separation execution context uses the same normalized value. Windows CI now runs the filtered library tests (`cargo test --lib execution_provider`) — the test binary previously failed to load with `STATUS_ENTRYPOINT_NOT_FOUND` (0xC0000139) because `tauri-build` only links the Common Controls v6 manifest into `[[bin]]` targets, not test binaries; `build.rs` now embeds the manifest into all targets so `comctl32.dll` resolves correctly on headless Windows Server runners.
- Library integrity audit never follows managed-root or nested symlinks outside the library ([#110](https://github.com/thedavidweng/OpenKara/issues/110)): top-level managed dirs (`media/`, `stems/`, …) are checked with `symlink_metadata` before `read_dir`; a root that is itself a symlink is reported as an orphan relative path and never opened. Nested symlinks are reported the same way. After a successful `remove_missing_library_entries` commit, the playback coordinator invalidates any current/loading track matching deleted song hashes and clears CDG so realtime playback cannot keep serving deleted media.
- Deleting broken library entries now also removes their cover-art thumbnail and preview derivatives ([#110](https://github.com/thedavidweng/OpenKara/issues/110)): `remove_missing_library_entries` previously only cleaned up the stem directory after the DB transaction committed, leaving the song's `artwork/thumb_*` and `artwork/preview_*` files behind forever. It now collects each deleted song's recorded artwork derivative paths before the DB rows are dropped (mirroring `delete_song_from_library`) and best-effort deletes them afterwards via `delete_artwork_derivative_if_unreferenced`, which skips any derivative still referenced by another song sharing the same cover-art digest.
- Enforce platform-scoped execution-provider choices ([#115](https://github.com/thedavidweng/OpenKara/issues/115)): the Hardware Acceleration settings section now exposes only providers valid for the running binary. macOS and Linux show CPU and XNNPACK; Windows shows CPU, XNNPACK, and DirectML. A single Rust policy table drives the available list, default, stale-config normalization, and write validation. `set_execution_provider` rejects known-but-unavailable providers (e.g. `directml` on macOS) before any config mutation, and `get_settings` normalizes a stale persisted cross-platform value to the platform default without writing to disk. The separation execution context uses the same normalized value. Windows CI now runs the filtered library tests (`cargo test --lib execution_provider`) — the test binary previously failed to load with `STATUS_ENTRYPOINT_NOT_FOUND` (0xC0000139) because `tauri-build` only links the Common Controls v6 manifest into `[[bin]]` targets, not test binaries; `build.rs` now embeds the manifest into all targets so `comctl32.dll` resolves correctly on headless Windows Server runners.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 CHANGELOG has duplicated and contradictory entries with missing line breaks

The changelog additions duplicate the same library-integrity note twice (CHANGELOG.md:40-41) and run two separate bullets together on one line without a newline, and elsewhere include two conflicting descriptions of the same temp-file fix, so the changelog no longer accurately reflects the completed change.
Impact: Readers of the changelog get duplicated, merged, and contradictory notes about this feature, undermining the changelog's role as the record of completed changes.

Prompt for agents
The CHANGELOG additions for this PR contain merge artifacts. Line 40 concatenates two distinct bullets (the 'Library integrity audit never follows managed-root or nested symlinks' entry and the 'Enforce platform-scoped execution-provider choices' entry) into a single line with no newline between them, and both of those bullets are then repeated as separate entries on lines 41 and 43, producing exact duplicates. Similarly, lines 29 and 31 contain two contradictory descriptions of the same #110 temp-file filter fix (one says the filter 'matches any hidden file ending in .tmp under artwork/', the other says it 'matches the exact .{name}.{pid}.{counter}.tmp shape') and line 29/32 also concatenate unrelated bullets without newlines. Deduplicate these entries, keep only the description that reflects the shipped behavior (exact .{name}.{pid}.{counter}.tmp match), and ensure each bullet is on its own line.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

The settings-overlay.integrity-actions test harness predates the EQ and
crossfade settings added by the audio-pipeline stack. The mock state and
api dependencies were missing eqEnabled, eqGainsDb, crossfadeEnabled,
crossfadeDurationMs and the corresponding setEq* / setCrossfade* api
functions, causing TS2322 errors on the stacked #110 branch.
@thedavidweng

Copy link
Copy Markdown
Owner Author

Fix: missing EQ/crossfade fields in integrity test mock

The Frontend quality / build / test and Build Flatpak (x86_64) CI failures were caused by TS2322 errors in settings-overlay.integrity-actions.test.ts. The test harness predates the EQ and crossfade settings added by the audio-pipeline stack, so the mock SettingsOverlayState and SettingsOverlayControllerDependencies.api were missing:

  • State: eqEnabled, eqGainsDb, crossfadeEnabled, crossfadeDurationMs
  • API: setEqEnabled, setEqGains, setCrossfadeEnabled, setCrossfadeDurationMs

Commit 217a8cb adds these fields to the test mock. Verified locally:

  • npx tsc --noEmit — clean
  • npx vitest run — 1506 passed (146 files)
  • cargo test — 647 passed (2 ONNX integration tests skipped — missing libonnxruntime.dylib on this machine, unrelated to this PR)
  • Pre-push hooks (lint, format-check, cargo-fmt, patch-coverage) — all green

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.

feat: add managed library integrity check

1 participant