Files
ECMJobRunner/ECMJobRunner.WebCron/ProfileWorker/ProfileWork.cs
TekH bc881bf70f Add XML documentation for improved code clarity
Added detailed XML documentation across multiple files to enhance
code maintainability and readability. Key updates include:

- Documented `AllowAllDashboardAuthorizationFilter` to clarify
  its development-only usage.
- Added comments to `DtoExtensions` for Hangfire job ID generation
  and DTO-to-command conversion methods.
- Enhanced `HealthCheckHtmlGenerator` with detailed descriptions
  of HTML generation methods and utility functions.
- Documented `DependencyInjection` and `ProfileWorkerOptionsValidator`
  to explain service registration and configuration validation.
- Updated `ProfileCache` with comments on thread-safe operations.
- Added documentation to `ProfileWork` for profile synchronization
  logic and execution flow.
- Enhanced `ProfileWorker` with health check logic and background
  service execution details.
- Documented `ProfileWorkerOptions` configuration properties.

These changes aim to improve developer understanding and ensure
best practices are followed in production environments.
2026-07-13 13:37:12 +02:00

83 lines
3.5 KiB
C#

using ECMJobRunner.Application.DEXJob.Queries;
using ECMJobRunner.WebCron.Extensions;
using Hangfire;
using MediatR;
namespace ECMJobRunner.WebCron.ProfileWorker;
/// <summary>
/// Handles the core business logic for synchronizing profiles between the database and Hangfire.
/// Responsible for adding, updating, and removing recurring jobs based on profile configuration.
/// </summary>
/// <param name="Logger">Logger for diagnostic output.</param>
/// <param name="JobManager">Hangfire recurring job manager.</param>
/// <param name="Mediator">MediatR mediator for executing commands and queries.</param>
/// <param name="ProfileCache">Thread-safe cache for tracking profile state.</param>
public class ProfileWork(ILogger<ProfileWork> Logger, IRecurringJobManager JobManager, IMediator Mediator, ProfileCache ProfileCache)
{
/// <summary>
/// Synchronizes active profiles from the database with Hangfire recurring jobs.
/// </summary>
/// <param name="stoppingToken">Cancellation token for graceful shutdown.</param>
/// <remarks>
/// Execution flow:
/// <list type="number">
/// <item><description>Fetches active profiles from database via MediatR query</description></item>
/// <item><description>Removes jobs from Hangfire that no longer exist in the database</description></item>
/// <item><description>Adds or updates jobs with changed schedules</description></item>
/// <item><description>Updates local cache to reflect current state</description></item>
/// </list>
/// Uses HashSet for O(1) job existence checks to optimize performance.
/// </remarks>
/// <returns>A task representing the asynchronous synchronization operation.</returns>
public async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Fetch active profiles from database
var profiles = await Mediator.Send(new GetProfileQuery()
{
Active = true,
IncludeSqlJobs = true
}, stoppingToken);
// Create HashSet for O(1) lookup performance
var profileJobIds = profiles.Select(p => p.JobId()).ToHashSet();
// Remove jobs from Hangfire that no longer exist in the database
foreach (var cachedJobId in ProfileCache.GetAllJobIds())
{
if (!profileJobIds.Contains(cachedJobId))
{
// Remove job from Hangfire if it no longer exists in the database
JobManager.RemoveIfExists(cachedJobId);
ProfileCache.TryRemove(cachedJobId, out _);
Logger.LogInformation("Job {JobId} removed", cachedJobId);
}
}
// Add or update jobs in Hangfire based on the database profiles
foreach (var profile in profiles)
{
if (ProfileCache.TryGetValue(profile.JobId(), out var currentProfile)
&& currentProfile!.Schedule == profile.Schedule)
continue;
// Add or update recurring job using MediatR command
JobManager.AddOrUpdate<IMediator>(
profile.JobId(),
mediator => mediator.Send(profile.ToJob(), stoppingToken),
profile.Schedule,
new RecurringJobOptions
{
TimeZone = TimeZoneInfo.Local
}
);
// Store/update in local cache
ProfileCache.AddOrUpdate(profile.JobId(), profile);
Logger.LogInformation("Job {JobId} registered with schedule: {Schedule}",
profile.JobId(), profile.Schedule);
}
}
}