Skip wishlist adds for manual library matches
This commit is contained in:
parent
3e7eeb7c9c
commit
0345478361
4 changed files with 96 additions and 1 deletions
|
|
@ -36,7 +36,10 @@ def get_match(
|
|||
server_source: str = "",
|
||||
) -> Optional[dict]:
|
||||
"""Return match row dict or None if not found."""
|
||||
return db.get_manual_library_match(profile_id, source, source_track_id, server_source)
|
||||
getter = getattr(db, "get_manual_library_match", None)
|
||||
if getter is None:
|
||||
return None
|
||||
return getter(profile_id, source, source_track_id, server_source)
|
||||
|
||||
|
||||
def delete_match(db, match_id: int, profile_id: int) -> bool:
|
||||
|
|
|
|||
|
|
@ -7217,6 +7217,16 @@ class MusicDatabase:
|
|||
logger.error("Cannot add track to wishlist: missing track ID")
|
||||
return False
|
||||
|
||||
track_source = spotify_track_data.get('provider') or spotify_track_data.get('source') or 'spotify'
|
||||
if self.get_manual_library_match(profile_id, track_source, track_id):
|
||||
logger.info(
|
||||
"Skipping wishlist add for manually matched track: '%s' (%s:%s)",
|
||||
spotify_track_data.get('name', 'Unknown Track'),
|
||||
track_source,
|
||||
track_id,
|
||||
)
|
||||
return True
|
||||
|
||||
track_name = spotify_track_data.get('name', 'Unknown Track')
|
||||
artists = spotify_track_data.get('artists', [])
|
||||
if artists:
|
||||
|
|
|
|||
56
pr_description.md
Normal file
56
pr_description.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
## Summary
|
||||
|
||||
Adds a new **Manual Library Match** tool that lets users map a wishlist/sync source track to an existing library track. Once saved, SoulSync can treat that source track as already owned/found instead of repeatedly trying to download it.
|
||||
|
||||
## What Changed
|
||||
|
||||
- Added a centralized Manual Library Match tool on the Tools page and a shortcut from the Sync page.
|
||||
- Added side-by-side source-track and library-track search UI, plus an existing matches table with removal support.
|
||||
- Added `manual_library_track_matches` persistence with profile/source/source-track scoping.
|
||||
- Added backend search/list/save/delete endpoints for manual matches.
|
||||
- Integrated manual matches into download analysis so matched source tracks are marked found and skipped.
|
||||
- Preserved the distinction between wishlist internal force mode and explicit user Force Download All:
|
||||
- Wishlist/internal `force_download_all` still honors manual matches.
|
||||
- User-facing Force Download All ignores manual matches and downloads anyway.
|
||||
- Updated wishlist cleanup so manual matches are removed from the wishlist through:
|
||||
- manual wishlist cleanup,
|
||||
- automatic cleanup after DB update,
|
||||
- wishlist download analysis.
|
||||
- Prevented manually matched source tracks from being re-added to the wishlist in the common database add path.
|
||||
- Polished the Tools page card so Manual Library Match visually matches the Discovery Pool tool card.
|
||||
|
||||
## Behavior
|
||||
|
||||
- Manual match saved:
|
||||
- `source + source_track_id + profile_id -> library_track_id`
|
||||
- Wishlist cleanup:
|
||||
- removes the item if a manual match exists.
|
||||
- Wishlist add:
|
||||
- skips inserting the item if a manual match already exists, returning a harmless success/no-op.
|
||||
- Wishlist download:
|
||||
- checks manual match first, marks found, skips download, and attempts wishlist removal.
|
||||
- Normal download with Force Download All off:
|
||||
- checks manual match before normal library matching.
|
||||
- Normal download with Force Download All on:
|
||||
- skips manual matches and downloads selected tracks intentionally.
|
||||
|
||||
## Tests
|
||||
|
||||
Verified by user:
|
||||
|
||||
```bash
|
||||
./.venv/bin/python -m pytest tests/test_manual_library_match.py tests/downloads/test_downloads_master.py
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
43 passed in 10.29s
|
||||
```
|
||||
|
||||
Additional local verification:
|
||||
|
||||
```text
|
||||
py_compile passed for touched Python files
|
||||
node --check passed for touched JS
|
||||
```
|
||||
|
|
@ -79,6 +79,32 @@ def test_get_returns_none_when_absent(db):
|
|||
assert db.get_manual_library_match(1, "spotify", "nonexistent") is None
|
||||
|
||||
|
||||
def test_add_to_wishlist_skips_manual_matched_track(db):
|
||||
db.save_manual_library_match(1, "spotify", "track-abc", 42)
|
||||
|
||||
ok = db.add_to_wishlist(
|
||||
track_data={
|
||||
"id": "track-abc",
|
||||
"name": "HUMBLE.",
|
||||
"artists": [{"name": "Kendrick Lamar"}],
|
||||
"album": {"name": "DAMN."},
|
||||
"provider": "spotify",
|
||||
},
|
||||
failure_reason="Download failed",
|
||||
profile_id=1,
|
||||
)
|
||||
|
||||
assert ok is True
|
||||
assert db.get_wishlist_tracks(profile_id=1) == []
|
||||
|
||||
|
||||
def test_get_match_returns_none_when_db_lacks_manual_match_method():
|
||||
class _MinimalDB:
|
||||
pass
|
||||
|
||||
assert mlm.get_match(_MinimalDB(), 1, "spotify", "track-abc") is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Service-layer tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue