protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // Get off the calling thread immediately
            await Task.Yield();

            try
            {
                _logger.LogInformation(new EventId(0, "StartingScanner"), "Starting pipeline scanner loop. Interval: {ScanInterval}.", _options.CurrentValue.ScanInterval);

                _buildClient = await _connection.GetClientAsync <BuildHttpClient>(stoppingToken);

                while (!stoppingToken.IsCancellationRequested)
                {
                    var options = _options.CurrentValue;
                    using (var scope = _scopeFactory.CreateScope())
                    {
                        var context = new TestResultsScrapeContext(scope.ServiceProvider.GetRequiredService <TestResultsDbContext>(), _buildClient, options, _loggerFactory.CreateLogger <TestResultsScrapeContext>());
                        _logger.LogInformation(new EventId(0, "PrimingCaches"), "Priming caches...");
                        await context.PrimeCachesAsync(stoppingToken);

                        _logger.LogInformation(new EventId(0, "PrimedCaches"), "Primed caches.");

                        _logger.LogInformation(new EventId(0, "RunningScanLoop"), "Running scan loop.");
                        foreach (var config in context.Options.Pipelines)
                        {
                            using (_logger.BeginScope("Pipeline: {PipelineProject}/{PipelineName}", config.Project, config.Name))
                            {
                                _logger.LogInformation(new EventId(0, "ProcessingPipeline"), "Processing pipeline {PipelineProject}/{PipelineName}...", config.Project, config.Name);
                                await ProcessPipelineAsync(context, config, stoppingToken);

                                _logger.LogInformation(new EventId(0, "ProcessedPipeline"), "Processed pipeline {PipelineProject}/{PipelineName}.", config.Project, config.Name);
                            }
                        }
                    }

                    _logger.LogInformation("Sleeping for {ScanInterval}...", options.ScanInterval);
                    await Task.Delay(options.ScanInterval);
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation(new EventId(0, "CancellingSync"), "Cancelling pipeline sync.");
            }

            _logger.LogInformation(new EventId(0, "Stopped"), "Service Stopped.");
        }
        private async Task ProcessPipelineAsync(TestResultsScrapeContext context, PipelineConfig config, CancellationToken stoppingToken)
        {
            try
            {
                var pipelineContext = await context.CreatePipelineSyncContextAsync(config, stoppingToken);

                foreach (var branch in config.Branches)
                {
                    using (_logger.BeginScope("Branch: {SourceBranch}", branch))
                    {
                        _logger.LogInformation(new EventId(0, "ProcessingBranch"), "Processing builds in branch {BranchName}...", branch);
                        await ProcessBranchAsync(pipelineContext, config, branch, stoppingToken);

                        _logger.LogInformation(new EventId(0, "ProcessedBranch"), "Processed builds in branch {BranchName}.", branch);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogDebug(new EventId(0, "CancellingPipelineProcessing"), "Cancelling processing of pipeline: {PipelineProject}/{PipelineName}.", config.Project, config.Name);
                throw;
            }
        }
 public PipelineScrapeContext(TestResultsScrapeContext resultsContext, Pipeline dbPipeline, IEnumerable <Regex> artifactRegexes)
 {
     ResultsContext  = resultsContext;
     DbPipeline      = dbPipeline;
     ArtifactRegexes = artifactRegexes;
 }