Editor/Mcp/Docs/ApiCache.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
namespace SboxMcp.Mcp.Docs;
internal sealed class ApiCacheManifest
{
public int Version { get; set; } = 1;
public string SchemaUrl { get; set; } = "";
public long FetchedAt { get; set; }
public int TypeCount { get; set; }
}
public sealed class ApiCache
{
private const int CacheVersion = 1;
private const int DefaultTtlSeconds = 86400;
private readonly string _cacheDir;
private readonly string _manifestPath;
private readonly string _typesPath;
private readonly long _ttlMs;
private ApiCacheManifest _manifest = new();
public ApiCache()
{
_cacheDir = Environment.GetEnvironmentVariable( "SBOX_DOCS_CACHE_DIR" )
?? Path.Combine(
Environment.GetFolderPath( Environment.SpecialFolder.UserProfile ),
".sbox-mcp", "cache" );
_manifestPath = Path.Combine( _cacheDir, "api-manifest.json" );
_typesPath = Path.Combine( _cacheDir, "api-types.json" );
var ttlSeconds = int.TryParse(
Environment.GetEnvironmentVariable( "SBOX_API_CACHE_TTL" ),
out var t ) ? t : DefaultTtlSeconds;
_ttlMs = ttlSeconds * 1000L;
}
public void Init()
{
Directory.CreateDirectory( _cacheDir );
if ( File.Exists( _manifestPath ) )
{
try
{
var raw = File.ReadAllText( _manifestPath );
var parsed = JsonSerializer.Deserialize<ApiCacheManifest>( raw, JsonOpts.Default );
if ( parsed is { Version: CacheVersion } )
_manifest = parsed;
}
catch
{
// Corrupt cache — start fresh
}
}
}
public bool IsFresh()
{
if ( _manifest.FetchedAt == 0 ) return false;
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - _manifest.FetchedAt < _ttlMs;
}
public int GetTypeCount() => _manifest.TypeCount;
public string GetSchemaUrl() => _manifest.SchemaUrl;
public List<ApiType> LoadTypes()
{
if ( !File.Exists( _typesPath ) ) return null;
try
{
var raw = File.ReadAllText( _typesPath );
return JsonSerializer.Deserialize<List<ApiType>>( raw, JsonOpts.Default );
}
catch
{
return null;
}
}
public void Save( string schemaUrl, List<ApiType> types )
{
Directory.CreateDirectory( _cacheDir );
File.WriteAllText( _typesPath, JsonSerializer.Serialize( types, JsonOpts.Default ) );
_manifest = new ApiCacheManifest
{
Version = CacheVersion,
SchemaUrl = schemaUrl,
FetchedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
TypeCount = types.Count,
};
File.WriteAllText( _manifestPath, JsonSerializer.Serialize( _manifest, JsonOpts.Default ) );
}
public void Clear()
{
_manifest = new ApiCacheManifest();
if ( File.Exists( _typesPath ) ) File.Delete( _typesPath );
if ( File.Exists( _manifestPath ) )
File.WriteAllText( _manifestPath, JsonSerializer.Serialize( _manifest, JsonOpts.Default ) );
}
}