Short and single-letter names in application code become full words (tests keep theirs). Wrapped calls, signatures and composite literals put one item per line, and one-line function bodies are expanded. Exported names and groups of constants get doc comments, and comments refer to the renamed identifiers.
109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
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"
|
|
)
|
|
|
|
// ErrNoNewReleases is returned by a source's task when there was nothing new
|
|
// to fetch; it wraps util.ErrWorkerNoWork.
|
|
var ErrNoNewReleases = fmt.Errorf("%w: no new releases", util.ErrWorkerNoWork)
|
|
|
|
// Option configures Run.
|
|
type Option func(*options)
|
|
|
|
// options holds the settings Options change.
|
|
type options struct {
|
|
log *slog.Logger
|
|
}
|
|
|
|
// WithLogger makes Run, its workers and their clients log to logger instead
|
|
// of slog.Default().
|
|
func WithLogger(logger *slog.Logger) Option {
|
|
return func(opts *options) {
|
|
opts.log = logger
|
|
}
|
|
}
|
|
|
|
// logger returns the logger Run logs to: the given one, or slog.Default().
|
|
// It isn't tagged here; each worker adds its source.
|
|
func (opts options) logger() *slog.Logger {
|
|
if opts.log == nil {
|
|
return slog.Default()
|
|
}
|
|
return opts.log
|
|
}
|
|
|
|
// runFunc runs one source's worker until ctx is cancelled, logging to
|
|
// logger.
|
|
type runFunc func(
|
|
ctx context.Context,
|
|
logger *slog.Logger,
|
|
conf *config.JobSearchConfig,
|
|
since time.Time,
|
|
) error
|
|
|
|
// 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. It
|
|
// logs to slog.Default() unless given WithLogger.
|
|
func Run(
|
|
ctx context.Context,
|
|
conf *config.JobSearchConfig,
|
|
since time.Time,
|
|
opts ...Option,
|
|
) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
var settings options
|
|
for _, opt := range opts {
|
|
opt(&settings)
|
|
}
|
|
logger := settings.logger().With("component", "fetcher")
|
|
|
|
var (
|
|
wg sync.WaitGroup
|
|
mu sync.Mutex
|
|
errs []error
|
|
)
|
|
|
|
start := func(
|
|
name string,
|
|
run runFunc,
|
|
) {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := run(ctx, logger, conf, since); err != nil {
|
|
logger.Error("source stopped, shutting down", "source", name, "error", 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...)
|
|
}
|