package fetcher import ( "context" "errors" "fmt" "log/slog" "sync" "time" "git.avsdev.uk/Go/job-search/internal/config" "git.avsdev.uk/Go/job-search/internal/util" ) var ErrNoNewReleases = fmt.Errorf("%w: no new releases", util.ErrWorkerNoWork) // Run starts a worker for each enabled source and blocks until every worker // has stopped. Workers stop when ctx is cancelled, or when any worker stops // with an error, which starts a shutdown of all of them. The errors are // logged as they happen and returned joined; nil means a clean shutdown. func Run( ctx context.Context, conf *config.JobSearchConfig, since time.Time, ) error { ctx, cancel := context.WithCancel(ctx) defer cancel() var ( wg sync.WaitGroup mu sync.Mutex errs []error ) start := func( name string, run func(context.Context, *config.JobSearchConfig, time.Time) error, ) { wg.Add(1) go func() { defer wg.Done() if err := run(ctx, conf, since); err != nil { slog.Error("source stopped, shutting down", "source", name, "err", err) mu.Lock() errs = append(errs, fmt.Errorf("%s: %w", name, err)) mu.Unlock() cancel() } }() } if conf.Sources.FindATender.Enabled { start("FindATender", runFindAOpportunity) } if conf.Sources.FTSMock.Enabled { start("FTSMock", runFTSMock) } if conf.Sources.Sleeper.Enabled { start("Sleeper", runSleeper) } wg.Wait() return errors.Join(errs...) }