Utopia-Canteen-System/App.xaml.cs

210 lines
7.7 KiB
C#

using System.Net.Http;
using System.Threading;
using System.Windows;
using Microsoft.EntityFrameworkCore;
using UtopiaCanteenSystem.Api;
using UtopiaCanteenSystem.Data;
using UtopiaCanteenSystem.Models;
using UtopiaCanteenSystem.Services;
using UtopiaCanteenSystem.ViewModels;
namespace UtopiaCanteenSystem;
/// <summary>
/// Application entry point. Initializes database, builds service graph, optional Kestrel API (server), HTTP client scans (client).
/// </summary>
public partial class App : Application
{
private static Mutex _mutex = null!;
private System.Timers.Timer? _syncTimer;
private System.Timers.Timer? _offlineCacheSyncTimer;
private int _isSyncRunning;
private int _isOfflineCacheSyncRunning;
private CancellationTokenSource? _apiHostCts;
private Task? _apiHostTask;
protected override void OnStartup(StartupEventArgs e)
{
bool isNewInstance;
_mutex = new Mutex(true, "UtopiaCanteenSystemMutex", out isNewInstance);
if (!isNewInstance)
{
MessageBox.Show("Another instance of the application is already running.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
Current.Shutdown();
return;
}
base.OnStartup(e);
var dbFactory = new DbContextFactory();
using (var db = dbFactory.CreateDbContext())
db.EnsureDatabaseCreated();
var configService = new ConfigService();
var isServer = configService.GetAppMode() != AppMode.Client;
var employeeRfidTagSyncService = new EmployeeRfidTagSyncService(dbFactory, configService);
var mealMenuCacheSyncService = new MealMenuCacheSyncService(dbFactory, configService);
var offlineCacheSyncService = new OfflineCacheSyncService(employeeRfidTagSyncService, mealMenuCacheSyncService);
var employeeLookupService = new EmployeeLookupService(dbFactory, configService);
var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };
var employeePhotoService = new EmployeePhotoService(configService, httpClient);
var menuLookupService = new MenuLookupService(dbFactory);
var mealScheduleService = new ProductionMealScheduleService(configService);
var mealSessionResolver = new DbMealSessionResolver(dbFactory);
var syncService = new SyncService(dbFactory, configService);
RfidService? serverRfid = null;
IRfidService rfidService;
if (isServer)
{
serverRfid = new RfidService(dbFactory, configService, employeeLookupService, mealSessionResolver, menuLookupService, syncService);
rfidService = serverRfid;
}
else
{
rfidService = new ClientRfidService(httpClient, configService);
}
var adminAuditService = new AdminAuditService(dbFactory);
var session = new AppSession();
var authenticationUrl = "https://portal.utopiaindustries.pk/uind/rest/auth/user/";
var authService = new AuthService(authenticationUrl);
NavigationService navigationService = null!;
navigationService = new NavigationService(
session,
() => new AdminLoginViewModel(authService, session, navigationService, configService, adminAuditService, employeeLookupService),
() => new ScannerDashboardViewModel(rfidService, navigationService, session, configService, menuLookupService, employeePhotoService),
() => new MainDashboardViewModel(navigationService, rfidService, configService, session),
() => new AdminSettingsAuthViewModel(authService, session, navigationService, configService, employeeLookupService),
() => new SettingsViewModel(configService, navigationService, adminAuditService, syncService, offlineCacheSyncService),
() => new MealSchedulesViewModel(mealScheduleService, navigationService, configService));
var mainViewModel = new MainViewModel(navigationService);
var mainWindow = new MainWindow
{
DataContext = mainViewModel
};
mainWindow.WindowState = WindowState.Maximized;
mainWindow.Show();
if (isServer && serverRfid != null)
{
_apiHostCts = new CancellationTokenSource();
var listenUrls = configService.GetLocalServerListenUrls();
var rfid = serverRfid;
var cts = _apiHostCts;
_apiHostTask = Task.Run(async () =>
{
try
{
await CanteenLocalApiHost.RunAsync(rfid, listenUrls, cts!.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Shutdown
}
catch (Exception ex)
{
Logger.Log(ex, "App.CanteenLocalApiHost");
}
}, CancellationToken.None);
}
if (isServer)
{
_ = RunOfflineCacheSyncAsync(offlineCacheSyncService);
if (!string.IsNullOrWhiteSpace(configService.GetHrmsLookupConnectionString()))
{
_offlineCacheSyncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(15).TotalMilliseconds)
{
AutoReset = true
};
_offlineCacheSyncTimer.Elapsed += async (_, _) =>
{
if (Interlocked.Exchange(ref _isOfflineCacheSyncRunning, 1) == 1)
return;
try
{
await RunOfflineCacheSyncAsync(offlineCacheSyncService).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Log(ex, "App.OfflineCacheSyncTimer");
}
finally
{
Interlocked.Exchange(ref _isOfflineCacheSyncRunning, 0);
}
};
_offlineCacheSyncTimer.Start();
}
if (configService.GetSyncServiceEnabled())
{
_syncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(1).TotalMilliseconds)
{
AutoReset = true
};
_syncTimer.Elapsed += async (_, _) =>
{
if (Interlocked.Exchange(ref _isSyncRunning, 1) == 1)
return;
try
{
await syncService.SyncNowAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Log(ex, "App.ProductionSyncTimer");
}
finally
{
Interlocked.Exchange(ref _isSyncRunning, 0);
}
};
_syncTimer.Start();
}
}
}
protected override void OnExit(ExitEventArgs e)
{
try
{
_apiHostCts?.Cancel();
_apiHostTask?.Wait(TimeSpan.FromSeconds(5));
}
catch
{
// Best-effort shutdown
}
_mutex.ReleaseMutex();
_syncTimer?.Stop();
_syncTimer?.Dispose();
_offlineCacheSyncTimer?.Stop();
_offlineCacheSyncTimer?.Dispose();
_apiHostCts?.Dispose();
base.OnExit(e);
}
private static async Task RunOfflineCacheSyncAsync(IOfflineCacheSyncService offlineCacheSyncService)
{
try
{
await offlineCacheSyncService.SyncEmployeeAndMenuCacheAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Log(ex, "App.RunOfflineCacheSyncAsync");
}
}
}