217 lines
8.6 KiB
C#
217 lines
8.6 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>
|
|
/// WPF shell: Server = backend + SQLite + API host; Client = scanner frontend calling backend HTTP API only.
|
|
/// </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 configService = new ConfigService();
|
|
var isServer = configService.GetAppMode() == AppMode.Server;
|
|
|
|
var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5) };
|
|
var backendApiClient = new CanteenBackendApiClient(httpClient, configService);
|
|
|
|
var dbFactory = new DbContextFactory();
|
|
CanteenBackendServices? backendServices = null;
|
|
RfidService? serverRfid = null;
|
|
IEmployeeLookupService employeeLookupService;
|
|
IMenuLookupService menuLookupService;
|
|
IRfidService scannerRfidService;
|
|
ISyncService? syncService = null;
|
|
ProductionMealScheduleService? mealScheduleService = null;
|
|
|
|
using (var db = dbFactory.CreateDbContext())
|
|
db.EnsureDatabaseCreated();
|
|
|
|
if (isServer)
|
|
{
|
|
|
|
var employeeRfidTagSync = new EmployeeRfidTagSyncService(dbFactory, configService);
|
|
var mealMenuCacheSync = new MealMenuCacheSyncService(dbFactory, configService);
|
|
var offlineCacheSync = new OfflineCacheSyncService(employeeRfidTagSync, mealMenuCacheSync);
|
|
employeeLookupService = new EmployeeLookupService(dbFactory, configService);
|
|
menuLookupService = new MenuLookupService(dbFactory);
|
|
mealScheduleService = new ProductionMealScheduleService(configService);
|
|
var mealSessionResolver = new DbMealSessionResolver(dbFactory);
|
|
syncService = new SyncService(dbFactory, configService);
|
|
serverRfid = new RfidService(dbFactory, configService, employeeLookupService, mealSessionResolver, menuLookupService, syncService);
|
|
backendServices = new CanteenBackendServices(serverRfid, offlineCacheSync, syncService, configService);
|
|
scannerRfidService = serverRfid;
|
|
}
|
|
else
|
|
{
|
|
employeeLookupService = new EmployeeLookupService(dbFactory, configService);
|
|
menuLookupService = new EmptyMenuLookupService();
|
|
scannerRfidService = backendApiClient;
|
|
}
|
|
|
|
var employeePhotoService = new EmployeePhotoService(configService, httpClient);
|
|
var adminAuditService = new AdminAuditService(dbFactory);
|
|
var session = new AppSession();
|
|
var authService = new AuthService("https://portal.utopiaindustries.pk/uind/rest/auth/user/");
|
|
|
|
NavigationService navigationService = null!;
|
|
navigationService = new NavigationService(
|
|
session,
|
|
() => new AdminLoginViewModel(authService, session, navigationService, configService, adminAuditService, employeeLookupService, backendApiClient),
|
|
() => new ScannerDashboardViewModel(scannerRfidService, navigationService, session, configService, menuLookupService, employeePhotoService),
|
|
() => new MainDashboardViewModel(navigationService, scannerRfidService, configService, session),
|
|
() => new AdminSettingsAuthViewModel(authService, session, navigationService, configService, employeeLookupService, backendApiClient),
|
|
() => new SettingsViewModel(configService, navigationService, adminAuditService, backendApiClient),
|
|
() => new MealSchedulesViewModel(
|
|
mealScheduleService ?? new ProductionMealScheduleService(configService),
|
|
navigationService,
|
|
configService));
|
|
|
|
var mainWindow = new MainWindow { DataContext = new MainViewModel(navigationService) };
|
|
mainWindow.WindowState = WindowState.Maximized;
|
|
mainWindow.Show();
|
|
|
|
if (isServer && backendServices != null)
|
|
{
|
|
_apiHostCts = new CancellationTokenSource();
|
|
var listenUrls = configService.GetLocalServerListenUrls();
|
|
var backend = backendServices;
|
|
var cts = _apiHostCts;
|
|
_apiHostTask = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(500, cts!.Token).ConfigureAwait(false);
|
|
await CanteenBackendHost.RunAsync(backend, listenUrls, cts.Token).ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Shutdown
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "App.CanteenBackendHost");
|
|
}
|
|
}, CancellationToken.None);
|
|
|
|
_ = RunOfflineCacheSyncViaApiAsync(backendApiClient);
|
|
|
|
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 RunOfflineCacheSyncViaApiAsync(backendApiClient).ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "App.OfflineCacheSyncTimer");
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Exchange(ref _isOfflineCacheSyncRunning, 0);
|
|
}
|
|
};
|
|
_offlineCacheSyncTimer.Start();
|
|
}
|
|
|
|
if (configService.GetSyncServiceEnabled() && syncService != null)
|
|
{
|
|
_syncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(1).TotalMilliseconds) { AutoReset = true };
|
|
_syncTimer.Elapsed += async (_, _) =>
|
|
{
|
|
if (Interlocked.Exchange(ref _isSyncRunning, 1) == 1)
|
|
return;
|
|
try
|
|
{
|
|
if (await backendApiClient.HealthCheckAsync().ConfigureAwait(false))
|
|
await backendApiClient.SyncOrdersNowAsync().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
|
|
}
|
|
|
|
_mutex.ReleaseMutex();
|
|
_syncTimer?.Stop();
|
|
_syncTimer?.Dispose();
|
|
_offlineCacheSyncTimer?.Stop();
|
|
_offlineCacheSyncTimer?.Dispose();
|
|
_apiHostCts?.Dispose();
|
|
base.OnExit(e);
|
|
}
|
|
|
|
private static async Task RunOfflineCacheSyncViaApiAsync(ICanteenBackendApiClient api)
|
|
{
|
|
try
|
|
{
|
|
for (var i = 0; i < 30; i++)
|
|
{
|
|
if (await api.HealthCheckAsync().ConfigureAwait(false))
|
|
{
|
|
await api.SyncCacheNowAsync().ConfigureAwait(false);
|
|
return;
|
|
}
|
|
await Task.Delay(1000).ConfigureAwait(false);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(ex, "App.RunOfflineCacheSyncViaApiAsync");
|
|
}
|
|
}
|
|
}
|