Files
disco-service-tracker-plugin/Services/DashboardCache.cs
T

116 lines
4.5 KiB
C#

using Disco.Plugins.ServiceTracker.Models;
using System;
using System.IO;
namespace Disco.Plugins.ServiceTracker.Services
{
/// <summary>
/// Static in-memory cache for the dashboard model.
/// Instead of running full EF queries + JSON reads + Google Sheet fetches on every page load,
/// this checks file modification timestamps to detect changes.
/// If nothing changed, returns the cached model instantly (near-zero CPU/memory cost).
/// </summary>
public static class DashboardCache
{
private static DashboardViewModel _cachedModel;
private static DateTime _cacheBuiltAt = DateTime.MinValue;
private static DateTime _ticketsFileTime = DateTime.MinValue;
private static DateTime _externalFileTime = DateTime.MinValue;
private static DateTime _sheetCacheTime = DateTime.MinValue;
private static DateTime _configFileTime = DateTime.MinValue;
private static string _lastSortBy;
private static string _lastFilter;
private static readonly object _lock = new object();
/// <summary>
/// Maximum age of cache in seconds before forced rebuild, even if no files changed.
/// Acts as a safety net for database-only changes (e.g. Disco job opened/closed externally).
/// </summary>
public static int MaxCacheAgeSeconds = 30;
/// <summary>
/// Returns cached model if still valid, or null if rebuild is needed.
/// A rebuild is needed when:
/// - Cache doesn't exist yet
/// - Any data file has been modified since last build
/// - Cache is older than MaxCacheAgeSeconds
/// - Sort/filter parameters changed
/// </summary>
public static DashboardViewModel GetIfValid(string dataDirectory, string sortBy, string filterKey)
{
lock (_lock)
{
if (_cachedModel == null) return null;
// Different sort/filter = must rebuild
if (_lastSortBy != sortBy || _lastFilter != filterKey) return null;
// Check age
var age = (DateTime.Now - _cacheBuiltAt).TotalSeconds;
if (age > MaxCacheAgeSeconds) return null;
// Check if any data files have been modified
if (HasFileChanged(Path.Combine(dataDirectory, "tickets.json"), ref _ticketsFileTime)) return null;
if (HasFileChanged(Path.Combine(dataDirectory, "external_tickets.json"), ref _externalFileTime)) return null;
if (HasFileChanged(Path.Combine(dataDirectory, "sheet_cache.csv"), ref _sheetCacheTime)) return null;
if (HasFileChanged(Path.Combine(dataDirectory, "config.json"), ref _configFileTime)) return null;
return _cachedModel;
}
}
/// <summary>
/// Store a freshly built model in the cache.
/// </summary>
public static void Store(DashboardViewModel model, string dataDirectory, string sortBy, string filterKey)
{
lock (_lock)
{
_cachedModel = model;
_cacheBuiltAt = DateTime.Now;
_lastSortBy = sortBy;
_lastFilter = filterKey;
// Snapshot current file times
_ticketsFileTime = GetFileTime(Path.Combine(dataDirectory, "tickets.json"));
_externalFileTime = GetFileTime(Path.Combine(dataDirectory, "external_tickets.json"));
_sheetCacheTime = GetFileTime(Path.Combine(dataDirectory, "sheet_cache.csv"));
_configFileTime = GetFileTime(Path.Combine(dataDirectory, "config.json"));
}
}
/// <summary>
/// Force invalidate the cache (called after writes if needed).
/// </summary>
public static void Invalidate()
{
lock (_lock)
{
_cachedModel = null;
_cacheBuiltAt = DateTime.MinValue;
}
}
private static bool HasFileChanged(string path, ref DateTime lastKnownTime)
{
var currentTime = GetFileTime(path);
if (currentTime != lastKnownTime)
{
lastKnownTime = currentTime;
return true;
}
return false;
}
private static DateTime GetFileTime(string path)
{
try
{
if (File.Exists(path)) return File.GetLastWriteTimeUtc(path);
}
catch { }
return DateTime.MinValue;
}
}
}