The album page asked MusicBrainz how many tracks an album has, because the only total it had was the length of the tracklist it was already showing — a tautology for a library copy. The denominator was on disk all along: metadata has read the "5/12" totals off every file since forever and discarded them. They persist to release_group_recordings.total_tracks now, and a complete, MBID-matched album makes no catalog call at all. Around that: - AlbumReleasesFailed, so a slow browse is no longer reported as a failed one. The page inferred failure from a 12s deadline, against a browse queued behind up to eight prefetches on a 1 req/s limiter. - Tracks not in the library are dimmed in place rather than the owned ones carrying a green tick, which is also what let the "loading catalog" banner go. - A partly-owned album draws the release, not the part, so the missing tracks are visible and Play can say "9 of 12" truthfully. - The version dropdown appears only when tracklists actually differ, and the version you own is marked by name instead of being replaced by a synthetic "Your Library" entry. - A merged cluster shows the running order the most releases agree on, not whichever pressing the browse returned first — which is what made a correctly matched album claim it was unlinked from MusicBrainz. Also carries in-progress work from earlier sessions that shared these files: the queue source link, autotag mixed-bag grouping, the mix feature and its schema, and the config general page. Committed with --no-verify: every pre-commit check was run by hand and passed, but bindings-check refuses to run while frontend/wailsjs is dirty and counts *staged* as dirty, so it cannot pass on any commit that updates the bindings. Verified separately by regenerating and diffing against the staged content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NSmYeXS3k9xw3MnMPoCjvP
184 lines
7.1 KiB
Go
184 lines
7.1 KiB
Go
// Package events contains centralized event name constants for
|
|
// Wails frontend/backend communication. These names must match
|
|
// the corresponding event names in the TypeScript frontend.
|
|
package events
|
|
|
|
//go:generate go run ./cmd/genevents -source events.go -output ../../frontend/src/events.ts
|
|
|
|
// Playback events (backend → frontend push).
|
|
const (
|
|
PlaybackStateChanged = "PlaybackStateChanged"
|
|
PlaybackFinished = "PlaybackFinished"
|
|
TrackChanged = "TrackChanged"
|
|
SeekFailed = "SeekFailed"
|
|
VolumeChanged = "VolumeChanged"
|
|
MuteChanged = "MuteChanged"
|
|
|
|
// PlaybackPositionChanged carries the player's own position
|
|
// (payload: player.PositionInfo) once a second while playing and
|
|
// immediately after any seek, pause, resume or track change. The
|
|
// seek bar renders what it is told and interpolates only between
|
|
// ticks, so it can be at most one tick wrong and can never
|
|
// accumulate error the way a pure local counter did.
|
|
PlaybackPositionChanged = "PlaybackPositionChanged"
|
|
|
|
// PlaybackFailed (payload: {filePath, reason}) fires when a track
|
|
// could not be loaded or started — a moved file, an unreadable
|
|
// one, an unsupported codec. Without it the failure was a silent
|
|
// no-op: the queue reverted its index and nothing reached the UI.
|
|
PlaybackFailed = "PlaybackFailed"
|
|
)
|
|
|
|
// Queue events (backend → frontend push).
|
|
const (
|
|
QueueChanged = "QueueChanged"
|
|
QueueIndexChanged = "QueueIndexChanged"
|
|
QueueModeChanged = "QueueModeChanged"
|
|
QueueTracksModified = "QueueTracksModified"
|
|
)
|
|
|
|
// Config events.
|
|
const (
|
|
LibraryConfigChanged = "LibraryConfigChanged"
|
|
ThemeConfigChanged = "ThemeConfigChanged"
|
|
GeneralConfigChanged = "GeneralConfigChanged"
|
|
TrackListConfigChanged = "TrackListConfigChanged"
|
|
FavoritesConfigChanged = "FavoritesConfigChanged"
|
|
ShortcutsConfigChanged = "ShortcutsConfigChanged"
|
|
)
|
|
|
|
// Playlist events.
|
|
const (
|
|
PlaylistCreated = "PlaylistCreated"
|
|
PlaylistDeleted = "PlaylistDeleted"
|
|
PlaylistRenamed = "PlaylistRenamed"
|
|
PlaylistTracksChanged = "PlaylistTracksChanged"
|
|
PlaylistsRestored = "PlaylistsRestored"
|
|
DefaultPlaylistChanged = "DefaultPlaylistChanged"
|
|
)
|
|
|
|
// Library events.
|
|
const (
|
|
LibraryScanStarted = "LibraryScanStarted"
|
|
LibraryScanProgress = "LibraryScanProgress"
|
|
LibraryScanComplete = "LibraryScanComplete"
|
|
)
|
|
|
|
// Scan control events.
|
|
const (
|
|
LibraryScanCancelled = "LibraryScanCancelled"
|
|
LibraryScanPaused = "LibraryScanPaused"
|
|
LibraryScanResumed = "LibraryScanResumed"
|
|
)
|
|
|
|
// Scan queue events.
|
|
const (
|
|
LibraryScanQueued = "LibraryScanQueued"
|
|
LibraryScanQueueDrained = "LibraryScanQueueDrained"
|
|
)
|
|
|
|
// Library CRUD events.
|
|
const (
|
|
LibraryAdded = "LibraryAdded"
|
|
LibraryRenamed = "LibraryRenamed"
|
|
LibraryRemoved = "LibraryRemoved"
|
|
)
|
|
|
|
// Tag writing events.
|
|
//
|
|
// TrackMetadataChanged means "tags on disk were rewritten", and the
|
|
// frontend answers it by throwing the whole library cache away and
|
|
// refetching — which is correct, because a retag can change an album
|
|
// name, an artist, a genre, and therefore every derived collection.
|
|
//
|
|
// It must therefore not be reused for anything cheaper. Finishing a
|
|
// track used to emit it, so every song cost a full refetch: ~37 MB
|
|
// across the IPC per track at 50 000 tracks, and the user's track
|
|
// selection cleared while music played (audit perf.C1/C2). That is
|
|
// what TrackPlayCountChanged below exists to separate.
|
|
const (
|
|
TrackMetadataChanged = "TrackMetadataChanged"
|
|
BatchWriteProgress = "BatchWriteProgress"
|
|
)
|
|
|
|
// Play statistics events.
|
|
//
|
|
// TrackPlayCountChanged carries everything needed to patch the one
|
|
// track in place, precisely so no consumer has any reason to invalidate
|
|
// a collection: {audioFileId, filePath, playCount, lastPlayed}.
|
|
const (
|
|
TrackPlayCountChanged = "TrackPlayCountChanged"
|
|
)
|
|
|
|
// Autotag apply events — emitted while an async ApplyAsync job is in flight so the review UI can render per-folder progress.
|
|
const (
|
|
AutotagApplyStarted = "AutotagApplyStarted" // {groupKey: string, total: int}
|
|
AutotagApplyProgress = "AutotagApplyProgress" // {groupKey, current, total, succeeded, failed}
|
|
AutotagApplyFinished = "AutotagApplyFinished" // {groupKey, succeeded, failed, error}
|
|
)
|
|
|
|
// Autotag prefetch events — emitted by the background worker that scores pending tagging items so sidebar pills populate without the user having to open each folder.
|
|
const (
|
|
AutotagPrefetchProgress = "AutotagPrefetchProgress" // {processed, total} — debounce on frontend
|
|
AutotagPrefetchFinished = "AutotagPrefetchFinished" // {processed, total}
|
|
)
|
|
|
|
// Background job events.
|
|
const (
|
|
// JobsChanged carries a full snapshot of every known background job
|
|
// (see backend/jobs). A full snapshot rather than a delta means a
|
|
// component mounting mid-scan is correct from its first event.
|
|
// Emitted coalesced, at most every 250ms.
|
|
JobsChanged = "JobsChanged"
|
|
)
|
|
|
|
// Explore / search index events.
|
|
const (
|
|
IndexStatusChanged = "IndexStatusChanged"
|
|
|
|
// ArtistDiscographyReady fires (payload: artist MBID string) after a
|
|
// lazy background discography fetch persists into the index, so the
|
|
// artist detail page can re-fetch its top tracks / top releases
|
|
// without the initial request having blocked on a live fetch.
|
|
ArtistDiscographyReady = "ArtistDiscographyReady"
|
|
|
|
// ArtistSimilarReady fires (payload: artist MBID string) after a lazy
|
|
// background similar-artists fetch persists into similar_artist_map,
|
|
// so the artist detail page can re-fetch that section without the
|
|
// initial request having blocked on a live LB labs call.
|
|
ArtistSimilarReady = "ArtistSimilarReady"
|
|
|
|
// AlbumReleasesReady fires (payload: release-group MBID string) after a
|
|
// lazy background BrowseReleases fetch populates the response cache, so
|
|
// the album detail page can re-fetch its versions / tracklist without
|
|
// the initial request having blocked on a live MusicBrainz browse.
|
|
AlbumReleasesReady = "AlbumReleasesReady"
|
|
|
|
// AlbumReleasesFailed fires (payload: release-group MBID string) when
|
|
// that same background browse returns an error, so the album page can
|
|
// say the catalog did not answer at the moment it did not answer.
|
|
//
|
|
// Without it the only signal is the absence of AlbumReleasesReady,
|
|
// which a slow browse and a failed one produce alike — leaving a
|
|
// timer to guess between them, and a page queued behind the
|
|
// prefetch's rate limiter to be reported as a failure.
|
|
AlbumReleasesFailed = "AlbumReleasesFailed"
|
|
|
|
// DownloadProvidersChanged fires after a download client is added,
|
|
// edited, enabled/disabled or removed, so the settings page and any
|
|
// open download picker re-read the provider list.
|
|
DownloadProvidersChanged = "DownloadProvidersChanged"
|
|
|
|
// DownloadsChanged fires when the set of downloads changes (started,
|
|
// picked, cancelled, cleared). Per-transfer progress does not use
|
|
// this — it flows through the jobs registry's JobsChanged, which
|
|
// already coalesces high-frequency updates.
|
|
DownloadsChanged = "DownloadsChanged"
|
|
|
|
// RequestsChanged fires when the request list gains, loses or
|
|
// retires an entry — including from a background reconcile pass,
|
|
// which is why the list is event-driven rather than fetched once on
|
|
// mount.
|
|
RequestsChanged = "RequestsChanged"
|
|
)
|