Some checks failed
Build and Push Docker Image / build (pull_request) Failing after 38s
This commit includes: 1. Live/Remix Filtering Feature: - Added ignore_live and ignore_remix columns to artist_settings table (migration 010) - Updated ArtistSettings struct with IgnoreLive and IgnoreRemix fields - Modified SaveArtistSettings and UpdateArtistSettings to handle new fields - Extended FilterOptions struct with IgnoreLive and IgnoreRemix - Updated ApplyTypeToggles and ApplyTypeTogglesToReleaseGroups to filter Live/Remix types - Added toggleIgnoreLive and toggleIgnoreRemix handlers in web layer - Updated ArtistData view model and artist.html template with new toggle UI - Comprehensive test coverage for all new functionality 2. CI/CD Pipeline with Gitea Actions: - Added .gitea/workflows/docker-build.yml for automated Docker builds - Workflow triggers on pushes to main/master and tags, plus PRs - Runs Go tests before building - Builds and pushes multi-architecture Docker images to gitea.mrixs.me - Includes caching for faster subsequent builds - Proper tagging strategy (branch, semver, SHA) - CI-CD-GUIDE.md documentation 3. Cleanup: - Removed temporary build artifacts and coverage files
92 lines
3.2 KiB
Go
92 lines
3.2 KiB
Go
package scanner
|
|
|
|
import (
|
|
"naviwatcher/internal/database"
|
|
"naviwatcher/internal/musicbrainz"
|
|
)
|
|
|
|
// MissingRelease describes an external release that has no sufficiently similar
|
|
// local album. It is a flattened, consumer-friendly projection of a
|
|
// database.ExternalRelease.
|
|
type MissingRelease struct {
|
|
RGID string `json:"rgid"`
|
|
ArtistID string `json:"artist_id"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
ReleaseDate string `json:"release_date"`
|
|
}
|
|
|
|
// FilterIsSuppressed reports whether an external release is dropped by the type toggles.
|
|
// A release counts as a Single/Compilation via either its primary Type or its
|
|
// secondary types, matching musicbrainz.FilterReleaseGroups so both the
|
|
// cache-miss (store-time) and read-time paths agree.
|
|
//
|
|
// This function 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 FilterIsSuppressed(filter musicbrainz.FilterOptions, ext database.ExternalRelease) bool {
|
|
filtered := musicbrainz.ApplyTypeToggles([]database.ExternalRelease{ext}, filter)
|
|
return len(filtered) == 0
|
|
}
|
|
|
|
// FindMissingReleases compares an artist's external discography against the
|
|
// user's local albums and returns the releases that are present externally but
|
|
// have no sufficiently similar local album.
|
|
//
|
|
// Rules:
|
|
// - External releases flagged IsIgnored are never reported.
|
|
// - External releases suppressed by the per-artist type toggles (filter) are
|
|
// never reported.
|
|
// - A local album only matches an external release for the same ArtistID.
|
|
// - An external release is "missing" when none of the local albums (same
|
|
// 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 musicbrainz.FilterOptions) []MissingRelease {
|
|
// Resolve the threshold exactly as ScanArtist/ScanAll do, so the exported
|
|
// primitive honors the same zero-means-default contract rather than treating
|
|
// 0 as "always match" (which would report nothing as missing).
|
|
threshold = resolveThreshold(threshold)
|
|
|
|
// Group local albums by artist for O(1) lookup per external release.
|
|
localByArtist := make(map[string][]database.LocalAlbum)
|
|
for _, a := range local {
|
|
localByArtist[a.ArtistID] = append(localByArtist[a.ArtistID], a)
|
|
}
|
|
|
|
var missing []MissingRelease
|
|
for _, ext := range external {
|
|
if ext.IsIgnored {
|
|
continue
|
|
}
|
|
if FilterIsSuppressed(filter, ext) {
|
|
continue
|
|
}
|
|
|
|
albums := localByArtist[ext.ArtistID]
|
|
matched := false
|
|
for _, a := range albums {
|
|
if IsMatch(a.Title, ext.Title, threshold) {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if matched {
|
|
continue
|
|
}
|
|
|
|
missing = append(missing, MissingRelease{
|
|
RGID: ext.RGID,
|
|
ArtistID: ext.ArtistID,
|
|
Title: ext.Title,
|
|
Type: ext.Type,
|
|
ReleaseDate: ext.ReleaseDate,
|
|
})
|
|
}
|
|
|
|
return missing
|
|
}
|