public async Task Execute(IJobExecutionContext context) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); logger.LogInformation("Updating Jenkins jobs."); var jenkinsChannel = client.Guilds.SelectMany(g => g.Channels).FirstOrDefault(c => c.Name == "jenkins") as SocketTextChannel; if (jenkinsChannel == null) { logger.LogWarning("Cannot find Jenkins channel."); } bool lockTaken = await semaphoreSlim.WaitAsync(0); if (!lockTaken) { logger.LogInformation("Update job already in progress, aborting update."); return; } try { var state = await stateStore.FetchState(); var jobs = await jenkinsRestClient.FetchCachedWorkflowJobs(); var updatedJobTasks = jobs.Select(j => jenkinsRestClient.FetchFullObject(j, false)).ToArray(); await Task.WhenAll(updatedJobTasks); var updatedJobs = updatedJobTasks.Select(t => t.Result).Cast <WorkflowJob>().ToArray(); logger.LogInformation($"{updatedJobs.Length} jobs found:\n{string.Join(",\n", updatedJobs.Select(r => r.Url).ToArray())}"); var buildLinks = updatedJobs.SelectMany(j => j.builds).ToArray(); var runTasks = buildLinks.Select(bl => jenkinsRestClient.FetchFullObject(bl)).ToArray(); await Task.WhenAll(runTasks); var runs = runTasks.Select(t => t.Result).Select(r => r as WorkflowRun).Where(r => r != null).ToArray(); var newRuns = runs.Where(r => r.Timestamp + r.Duration > state.LastUpdateDateTime && r.Result != null) .ToArray(); logger.LogInformation($"{newRuns.Length} new runs found:\n{string.Join(",\n", newRuns.Select(r => r.url).ToArray())}"); // getting timezones is not platform agnostic, so try both ways TimeZoneInfo timezoneInfo = TimeZoneInfo.Utc; var zones = TimeZoneInfo.GetSystemTimeZones(); timezoneInfo = zones.FirstOrDefault(tz => tz.Id == "Europe/London") ?? zones.FirstOrDefault(tz => tz.Id == "GMT Standard Time") ?? TimeZoneInfo.Utc; logger.LogInformation($"Using timezone {timezoneInfo.Id}"); foreach (var newRun in newRuns) { string passEmoji = $"[{newRun.Result}]"; if (newRun.Result == "SUCCESS") { passEmoji = ":white_check_mark:"; } else if (newRun.Result == "FAILURE") { passEmoji = ":octagonal_sign:"; } else if (newRun.Result == "ABORTED") { passEmoji = ":no_pedestrians:"; } else if (newRun.Result == "UNSTABLE") { passEmoji = ":warning:"; } string durationStr = $"Job ran for {(newRun.Duration.Hours > 0 ? $"{newRun.Duration.Hours} hours, " : "")}" + $"{(newRun.Duration.Minutes > 0 ? $"{newRun.Duration.Minutes} minutes, " : "")}" + $"{(newRun.Duration.Seconds > 0 ? $"{newRun.Duration.Seconds} seconds." : "")}"; var localisedDateTime = TimeZoneInfo.ConvertTime(newRun.Timestamp.UtcDateTime, TimeZoneInfo.Utc, timezoneInfo); string timestampStr = localisedDateTime.ToLongTimeString(); var embed = new EmbedBuilder { // Embed property can be set within object initializer Title = newRun.fullDisplayName, Url = newRun.url }; await jenkinsChannel?.SendMessageAsync($"{passEmoji} [{timestampStr}] {newRun.fullDisplayName}. {durationStr}", false, embed.Build()); } state.LastUpdateDateTime = DateTimeOffset.Now; await stateStore.SaveState(state); } catch (TaskCanceledException ex) { logger.LogError(ex, "Task cancelled exception getting Jenkins job status."); var innerExMessage = PrintInnerExceptions(ex.InnerException); await jenkinsChannel?.SendMessageAsync($":loudspeaker: Task cancelled exception. Error getting Jenkins job status:\n{innerExMessage}"); } catch (OperationCanceledException ex) { logger.LogError(ex, "Operation cancelled exception getting Jenkins job status."); var innerExMessage = PrintInnerExceptions(ex.InnerException); await jenkinsChannel?.SendMessageAsync($":loudspeaker: Operation cancelled exception. Error getting Jenkins job status:\n{innerExMessage}"); } catch (Exception ex) { logger.LogError(ex, "Error getting Jenkins job status."); await jenkinsChannel?.SendMessageAsync($":loudspeaker: Error getting Jenkins job status: {ex.GetType().Name} - {ex.Message}"); } finally { stopwatch.Stop(); if (lockTaken) { semaphoreSlim.Release(); } logger.LogInformation($"Updating Jenkins jobs complete ({stopwatch.Elapsed.TotalSeconds} seconds)."); } }