Refactor MonitorAlbumStream: EventPublisher interface, background workflows, DB-before-qBit save order

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Alexander
2026-05-11 15:54:01 +02:00
parent 52e81faedd
commit 5a5660bf21
4 changed files with 643 additions and 146 deletions
+56
View File
@@ -49,6 +49,10 @@ type MusicAgregatorService struct {
albumReleases *database.AlbumReleaseRepository
trackReleases *database.TrackReleaseRepository
analyzer *analysis.ReleaseAnalyzer
workflowRuns *database.WorkflowRunRepository
albumEvents *database.AlbumEventRepository
shutdownCtx context.Context
shutdownCancel context.CancelFunc
}
func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.Tx], torrentClient torrent.TorrentClient, pathMapper *torrent.PathMapper, db *database.DB) (*MusicAgregatorService, error) {
@@ -70,6 +74,8 @@ func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.T
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
return &MusicAgregatorService{
config: cfg,
metadata: metadata.NewMetadataService(metadataClient, db),
@@ -85,6 +91,10 @@ func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.T
albumReleases: database.NewAlbumReleaseRepository(db.Pool),
trackReleases: database.NewTrackReleaseRepository(db.Pool),
analyzer: analysis.NewReleaseAnalyzer(db),
workflowRuns: database.NewWorkflowRunRepository(db.Pool),
albumEvents: database.NewAlbumEventRepository(db.Pool),
shutdownCtx: ctx,
shutdownCancel: cancel,
}, nil
}
@@ -97,6 +107,8 @@ func NewMusicAgregatorServiceWithDeps(
pathMapper *torrent.PathMapper,
db *database.DB,
) *MusicAgregatorService {
ctx, cancel := context.WithCancel(context.Background())
return &MusicAgregatorService{
metadata: metadata,
indexer: searcher,
@@ -111,15 +123,59 @@ func NewMusicAgregatorServiceWithDeps(
albumReleases: database.NewAlbumReleaseRepository(db.Pool),
trackReleases: database.NewTrackReleaseRepository(db.Pool),
analyzer: analysis.NewReleaseAnalyzer(db),
workflowRuns: database.NewWorkflowRunRepository(db.Pool),
albumEvents: database.NewAlbumEventRepository(db.Pool),
shutdownCtx: ctx,
shutdownCancel: cancel,
}
}
func (s *MusicAgregatorService) Close() {
if s.shutdownCancel != nil {
s.shutdownCancel()
}
if closer, ok := s.magnetResolver.(interface{ Close() }); ok {
closer.Close()
}
}
func (s *MusicAgregatorService) RecoverWorkflows(ctx context.Context) {
stale, err := s.workflowRuns.GetRunning(ctx)
if err != nil {
log.Error().Err(err).Msg("failed to query stale workflow runs for recovery")
return
}
if len(stale) == 0 {
return
}
for _, run := range stale {
downloads, err := s.downloads.GetByAlbumID(ctx, run.AlbumID)
if err != nil {
log.Error().Err(err).Str("workflow_run_id", run.ID).Msg("failed to query downloads for recovery")
s.workflowRuns.SetFailed(ctx, run.ID, "recovery: failed to query downloads")
continue
}
hasActive := false
for _, d := range downloads {
if d.State == "downloading" || d.State == "completed" || d.State == "seeding" {
hasActive = true
break
}
}
if hasActive {
s.workflowRuns.SetCompleted(ctx, run.ID)
log.Info().Str("workflow_run_id", run.ID).Str("album_id", run.AlbumID).Msg("recovered stale workflow as completed")
} else {
s.workflowRuns.SetFailed(ctx, run.ID, "server restarted during workflow")
log.Warn().Str("workflow_run_id", run.ID).Str("album_id", run.AlbumID).Msg("recovered stale workflow as failed")
}
}
}
func (service *MusicAgregatorService) GetArtists(ctx context.Context, _ *pb.GetArtistsRequest) (*pb.GetArtistsResponse, error) {
dbArtists, err := service.artists.GetAll(ctx)
if err != nil {