Utopia-Canteen-System/Services/DbMealSessionResolver.cs

142 lines
4.4 KiB
C#

using Microsoft.EntityFrameworkCore;
using UtopiaCanteenSystem.Data;
using UtopiaCanteenSystem.Models;
namespace UtopiaCanteenSystem.Services;
/// <summary>
/// Resolves current meal session from local SQLite <c>meal_schedule_cache</c>.
/// </summary>
public class DbMealSessionResolver : IMealSessionResolver
{
public const string OfflineCacheMissingMessage =
"Meal schedule/menu cache not found. Please sync once while online.";
private readonly IDbContextFactory<AppDbContext> _dbFactory;
private readonly TimeSpan _cacheTtl = TimeSpan.FromSeconds(60);
private readonly Dictionary<string, (List<MealSchedule> Schedules, DateTime ExpiryUtc)> _cache = new(StringComparer.OrdinalIgnoreCase);
private readonly object _cacheLock = new();
public DbMealSessionResolver(IDbContextFactory<AppDbContext> dbFactory)
{
_dbFactory = dbFactory;
}
public bool IsScheduleCacheAvailable()
{
using var db = _dbFactory.CreateDbContext();
return db.MealScheduleCache.Any();
}
public bool IsMenuCacheAvailable()
{
using var db = _dbFactory.CreateDbContext();
return db.MenuItemCache.Any() && db.LunchMenuWeekCache.Any();
}
public bool IsOfflineMealDataAvailable() => IsScheduleCacheAvailable() && IsMenuCacheAvailable();
private static MealSession MapMealNameToSession(string? mealName)
{
if (string.IsNullOrWhiteSpace(mealName))
return MealSession.None;
var name = mealName.Trim();
return name.ToLowerInvariant() switch
{
"breakfast" => MealSession.Breakfast,
"sehri" => MealSession.Breakfast,
"lunch" => MealSession.Lunch,
"iftari" => MealSession.Lunch,
"tea" => MealSession.Tea,
"dinner" => MealSession.Dinner,
_ => MealSession.None
};
}
public ResolvedMealSession? GetCurrentSession(DateTime nowLocal, string siteId)
{
var normalizedSite = SiteIdHelper.NormalizeSiteId(siteId);
var schedules = GetSchedulesForSiteCached(normalizedSite);
var t = nowLocal.TimeOfDay;
foreach (var s in schedules)
{
if (!TryParseTime(s.StartTime, out var start) || !TryParseTime(s.EndTime, out var end))
continue;
if (t >= start && t < end)
{
return new ResolvedMealSession
{
Session = MapMealNameToSession(s.MealName),
MealName = s.MealName ?? string.Empty,
SiteId = normalizedSite,
StartTime = s.StartTime ?? string.Empty,
EndTime = s.EndTime ?? string.Empty
};
}
}
return null;
}
private List<MealSchedule> GetSchedulesForSiteCached(string siteId)
{
lock (_cacheLock)
{
if (_cache.TryGetValue(siteId, out var entry) && DateTime.UtcNow < entry.ExpiryUtc)
return entry.Schedules;
}
var list = LoadSchedulesForSiteFromCache(siteId);
lock (_cacheLock)
{
_cache[siteId] = (list, DateTime.UtcNow.Add(_cacheTtl));
}
return list;
}
private List<MealSchedule> LoadSchedulesForSiteFromCache(string siteId)
{
var siteIdInt = SiteIdHelper.ToInt(siteId);
using var db = _dbFactory.CreateDbContext();
var rows = db.MealScheduleCache
.AsNoTracking()
.Where(x => x.LocationSiteId == siteIdInt)
.OrderBy(x => x.StartTime)
.ToList();
return rows.Select(MapToMealSchedule).ToList();
}
private static MealSchedule MapToMealSchedule(MealScheduleCache row)
{
return new MealSchedule
{
Id = row.HrmsId,
MealName = row.MealName,
LocationSiteId = SiteIdHelper.ToDisplayString(row.LocationSiteId),
StartTime = row.StartTime,
EndTime = row.EndTime,
CreatedAt = row.CreatedAt ?? DateTime.MinValue,
UpdatedAt = row.UpdatedAt ?? DateTime.MinValue,
IsActive = true
};
}
private static bool TryParseTime(string value, out TimeSpan time)
{
time = TimeSpan.Zero;
if (string.IsNullOrWhiteSpace(value))
return false;
return TimeSpan.TryParse(value.Trim(), null, out time)
|| TimeSpan.TryParse(value.Trim().Replace(".", ":"), null, out time);
}
}