feat: integrate musicfs for post-download metadata enrichment

- Add internal/musicfs package: gRPC client, TriggerRescan (stream consumer), EnrichFiles (BatchUpdateMetadata), DeriveSubdir helper
- Add MusicFS config section (enabled, endpoint, origin_id, origin_root, timeout)
- Wire MusicFSClient into PollDownloadWorker: on download completion, trigger scoped rescan then push enriched metadata
- Fix album auto-persist in monitor workflow: persist artist+album from metadata-agregator before saving torrent/download
- Add filterByActiveSeeders: skip torrents where magnet resolution found 0 real active seeders
- Add musicfs proto to buf.gen.yaml inputs
- Enricher only sends non-empty fields to avoid overwriting existing file tag data with empty values
This commit is contained in:
Alexander
2026-05-17 23:32:44 +02:00
parent eac8196dcc
commit 76e426c5d3
11 changed files with 419 additions and 19 deletions
+65 -8
View File
@@ -10,6 +10,8 @@ import (
"homelab.lan/music-agregator/internal/analysis"
"homelab.lan/music-agregator/internal/database"
"homelab.lan/music-agregator/internal/metadata"
"homelab.lan/music-agregator/internal/musicfs"
"homelab.lan/music-agregator/internal/torrent"
)
@@ -23,14 +25,18 @@ func (PollDownloadArgs) Kind() string { return "poll_download" }
type PollDownloadWorker struct {
river.WorkerDefaults[PollDownloadArgs]
TorrentClient torrent.TorrentClient
Downloads *database.DownloadRepository
DownloadFiles *database.DownloadFileRepository
AlbumReleases *database.AlbumReleaseRepository
TrackReleases *database.TrackReleaseRepository
RiverClient *river.Client[pgx.Tx]
PathMapper *torrent.PathMapper
Analyzer *analysis.ReleaseAnalyzer
TorrentClient torrent.TorrentClient
Downloads *database.DownloadRepository
DownloadFiles *database.DownloadFileRepository
AlbumReleases *database.AlbumReleaseRepository
TrackReleases *database.TrackReleaseRepository
RiverClient *river.Client[pgx.Tx]
PathMapper *torrent.PathMapper
Analyzer *analysis.ReleaseAnalyzer
MusicFSClient *musicfs.Client
MusicFSOriginID string
MusicFSOriginRoot string
MetadataService *metadata.MetadataService
}
func (w *PollDownloadWorker) Work(ctx context.Context, job *river.Job[PollDownloadArgs]) error {
@@ -117,9 +123,60 @@ func (w *PollDownloadWorker) onCompleted(ctx context.Context, args PollDownloadA
}
}
if w.MusicFSClient != nil {
w.syncAndEnrichMusicFS(ctx, args, contentPath)
}
return nil
}
func (w *PollDownloadWorker) syncAndEnrichMusicFS(ctx context.Context, args PollDownloadArgs, contentPath string) {
subdir := musicfs.DeriveSubdir(w.MusicFSOriginRoot, contentPath)
result, err := w.MusicFSClient.TriggerRescan(ctx, w.MusicFSOriginID, subdir)
if err != nil {
log.Warn().Err(err).Msg("musicfs rescan failed")
return
}
log.Info().
Int("new_files", len(result.NewFiles)).
Uint64("bytes_synced", result.BytesSynced).
Msg("musicfs rescan complete")
if len(result.NewFiles) == 0 {
return
}
download, err := w.Downloads.GetByID(ctx, args.DownloadID)
if err != nil {
log.Warn().Err(err).Msg("failed to get download for enrichment")
return
}
if w.MetadataService == nil {
log.Warn().Msg("no metadata service configured, skipping enrichment")
return
}
album, err := w.MetadataService.GetAlbumByID(ctx, download.AlbumID)
if err != nil {
log.Warn().Err(err).Str("album_id", download.AlbumID).Msg("failed to get album for enrichment")
return
}
albumMeta, err := w.MetadataService.GetAlbum(ctx, album.ExternalID)
if err != nil {
log.Warn().Err(err).Str("external_id", album.ExternalID).Msg("failed to get album metadata for enrichment")
return
}
err = w.MusicFSClient.EnrichFiles(ctx, result.NewFiles, albumMeta)
if err != nil {
log.Warn().Err(err).Msg("musicfs enrichment failed")
}
}
func (w *PollDownloadWorker) reschedule(ctx context.Context, args PollDownloadArgs) error {
if w.RiverClient == nil {
log.Warn().Str("download_id", args.DownloadID).Msg("no river client, cannot reschedule poll_download")