Commit graph

2306 commits

Author SHA1 Message Date
BoulderBadgeDad
9d1009fa69
Merge pull request #347 from Nezreka/feat/service-status-indicators
Add per-service config status indicators to Settings Connections
2026-04-21 14:29:36 -07:00
Broque Thomas
0b4647ddd4 Add per-service config status indicators to Settings Connections tab
Adds green/yellow header gradient on each service card showing whether the
user has filled in credentials, plus an expand-triggered verification layer
that surfaces working-or-not status inline.

Backend (web_server.py):
- SERVICE_CONFIG_REGISTRY mapping each of the 11 services in Connections to
  its config requirements. Supports required-keys, always-green, any-of,
  and custom-check semantics (Tidal uses token-file check, Qobuz accepts
  either email/password OR cached auth token).
- _is_service_configured(service) — cheap config presence check, no APIs hit.
- GET /api/settings/config-status — returns {service: {configured}} for all
  services in one call. Drives the page-load gradient.
- POST /api/settings/verify — takes {services: [...]}, runs
  run_service_test per service, caches results 5 min in-memory, parallelizes
  with ThreadPoolExecutor(max_workers=3) to avoid self-rate-limiting. Query
  param ?force=true busts cache.
- Added verify branches for iTunes, Deezer, Discogs, Qobuz, Hydrabase in
  run_service_test (previously missing — these services couldn't be tested).

HTML (webui/index.html):
- data-service="..." on all 11 .stg-service containers so JS can map card
  to backend service name.

CSS (webui/static/style.css):
- .status-configured gradient (subtle green, left-to-transparent fade)
- .status-missing gradient (yellow, same shape)
- Spinner badge in header for .status-checking state
- "Testing connection…" status line style inside panel body
- Red warning bar style for verify failures at top of expanded panel
- Brand dot now glows always (was only glowing when expanded); hover and
  expand states intensify the glow progressively.

JS (webui/static/script.js):
- applyServiceStatusGradients() fetches config-status and applies
  green/yellow class per card. Called on Connections tab activate + after
  any settings save.
- _stgVerifyServices(services, {force}) — batch verify POST, tracks
  in-flight state, renders spinners/status lines/warnings per service.
- toggleStgService() fires single-service verify when a card is expanded
  (not on collapse). Skipped if a verify is already in flight for that
  service.
- toggleAllServiceAccordions() fires one batched verify for all 11 services
  when "Expand All" is clicked; skipped on "Collapse All".
- _stgRefreshAfterSave() — after settings save, refreshes gradient (cheap)
  and re-verifies only the cards the user currently has expanded (so
  freshly-edited credentials show their new verify result immediately,
  without re-pinging every service).

Failure UI: top-of-panel red warning bar with the error message (e.g.
"Discogs token rejected (HTTP 401)", "Hydrabase not connected…"). Removed
automatically on next successful verify.

No existing tests changed. Full suite stays at 263 passed. Ruff clean.
2026-04-21 14:24:41 -07:00
Broque Thomas
d9217237d2 Clean up 286 ruff lint errors to unblock CI and fix 10 latent bugs
PR #340 added ruff to the build-and-test.yml CI gate, which surfaced
286 pre-existing lint errors. Left unfixed, every feature branch push
fails CI. This commit resolves all of them so CI goes green and
contributors can actually land work.

Auto-fixes (248 of 286): removed unused f-string prefixes (F541),
renamed unused loop control variables with underscore prefix (B007),
removed duplicate imports (F811).

Manually fixed 10 latent bugs ruff caught (all wrapped in try/except
today, silently failing):

- music_database.py: _add_discovery_tables() called undefined
  conn.commit() — would have crashed the iTunes-support migration
  for existing databases. Now uses cursor.connection.commit().
- web_server.py settings GET: referenced undefined download_orchestrator
  when it should be soulseek_client. Feature (_source_status on the
  settings payload) was silently missing for UI auto-disable logic.
- web_server.py _process_wishlist_automatically: active_server
  undefined in track-ownership check. Auto-wishlist was falling
  through to the error handler and re-downloading owned tracks.
- web_server.py start_wishlist_missing_downloads: same active_server
  bug in the manual wishlist path.
- web_server.py _process_failed_tracks_to_wishlist_exact: emitted
  wishlist_item_added automation event with undefined artist_name
  and track. Automation event silently never fired correctly.
- web_server.py discovery metadata enrichment: referenced cache
  without calling get_metadata_cache() first. Track enrichment from
  cached API responses was silently skipped.
- web_server.py Beatport discovery worker: wing-it fallback branch
  used undefined successful_discoveries variable. Wing-it counter
  never incremented correctly. Now uses state['spotify_matches']
  consistently with the rest of the function.
- web_server.py _run_full_missing_tracks_process: stale import json
  mid-function shadowed the module-level import, making an earlier
  json.dumps() call reference an unbound local (F823).
- web_server.py discovery loop: platform loop variable shadowed
  the module-level platform import (F402).
- core/watchlist_scanner.py: 7 lambda captures of loop variables
  (B023 classic Python closure-in-loop bug) now bind at creation.

No existing tests had to change. Full suite stays at 263 passed.
2026-04-21 13:30:52 -07:00
BoulderBadgeDad
32923e366c
Merge pull request #346 from JohnBaumb/fix/ghcr-lowercase-owner
Fix GHCR tags: lowercase repository owner for Docker compatibility
2026-04-21 12:27:02 -07:00
JohnBaumb
dee0b6e3ce Fix GHCR tags: lowercase repository owner for Docker compatibility 2026-04-21 12:25:11 -07:00
BoulderBadgeDad
1ddfa727bd
Merge pull request #340 from JohnBaumb/pipeline-update
CI/CD pipeline improvements: dev nightlies, linting, caching, cleanup
2026-04-21 12:22:16 -07:00
JohnBaumb
c91e651bcd Fix duplicate nightly tag, add date+sha pinned tags, lint dev builds, preserve nightly in cleanup 2026-04-21 11:56:08 -07:00
JohnBaumb
26ba1bdc0f Add :nightly tag, dual-push to GHCR, remove pinned dev tags 2026-04-21 11:56:08 -07:00
JohnBaumb
5edd72b6cf Add dev nightly builds, ruff linting, Docker layer caching, and GHCR cleanup 2026-04-21 11:56:08 -07:00
Broque Thomas
6314827e91 Fix test-order-dependent failures in new metadata_service tests
The _DummyConfigManager stubs in test_metadata_service_musicmap.py and
test_metadata_service_artist_image.py were missing get_active_media_server(),
which the existing test_metadata_service_discography.py dummy provides.

Both files install their dummy via sys.modules["config.settings"] with an
"if not in sys.modules" guard, so whichever test file loads first wins.
When the new files load alphabetically before discography, the limited
dummy persists and later tests hit AttributeError on get_active_media_server.

Adds the same get_active_media_server method to both dummies so all three
test files are equivalent and test ordering no longer affects outcomes.
2026-04-21 11:39:56 -07:00
BoulderBadgeDad
9eaf53630c
Merge pull request #345 from kettui/fix/get_similar_artists_stream
Refactor artist image and similar-artist lookup to use source priority
2026-04-21 11:33:38 -07:00
BoulderBadgeDad
0ebb4f9d80
Merge pull request #344 from JohnBaumb/fix/ruff-lint-errors
Fix existing ruff lint errors (F541, B007)
2026-04-21 11:20:18 -07:00
JohnBaumb
a1886ed87f Fix ruff F541 and B007 lint errors 2026-04-21 11:18:40 -07:00
Antti Kettunen
6f79214439
Route artist image lookup through metadata service
- Move /api/artist/<artist_id>/image resolution into core.metadata_service.
- Resolve artist artwork through source priority, with explicit source/plugin overrides preserved.
- Keep Spotify call tracking inside the client layer to avoid double counting.
- Update similar-artist lazy loading to pass source context and add service coverage.
2026-04-21 21:14:35 +03:00
Antti Kettunen
b022a90997
Move MusicMap similar artist matching into metadata service
- Relocate the streamed MusicMap similar-artist flow out of web_server.py and into core.metadata_service.
- Match similar artists through the configured source-priority chain instead of assuming Spotify first.
- Add iTunes artwork fallback so streamed artist payloads still carry image_url when search results are sparse.
- Cover the new service behavior with tests.
2026-04-21 21:14:35 +03:00
BoulderBadgeDad
9863c947dc
Merge pull request #343 from kettui/fix/replace-print-with-logger
Tidy up logging across the app
2026-04-21 11:05:52 -07:00
Broque Thomas
cf143f70af Batch discography completion matching against pre-fetched candidates
Artist detail pages ran check_album_exists_with_editions and check_track_exists
per discography item, each firing 5+ title variations times 3 artist variations
of fuzzy LIKE searches plus fallback broad-artist queries. For a 30-album artist
that was ~450 SQL round-trips just to answer "which of these do I own."

Hoist the artist's library albums and tracks into memory once per request via
two new helpers — get_candidate_albums_for_artist and get_candidate_tracks_for_albums —
and thread them through as optional candidate_albums / candidate_tracks params on
check_album_exists_with_editions, check_album_exists_with_completeness,
check_track_exists, check_album_completion, and check_single_completion.

Batched path scores the same _calculate_album_confidence / _calculate_track_confidence
against the in-memory list, preserving Smart Edition Matching and accuracy.
Title-only cross-artist fallback still fires for collaborative-album edge cases.
None on either param preserves legacy per-item SQL behavior for unaffected callers.

Applied to both /api/library/completion-stream (library artist detail page) and
iter_artist_discography_completion_events (Artists search page). Timing logs
added to confirm the pre-fetch cost and loop elapsed time.

On a Kendrick page load, per-album resolution drops from ~8 seconds to under
the 50ms streaming sleep floor. Observed ~100x SQL reduction on the happy path.
2026-04-21 10:47:47 -07:00
Broque Thomas
739d2f67c1 Fix Reorganize All sending every album to Albums folder
The reorganize endpoints built a template context without albumtype,
so ${albumtype} silently fell through to the engine's hardcoded "Album"
default — EPs, singles, and compilations all landed in Albums/.

Wires albumtype through from the albums table's record_type column
(populated by Spotify/Deezer/iTunes enrichment workers) with track-count
fallback when record_type is missing. New helper mirrors the download
pipeline's classification logic so reorganize produces the same folders
as initial placement. Also handles Deezer's raw 'compile' value which
the Deezer worker writes directly without mapping.
2026-04-21 08:59:16 -07:00
Antti Kettunen
bf31e929b5
Filter out curl healthchecks from access logs 2026-04-21 18:13:48 +03:00
Antti Kettunen
71e114b6fe
Tighten legacy logging output
- collapse old multi-line debug bursts into single structured rows
- remove leftover DEBUG-style prefixes from message text
- keep the app log readable without losing useful trace detail
2026-04-21 18:00:31 +03:00
Antti Kettunen
01d118daa6
Separate AcoustID file logging
- keep AcoustID logs out of app.log
- route client and verification to logs/acoustid.log
- align tag writer with the soulsync logger namespace
2026-04-21 18:00:31 +03:00
Antti Kettunen
8b9284f414
Tune Gunicorn log output
- keep access logs on stdout/stderr
- filter static, Socket.IO, and boot noise
- align Gunicorn rows with app log format
2026-04-21 18:00:29 +03:00
Antti Kettunen
9619a63e4e
Ignore hidden folders by default 2026-04-21 14:45:33 +03:00
Antti Kettunen
e3e0d46af9
Add another sanity check for api_call_history.json load 2026-04-21 14:42:22 +03:00
Antti Kettunen
5265864e1f
Store all log files under the same folder as the configured app.log
If the application was using a non-standard location for app.log, the other logs would still go to the default location. Now everything goes under the same, configured folder
2026-04-21 14:42:22 +03:00
Antti Kettunen
df18801ae5
Replace beatport scraper print logging with logger, mute it by default
web_server.py is already doing some logging with the scraped data, so we don't necessarily need to include the scraper's own logs in the output.

A new env variable was added to make it possible to surface the scraper's logs if needed, but it's expected that the web server's own logging should be sufficient here
2026-04-21 14:42:22 +03:00
Antti Kettunen
da40618818
Add the option to override log level via env variables 2026-04-21 14:42:21 +03:00
Antti Kettunen
721b721077
Apply persisted log level at startup 2026-04-21 14:42:21 +03:00
Antti Kettunen
e262b22e45
Remove hard-coded debug-level logging 2026-04-21 14:42:21 +03:00
Antti Kettunen
67a5bcb5a7
Rename logger namespace from newmusic to soulsync 2026-04-21 14:42:21 +03:00
Antti Kettunen
fe7ae29b8a
Replace more print logs with proper logger usage 2026-04-21 14:42:21 +03:00
Antti Kettunen
ac17bc8d87
Adjust severity for some web_server logs, remove redundant prefixes 2026-04-21 14:42:21 +03:00
Antti Kettunen
2b856b65a7
Replace web_server print statements with logger use
print calls only end up in stdout, so there will be no trace of them
once docker loses access to its own logs. Using the logger makes sure
that logs end up in the filesystem as well
2026-04-21 14:42:19 +03:00
Broque Thomas
14359a1f98 Add confirmation dialog to Reorganize All and fix button style
Shows standard SoulSync confirm dialog before bulk reorganize with
album count and template preview. Button now uses enhanced-sync-btn
class to match other artist header buttons.
2026-04-20 23:51:17 -07:00
Broque Thomas
95cf1eeeea Add Reorganize All Albums button, version bump to 2.35, changelogs
New "Reorganize All" button in enhanced library artist header processes
all albums sequentially using the configured path template.

Version bumped to 2.35. Updated What's New modal with major features
(Discography Backfill, Multi-Artist Tagging, Enriched Downloads,
Template Delimiters, Reorganize All). Updated helper.js changelog
with all April 20 fixes and features.
2026-04-20 23:46:37 -07:00
Broque Thomas
e39a3f2af7 Add multi-artist tagging options: separator, multi-value tags, feat-in-title
Three new settings in Paths & Organization:
- Artist Tag Separator: choose comma, semicolon, or slash between artists
- Write multi-value ARTISTS tag: each artist as separate tag value for
  Navidrome/Jellyfin multi-artist linking (FLAC ARTISTS key, ID3 TPE1
  multi-value, MP4 multi-entry)
- Move featured artists to title: keep only primary artist in ARTIST
  tag, append others as (feat. ...) in track title

All opt-in with defaults matching current behavior. Raw artist list
stored on metadata dict for tag writers to access without re-parsing.
2026-04-20 23:18:10 -07:00
Broque Thomas
fd014e2745 Use parent folder name as artist override in auto-import
When staging files are organized as Artist/Albums/AlbumFolder or
Artist/AlbumFolder, the auto-import now uses the parent folder name
as the artist instead of trusting embedded file tags.

Uses relative path from staging root to determine folder depth, so
albums directly in staging root don't accidentally pick up container
paths as artist names. Common category subfolder names (Albums,
Singles, EPs, Mixtapes, etc.) are recognized and skipped.

Fixes mixtapes and compilations where file tags have DJ names or
incorrect artists (e.g. files tagged as "Slim" in a 2Pac folder).
2026-04-20 22:56:34 -07:00
Broque Thomas
9c15d00128 Enrich downloads page cards with artwork, artist, album, and source badges
The downloads page previously showed only title and source per download.
Now shows album artwork thumbnail, artist name, album name, source badge,
and quality badge (after post-processing). All metadata comes from the
existing matched_downloads_context — no extra API calls needed.

Falls back gracefully to title-only display when context metadata is
not available (e.g. orphaned Soulseek transfers with no task mapping).
2026-04-20 22:18:20 -07:00
Broque Thomas
ed97fecc31 Add Discography Backfill maintenance job
New repair job that scans each artist in the library, fetches their
full discography from metadata sources, and creates findings for any
tracks not already owned. Users review findings and click "Add to
Wishlist" to queue missing tracks for download.

Respects content filters (live/remix/acoustic/instrumental/compilation)
and release type filters (album/EP/single). Opt-in, disabled by default,
runs weekly, processes up to 50 artists per run with rate limiting.
2026-04-20 22:04:38 -07:00
Broque Thomas
7287a9d184 Fix test using old deezer_track_id column name
The unknown_artist_fixer was updated to use deezer_id (matching the
actual tracks table column) but the test still passed deezer_track_id
in the track dict, causing the deezer lookup to miss and fall back
to Spotify.
2026-04-20 21:11:45 -07:00
Broque Thomas
dad915dc2c Add ${var} hint text to single, playlist, and video template hints 2026-04-20 21:07:16 -07:00
Broque Thomas
93e036848b Add ${var} delimiter syntax for path templates
Users can now append literal text to template variables using curly
braces: ${albumtype}s produces "Albums", "Singles", "EPs". Without
braces, $albumtypes was rejected as an unknown variable by validation.

Both syntaxes work: $albumtype (plain) and ${albumtype} (delimited).
Bracket vars are resolved first to prevent partial matching conflicts.
Validation updated for album, single, and playlist templates.
2026-04-20 18:03:11 -07:00
Broque Thomas
c3f88a713a Fix unknown_artist_fixer crash on missing deezer_track_id column
The tracks table uses 'deezer_id' not 'deezer_track_id'. The query
and source field mapping both referenced the wrong column name.
2026-04-20 16:29:38 -07:00
Broque Thomas
e1819186e4 Add fix action prompt for AcoustID mismatch findings
AcoustID findings had no Fix button and bulk "Fix Selected" silently
defaulted to retag with no user choice. Now shows a 3-option prompt
(Retag / Re-download / Delete) for both individual and bulk fix,
matching the pattern used by orphan files and dead files.
2026-04-20 16:22:26 -07:00
Broque Thomas
8ff3818eec Fix DownloadOrchestrator has no attribute 'base_url' error
soulseek_client is the DownloadOrchestrator, not the SoulseekClient.
The base_url check needs to go through soulseek_client.soulseek.base_url
to reach the actual slskd client instance.
2026-04-20 14:00:44 -07:00
Broque Thomas
741712ce6e Fix playlist mode downloads getting no metadata or cover art
Playlist folder mode passed album_info=None to _enhance_file_metadata,
which crashed on the first .get() call. The try/except caught it and
called _wipe_source_tags, stripping ALL metadata from the file.

Now normalizes None album_info to empty dict at the top of the function,
and adds a cover art fallback that pulls the image URL from the
spotify_album context when album_info doesn't have one.
2026-04-20 13:54:01 -07:00
Broque Thomas
2249aa7594 Fix repair worker crash on zero interval_hours division
Jobs with interval_hours set to 0 caused ZeroDivisionError in
_pick_next_job staleness calculation. Now skips jobs with invalid
(zero or negative) intervals.
2026-04-20 13:46:38 -07:00
BoulderBadgeDad
c63766145e
Merge pull request #339 from kettui/refactor/get_artist_album_tracks
Refactor artist album track lookup to use source priority
2026-04-20 12:54:15 -07:00
Broque Thomas
44cb4a5403 Fix Navidrome full refresh importing albums from unselected music folders
The Subsonic getArtist endpoint doesn't support musicFolderId filtering,
so when an artist exists in multiple libraries, all their albums were
imported regardless of which music folder was selected in settings.

Now passes musicFolderId to getArtist (in case Navidrome supports it),
and as a fallback filters albums against a cached set of album IDs
built from getAlbumList2 (which reliably supports musicFolderId).
The set is built once per session and invalidated on folder change.
2026-04-20 12:52:37 -07:00
Broque Thomas
1695953705 Fix AcoustID high-confidence skip letting wrong files through
The high-confidence fingerprint skip (≥0.95) assumed title mismatches
were language/script differences and bypassed verification. But a high
fingerprint score just means AcoustID identified the audio confidently —
not that it matches the requested track. Now requires partial title
(≥0.55) or artist (≥0.60) similarity before skipping, so completely
wrong files (e.g. different song/artist from same remix producer) are
correctly rejected.
2026-04-20 12:46:01 -07:00