Skip to content

Commit fe04da2

Browse files
committed
Added task runners for weekly schedules + began work on calender events
1 parent fb2d0c9 commit fe04da2

File tree

5 files changed

+439
-0
lines changed

5 files changed

+439
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+

2+
/// <summary>
3+
/// Contract for a scheduled background task that includes its execution logic and its specific schedule.
4+
/// </summary>
5+
public interface ICronScheduledTask
6+
{
7+
/// <summary>
8+
/// A unique name for logging and identification.
9+
/// </summary>
10+
string Name { get; }
11+
12+
/// <summary>
13+
/// The schedule definition for the task.
14+
/// Format: [DayOfWeek|Daily]@[HH:MM]
15+
/// Examples: "Daily@08:30", "Monday@08:30", "Sunday@00:00"
16+
/// </summary>
17+
string Schedule { get; }
18+
19+
/// <summary>
20+
/// Executes the scheduled job logic.
21+
/// </summary>
22+
Task ExecuteAsync();
23+
}
24+
25+
26+
/// <summary>
27+
/// A daily scheduled task for routine maintenance (e.g., database cleanup, log rotation).
28+
/// Runs every day at 04:00 AM UTC.
29+
/// </summary>
30+
public class DailyMaintenanceTask : ICronScheduledTask
31+
{
32+
private readonly ILogger<DailyMaintenanceTask> _logger;
33+
34+
public DailyMaintenanceTask(ILogger<DailyMaintenanceTask> logger)
35+
{
36+
_logger = logger;
37+
}
38+
39+
// --- ICronScheduledTask Implementation ---
40+
public string Name => "Daily Maintenance & Cleanup";
41+
public string Schedule => "Daily@04:00"; // Run every day at 4:00 AM UTC
42+
// ----------------------------------------
43+
44+
public async Task ExecuteAsync()
45+
{
46+
_logger.LogInformation("Executing Daily Maintenance Task: Running routine cleanup.");
47+
48+
// --- Actual Scheduled Logic ---
49+
await Task.Delay(100); // Simulate asynchronous database cleanup or file deletion
50+
// Note: For a real cleanup task, you would inject and use a repository here.
51+
// ------------------------------
52+
53+
_logger.LogInformation("Daily Maintenance Task completed successfully.");
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Scheduled task for generating a report every Monday morning.
59+
/// </summary>
60+
public class BiWeeklyReportTaskMonday : ICronScheduledTask
61+
{
62+
private readonly ILogger<BiWeeklyReportTaskMonday> _logger;
63+
64+
public BiWeeklyReportTaskMonday(ILogger<BiWeeklyReportTaskMonday> logger)
65+
{
66+
_logger = logger;
67+
}
68+
69+
public string Name => "Bi-Weekly Report (Monday)";
70+
public string Schedule => "Monday@08:30"; // Run every Monday at 8:30 AM UTC
71+
72+
public async Task ExecuteAsync()
73+
{
74+
_logger.LogInformation("Executing Monday Report Task: Compiling weekly progress report.");
75+
await Task.Delay(100); // Simulate report generation
76+
_logger.LogInformation("Monday Report Task completed successfully.");
77+
}
78+
}
79+
80+
/// <summary>
81+
/// Scheduled task for generating a report every Friday morning.
82+
/// </summary>
83+
public class BiWeeklyReportTaskFriday : ICronScheduledTask
84+
{
85+
private readonly ILogger<BiWeeklyReportTaskFriday> _logger;
86+
87+
public BiWeeklyReportTaskFriday(ILogger<BiWeeklyReportTaskFriday> logger)
88+
{
89+
_logger = logger;
90+
}
91+
92+
public string Name => "Bi-Weekly Report (Friday)";
93+
public string Schedule => "Friday@08:30"; // Run every Friday at 8:30 AM UTC
94+
95+
public async Task ExecuteAsync()
96+
{
97+
_logger.LogInformation("Executing Friday Report Task: Compiling end-of-week summary report.");
98+
await Task.Delay(100); // Simulate report generation
99+
_logger.LogInformation("Friday Report Task completed successfully.");
100+
}
101+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+

2+
/// <summary>
3+
/// The centralized background service that monitors the clock and executes
4+
/// all registered ICronScheduledTask instances based on their Schedule property.
5+
/// </summary>
6+
public class CronTaskRunnerService : BackgroundService
7+
{
8+
private readonly IServiceProvider _serviceProvider;
9+
private readonly ILogger<CronTaskRunnerService> _logger;
10+
11+
// The runner checks the schedule every 60 seconds (or less if needed).
12+
private static readonly TimeSpan CheckInterval = TimeSpan.FromSeconds(60);
13+
14+
public CronTaskRunnerService(
15+
ILogger<CronTaskRunnerService> logger,
16+
IServiceProvider serviceProvider)
17+
{
18+
_logger = logger;
19+
_serviceProvider = serviceProvider;
20+
}
21+
22+
/// <summary>
23+
/// Logic to check if a task's schedule matches the current minute.
24+
/// Uses a custom format: [DayOfWeek|Daily]@[HH:MM]
25+
/// </summary>
26+
private static bool IsScheduleDue(string schedule, DateTime now)
27+
{
28+
// Example: "Sunday@00:00"
29+
if (string.IsNullOrWhiteSpace(schedule) || !schedule.Contains('@')) return false;
30+
31+
var parts = schedule.Split('@');
32+
var dayPart = parts[0].Trim();
33+
var timePart = parts[1].Trim();
34+
35+
// 1. Check Time (HH:MM)
36+
// Check if the current hour and minute match the scheduled time
37+
if (!TimeSpan.TryParseExact(timePart, "hh\\:mm", null, out TimeSpan scheduledTime))
38+
{
39+
return false;
40+
}
41+
42+
// Only fire if the current UTC hour and minute match the scheduled time
43+
if (now.Hour != scheduledTime.Hours || now.Minute != scheduledTime.Minutes)
44+
{
45+
return false;
46+
}
47+
48+
// 2. Check Day (Daily or Specific DayOfWeek)
49+
if (dayPart.Equals("Daily", StringComparison.OrdinalIgnoreCase))
50+
{
51+
return true; // Scheduled to run every day at this time
52+
}
53+
54+
// Check if the current DayOfWeek matches the scheduled day
55+
if (Enum.TryParse<DayOfWeek>(dayPart, true, out DayOfWeek scheduledDay))
56+
{
57+
return now.DayOfWeek == scheduledDay;
58+
}
59+
60+
return false;
61+
}
62+
63+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
64+
{
65+
_logger.LogInformation("Cron Task Runner Service started. Checking schedule every minute.");
66+
67+
while (!stoppingToken.IsCancellationRequested)
68+
{
69+
// Get the current time in UTC, rounded down to the nearest minute.
70+
var now = DateTime.UtcNow.AddSeconds(-DateTime.UtcNow.Second);
71+
_logger.LogDebug($"Checking schedules for time: {now:yyyy-MM-dd HH:mm} UTC");
72+
73+
try
74+
{
75+
// Must create a scope for each execution cycle
76+
using (var scope = _serviceProvider.CreateScope())
77+
{
78+
// Resolve ALL services registered under the ICronScheduledTask contract.
79+
IEnumerable<ICronScheduledTask> scheduledTasks =
80+
scope.ServiceProvider.GetServices<ICronScheduledTask>();
81+
82+
var tasksToRun = scheduledTasks
83+
.Where(task => IsScheduleDue(task.Schedule, now))
84+
.ToList();
85+
86+
if (tasksToRun.Any())
87+
{
88+
_logger.LogInformation($"Found {tasksToRun.Count} tasks due now. Executing concurrently.");
89+
90+
var executionTasks = tasksToRun
91+
.Select(task => task.ExecuteAsync().ContinueWith(t =>
92+
{
93+
if (t.IsFaulted)
94+
{
95+
_logger.LogError(t.Exception, $"Task '{task.Name}' failed to execute.");
96+
}
97+
}, TaskContinuationOptions.ExecuteSynchronously)) // Ensure logging is safe
98+
.ToList();
99+
100+
await Task.WhenAll(executionTasks);
101+
_logger.LogInformation("Concurrent execution completed for this cycle.");
102+
}
103+
}
104+
}
105+
catch (Exception ex)
106+
{
107+
_logger.LogError(ex, "An unhandled error occurred during the Cron execution cycle.");
108+
}
109+
110+
// Wait for the next interval check.
111+
await Task.Delay(CheckInterval, stoppingToken);
112+
}
113+
114+
_logger.LogInformation("Cron Task Runner Service stopping.");
115+
}
116+
}

0 commit comments

Comments
 (0)