musicbrainz-provider #2
@@ -108,6 +108,7 @@ Based on the specification (docs/Specification.md), the application follows a mo
|
|||||||
- Implements the comparison algorithm (0.85 similarity threshold)
|
- Implements the comparison algorithm (0.85 similarity threshold)
|
||||||
- Handles removal of special characters, years, and bracketed keywords
|
- Handles removal of special characters, years, and bracketed keywords
|
||||||
- Compares local albums vs. external discographies
|
- Compares local albums vs. external discographies
|
||||||
|
- Applies per-artist ignore_singles/ignore_compilations filters at scan time for immediate responsiveness to setting changes
|
||||||
|
|
||||||
Shared normalization lives in `internal/normalize` (`NormalizeString`, `NormalizeArtistName`) — this is the single source of truth for string normalization, reused by both `internal/musicbrainz` and `internal/scanner`. Do NOT add local copies of normalization logic elsewhere.
|
Shared normalization lives in `internal/normalize` (`NormalizeString`, `NormalizeArtistName`) — this is the single source of truth for string normalization, reused by both `internal/musicbrainz` and `internal/scanner`. Do NOT add local copies of normalization logic elsewhere.
|
||||||
|
|
||||||
|
|||||||
@@ -81,17 +81,17 @@ This creates two filtering points:
|
|||||||
- [x] Must pass before next task
|
- [x] Must pass before next task
|
||||||
|
|
||||||
### Task 3: Document the Data Flow
|
### Task 3: Document the Data Flow
|
||||||
- [ ] Update documentation to clearly explain how ignore_singles/ignore_compilations settings propagate through the system
|
- [x] Update documentation to clearly explain how ignore_singles/ignore_compilations settings propagate through the system
|
||||||
- [ ] Add comments to key functions explaining the filtering flow
|
- [x] Add comments to key functions explaining the filtering flow
|
||||||
- [ ] Ensure CLAUDE.md accurately reflects the current implementation
|
- [x] Ensure CLAUDE.md accurately reflects the current implementation
|
||||||
- [ ] Create diagrams or flowcharts if helpful for understanding
|
- [x] Create diagrams or flowcharts if helpful for understanding
|
||||||
- [ ] Must pass before next task
|
- [x] Must pass before next task
|
||||||
|
|
||||||
### Task 4: Final Verification
|
### Task 4: Final Verification
|
||||||
- [ ] Run full test suite to ensure all changes work correctly
|
- [x] Run full test suite to ensure all changes work correctly
|
||||||
- [ ] Verify no breaking changes were introduced
|
- [x] Verify no breaking changes were introduced
|
||||||
- [ ] Confirm that the implementation handles the use case described in the memory file
|
- [x] Confirm that the implementation handles the use case described in the memory file
|
||||||
- [ ] Update this plan with completion status
|
- [x] Update this plan with completion status
|
||||||
|
|
||||||
## Post-Completion
|
## Post-Completion
|
||||||
*Items requiring manual intervention or external systems - no checkboxes, informational only*
|
*Items requiring manual intervention or external systems - no checkboxes, informational only*
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ func SyncArtistDiscography(
|
|||||||
// ignore_singles / ignore_compilations take effect without waiting for cache
|
// ignore_singles / ignore_compilations take effect without waiting for cache
|
||||||
// expiry. (Status/type inclusion was already applied when the rows were first
|
// expiry. (Status/type inclusion was already applied when the rows were first
|
||||||
// synced and stored, so only the toggles can change.)
|
// synced and stored, so only the toggles can change.)
|
||||||
|
//
|
||||||
|
// The MusicBrainz sync path applies filtering at store-time (when caching
|
||||||
|
// release groups from the API), while the scanner path applies filtering at
|
||||||
|
// read-time (when retrieving cached data). This dual-path approach ensures:
|
||||||
|
// 1. Storage efficiency: filtered results are stored, reducing database size
|
||||||
|
// 2. Real-time responsiveness: changes to ignore_singles/ignore_compilations
|
||||||
|
// take effect immediately without waiting for cache expiry
|
||||||
|
// 3. Consistency: both paths use the same filtering logic via
|
||||||
|
// musicbrainz.ApplyTypeToggles
|
||||||
if fresh {
|
if fresh {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return nil, fmt.Errorf("sync artist discography: %w", err)
|
return nil, fmt.Errorf("sync artist discography: %w", err)
|
||||||
|
|||||||
@@ -25,6 +25,20 @@ type MissingRelease struct {
|
|||||||
// the dashboard, artist page, and Telegram digest — rather than waiting for the
|
// the dashboard, artist page, and Telegram digest — rather than waiting for the
|
||||||
// artist's MusicBrainz cache to expire and the rows to be pruned on the next
|
// artist's MusicBrainz cache to expire and the rows to be pruned on the next
|
||||||
// cache-miss re-sync.
|
// cache-miss re-sync.
|
||||||
|
//
|
||||||
|
// The scanner applies filtering at at scan/read time (not only when the MusicBrainz
|
||||||
|
// discography is synced) so a user flipping a toggle takes effect immediately on
|
||||||
|
// the dashboard, artist page, and Telegram digest — rather than waiting for the
|
||||||
|
// artist's MusicBrainz cache to expire and the rows to be pruned on the next
|
||||||
|
// cache-miss re-sync.
|
||||||
|
//
|
||||||
|
// The scanner path applies filtering at read-time, while the MusicBrainz sync
|
||||||
|
// path applies filtering at store-time. This dual-path approach ensures:
|
||||||
|
// 1. Storage efficiency: filtered results are stored during MusicBrainz sync
|
||||||
|
// 2. Real-time responsiveness: changes to ignore_singles/ignore_compilations
|
||||||
|
// take effect immediately in scan results
|
||||||
|
// 3. Consistency: both paths use the same filtering logic via
|
||||||
|
// musicbrainz.ApplyTypeToggles
|
||||||
type TypeFilter struct {
|
type TypeFilter struct {
|
||||||
IgnoreSingles bool
|
IgnoreSingles bool
|
||||||
IgnoreCompilations bool
|
IgnoreCompilations bool
|
||||||
@@ -34,6 +48,10 @@ type TypeFilter struct {
|
|||||||
// A release counts as a Single/Compilation via either its primary Type or its
|
// A release counts as a Single/Compilation via either its primary Type or its
|
||||||
// secondary types, matching musicbrainz.FilterReleaseGroups so both the
|
// secondary types, matching musicbrainz.FilterReleaseGroups so both the
|
||||||
// cache-miss (store-time) and read-time paths agree.
|
// cache-miss (store-time) and read-time paths agree.
|
||||||
|
//
|
||||||
|
// This method reuses the centralized filtering logic from the musicbrainz
|
||||||
|
// package to ensure consistency between the scanner's read-time filtering
|
||||||
|
// and the MusicBrainz sync's store-time filtering.
|
||||||
func (f TypeFilter) suppressed(ext database.ExternalRelease) bool {
|
func (f TypeFilter) suppressed(ext database.ExternalRelease) bool {
|
||||||
// Use the centralized filtering logic from musicbrainz package
|
// Use the centralized filtering logic from musicbrainz package
|
||||||
opts := musicbrainz.FilterOptions{
|
opts := musicbrainz.FilterOptions{
|
||||||
@@ -55,6 +73,10 @@ func (f TypeFilter) suppressed(ext database.ExternalRelease) bool {
|
|||||||
// - A local album only matches an external release for the same ArtistID.
|
// - A local album only matches an external release for the same ArtistID.
|
||||||
// - An external release is "missing" when none of the local albums (same
|
// - An external release is "missing" when none of the local albums (same
|
||||||
// ArtistID) IsMatch at the given threshold.
|
// ArtistID) IsMatch at the given threshold.
|
||||||
|
//
|
||||||
|
// The filter.suppressed() check applies the same IgnoreSingles/IgnoreCompilations
|
||||||
|
// filtering logic as used in the MusicBrainz sync path, ensuring consistent
|
||||||
|
// behavior between cache-hit (read-time) and cache-miss (store-time) paths.
|
||||||
func FindMissingReleases(local []database.LocalAlbum, external []database.ExternalRelease, threshold float64, filter TypeFilter) []MissingRelease {
|
func FindMissingReleases(local []database.LocalAlbum, external []database.ExternalRelease, threshold float64, filter TypeFilter) []MissingRelease {
|
||||||
// Resolve the threshold exactly as ScanArtist/ScanAll do, so the exported
|
// Resolve the threshold exactly as ScanArtist/ScanAll do, so the exported
|
||||||
// primitive honors the same zero-means-default contract rather than treating
|
// primitive honors the same zero-means-default contract rather than treating
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ func ScanArtist(ctx context.Context, db *database.DB, artistID string, threshold
|
|||||||
// Apply the artist's type toggles at read time so ignore_singles /
|
// Apply the artist's type toggles at read time so ignore_singles /
|
||||||
// ignore_compilations changes take effect immediately, without waiting for
|
// ignore_compilations changes take effect immediately, without waiting for
|
||||||
// the MusicBrainz cache to expire and prune rows on the next re-sync.
|
// the MusicBrainz cache to expire and prune rows on the next re-sync.
|
||||||
|
// This ensures that changes to ignore_singles/ignore_compilations take
|
||||||
|
// effect immediately in the scanner, providing real-time responsiveness
|
||||||
|
// to user preference changes.
|
||||||
settings, err := database.GetArtistSettings(db, artistID)
|
settings, err := database.GetArtistSettings(db, artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If artist settings don't exist, use empty filter (no filtering)
|
// If artist settings don't exist, use empty filter (no filtering)
|
||||||
@@ -57,6 +60,10 @@ func ScanArtist(ctx context.Context, db *database.DB, artistID string, threshold
|
|||||||
// and computes the missing releases for each. Results are concatenated into a
|
// and computes the missing releases for each. Results are concatenated into a
|
||||||
// single slice across all artists.
|
// single slice across all artists.
|
||||||
//
|
//
|
||||||
|
// The function retrieves all artist settings once and then calls ScanArtist
|
||||||
|
// for each monitored artist, ensuring consistent application of
|
||||||
|
// ignore_singles/ignore_compilations filters across all artists.
|
||||||
|
//
|
||||||
// ctx.Err() is checked between artists; if cancellation occurs mid-iteration,
|
// ctx.Err() is checked between artists; if cancellation occurs mid-iteration,
|
||||||
// scanning stops early and the accumulated results so far are returned along
|
// scanning stops early and the accumulated results so far are returned along
|
||||||
// with the cancellation error. threshold follows the same contract as
|
// with the cancellation error. threshold follows the same contract as
|
||||||
|
|||||||
Reference in New Issue
Block a user