Release v1.0.0 #16
.envDbInitializer.csDependencyInjection.csnuget.config
.gitea/workflows
.gitignoreApiDto
ApiDto.csproj
Backend.slnDockerfileCommon
Requests
Responses
Endpoint
Backend.httpISaveSettings.cs
README.mdCommon
Attributes
BadRequestResponseAttribute.csCacheMaxAgeAttribute.csLocalhostAttribute.csMaintenanceModeIgnoreAttribute.csNotFoundResponseAttribute.csSwaggerDefaultAttribute.csTokenAuthenticationAttribute.cs
Exceptions
Interfaces
Services
Configuration
Core
BackgroundTasks
Middleware
CacheMaxAgeMiddleware.csCookieAuthorizationMiddleware.csCustomExceptionHandlerMiddleware.csJwtRevocationMiddleware.csMaintenanceModeMiddleware.cs
Startup
Model
SwaggerOptions
Validation
Controllers
BaseController.cs
Endpoint.csprojProgram.csConfiguration
V1
AuthController.csCampusController.csDisciplineController.csFacultyController.csGroupController.csLectureHallController.csProfessorController.csScheduleController.cs
WeatherForecastController.csSync
WeatherForecast.cswwwroot
css
swagger
Security
SqlData
Application
Application.csprojDependencyInjection.cs
Common
Cqrs
Campus
Queries
Discipline
Queries
Faculty
Queries
Group
Queries
LectureHall
Queries
Professor
Queries
GetProfessorDetails
GetProfessorDetailsBySearch
GetProfessorList
Schedule
Interfaces
Domain
Domain.csproj
Schedule
Migrations
MysqlMigrations
Migrations
20240601023106_InitialMigration.Designer.cs20240601023106_InitialMigration.csUberDbContextModelSnapshot.cs
MysqlMigrations.csprojPsqlMigrations
Migrations
20240601021702_InitialMigration.Designer.cs20240601021702_InitialMigration.csUberDbContextModelSnapshot.cs
PsqlMigrations.csprojSqliteMigrations
Persistence
Common
BaseDbContext.csConfigurationResolver.csDatabaseProvider.csDbContextFactory.csModelBuilderExtensions.cs
Contexts
Schedule
EntityTypeConfigurations
Persistence.csprojUberDbContext.cs
15
Endpoint/Common/Services/ScheduleSyncManager.cs
Normal file
15
Endpoint/Common/Services/ScheduleSyncManager.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Mirea.Api.Endpoint.Common.Services;
|
||||
|
||||
public static class ScheduleSyncManager
|
||||
{
|
||||
public static event Action? OnUpdateIntervalRequested;
|
||||
public static event Action? OnForceSyncRequested;
|
||||
|
||||
public static void RequestIntervalUpdate() =>
|
||||
OnUpdateIntervalRequested?.Invoke();
|
||||
|
||||
public static void RequestForceSync() =>
|
||||
OnForceSyncRequested?.Invoke();
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
using Cronos;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Mirea.Api.Endpoint.Common.Services;
|
||||
using Mirea.Api.Endpoint.Configuration.Model;
|
||||
using Mirea.Api.Endpoint.Sync;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IServiceProvider = System.IServiceProvider;
|
||||
|
||||
namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
|
||||
|
||||
public class ScheduleSyncService : IHostedService, IDisposable
|
||||
{
|
||||
private Timer? _timer;
|
||||
private readonly IOptionsMonitor<GeneralConfig> _generalConfigMonitor;
|
||||
private readonly ILogger<ScheduleSyncService> _logger;
|
||||
private CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public ScheduleSyncService(IOptionsMonitor<GeneralConfig> generalConfigMonitor, ILogger<ScheduleSyncService> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
_generalConfigMonitor = generalConfigMonitor;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested;
|
||||
ScheduleSyncManager.OnUpdateIntervalRequested += OnUpdateIntervalRequested;
|
||||
}
|
||||
|
||||
private void OnForceSyncRequested()
|
||||
{
|
||||
StopAsync(default).ContinueWith(_ =>
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
ExecuteTask(null);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUpdateIntervalRequested()
|
||||
{
|
||||
StopAsync(default).ContinueWith(_ =>
|
||||
{
|
||||
StartAsync(default);
|
||||
});
|
||||
}
|
||||
|
||||
private void ScheduleNextRun()
|
||||
{
|
||||
var cronExpression = _generalConfigMonitor.CurrentValue.ScheduleSettings?.CronUpdateSchedule;
|
||||
if (string.IsNullOrEmpty(cronExpression))
|
||||
{
|
||||
_logger.LogWarning("Cron expression is not set. The scheduled task will not run.");
|
||||
return;
|
||||
}
|
||||
|
||||
var nextRunTime = CronExpression.Parse(cronExpression).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local);
|
||||
|
||||
if (!nextRunTime.HasValue)
|
||||
{
|
||||
_logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", TimeZoneInfo.Local.DisplayName);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Next task run in {Time}", nextRunTime.Value.ToString("G"));
|
||||
|
||||
var delay = (nextRunTime.Value - DateTimeOffset.Now).TotalMilliseconds;
|
||||
|
||||
// The chance is small, but it's better to check
|
||||
if (delay <= 0)
|
||||
delay = 1;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_timer = new Timer(ExecuteTask, null, (int)delay, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private async void ExecuteTask(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var syncService = ActivatorUtilities.GetServiceOrCreateInstance<ScheduleSynchronizer>(scope.ServiceProvider);
|
||||
await syncService.StartSync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred during schedule synchronization.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ScheduleNextRun();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ScheduleNextRun();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_timer?.Change(Timeout.Infinite, 0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync(default).GetAwaiter().GetResult();
|
||||
_timer?.Dispose();
|
||||
ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested;
|
||||
ScheduleSyncManager.OnUpdateIntervalRequested -= OnUpdateIntervalRequested;
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Company>Winsomnia</Company>
|
||||
<Version>1.0-rc3</Version>
|
||||
<AssemblyVersion>1.0.2.3</AssemblyVersion>
|
||||
<FileVersion>1.0.2.3</FileVersion>
|
||||
<Version>1.0-rc4</Version>
|
||||
<AssemblyVersion>1.0.2.4</AssemblyVersion>
|
||||
<FileVersion>1.0.2.4</FileVersion>
|
||||
<AssemblyName>Mirea.Api.Endpoint</AssemblyName>
|
||||
<RootNamespace>$(AssemblyName)</RootNamespace>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
@ -10,6 +10,7 @@ using Mirea.Api.DataAccess.Persistence;
|
||||
using Mirea.Api.DataAccess.Persistence.Common;
|
||||
using Mirea.Api.Endpoint.Common.Interfaces;
|
||||
using Mirea.Api.Endpoint.Common.Services;
|
||||
using Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
|
||||
using Mirea.Api.Endpoint.Configuration.Core.Middleware;
|
||||
using Mirea.Api.Endpoint.Configuration.Core.Startup;
|
||||
using Mirea.Api.Endpoint.Configuration.Model;
|
||||
@ -63,6 +64,8 @@ public class Program
|
||||
builder.Services.AddSingleton<IMaintenanceModeService, MaintenanceModeService>();
|
||||
builder.Services.AddSingleton<ISetupToken, SetupTokenService>();
|
||||
|
||||
builder.Services.AddHostedService<ScheduleSyncService>();
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddCustomRedis(builder.Configuration, healthCheckBuilder);
|
||||
|
||||
|
Reference in New Issue
Block a user