Editor/Mcp/Docs/DocCache.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace SboxMcp.Mcp.Docs;
internal sealed class DocCacheManifest
{
public int Version { get; set; } = 1;
public Dictionary<string, CachedPage> Pages { get; set; } = new();
public long LastFullCrawl { get; set; }
}
public sealed class DocCache
{
private const int CacheVersion = 1;
private readonly string _cacheDir;
private readonly string _manifestPath;
private readonly long _ttlMs;
private DocCacheManifest _manifest = new();
public DocCache()
{
_cacheDir = Environment.GetEnvironmentVariable( "SBOX_DOCS_CACHE_DIR" )
?? Path.Combine(
Environment.GetFolderPath( Environment.SpecialFolder.UserProfile ),
".sbox-mcp", "cache" );
_manifestPath = Path.Combine( _cacheDir, "docs-manifest.json" );
var ttlSeconds = int.TryParse(
Environment.GetEnvironmentVariable( "SBOX_DOCS_CACHE_TTL" ),
out var t ) ? t : 14400;
_ttlMs = ttlSeconds * 1000L;
}
public string CacheDir => _cacheDir;
public void Init()
{
Directory.CreateDirectory( _cacheDir );
if ( File.Exists( _manifestPath ) )
{
try
{
var raw = File.ReadAllText( _manifestPath );
var parsed = JsonSerializer.Deserialize<DocCacheManifest>( raw, JsonOpts.Default );
if ( parsed is { Version: CacheVersion } )
_manifest = parsed;
}
catch
{
// Corrupt cache — start fresh
}
}
}
public bool IsFresh()
{
if ( _manifest.LastFullCrawl == 0 ) return false;
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - _manifest.LastFullCrawl < _ttlMs;
}
public bool IsPageFresh( string url )
{
if ( !_manifest.Pages.TryGetValue( url, out var page ) ) return false;
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - page.FetchedAt < _ttlMs;
}
public CachedPage GetPage( string url ) =>
_manifest.Pages.TryGetValue( url, out var p ) ? p : null;
public IReadOnlyList<CachedPage> GetAllPages() => _manifest.Pages.Values.ToList();
public int GetPageCount() => _manifest.Pages.Count;
public void SetPage( CachedPage page ) => _manifest.Pages[page.Url] = page;
public void MarkFullCrawl() =>
_manifest.LastFullCrawl = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
public void Save()
{
Directory.CreateDirectory( _cacheDir );
File.WriteAllText( _manifestPath, JsonSerializer.Serialize( _manifest, JsonOpts.Default ) );
}
public int RemovePagesNotIn( HashSet<string> validUrls )
{
var toRemove = _manifest.Pages.Keys.Where( k => !validUrls.Contains( k ) ).ToList();
foreach ( var k in toRemove ) _manifest.Pages.Remove( k );
return toRemove.Count;
}
public void Clear()
{
_manifest = new DocCacheManifest();
Save();
}
}