using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Caching.Distributed; namespace Iceshrimp.Backend.Core.Extensions; public static class DistributedCacheExtensions { //TODO: named caches, CacheService? (that optionally uses StackExchange.Redis directly)? //TODO: thread-safe locks to prevent fetching data more than once //TODO: sliding window ttl? //TODO: renew option on GetAsync and FetchAsync //TODO: check that this actually works for complex types (sigh) private static readonly JsonSerializerOptions Options = new(JsonSerializerOptions.Default) { ReferenceHandler = ReferenceHandler.Preserve }; public static async Task GetAsync(this IDistributedCache cache, string key) where T : class { var buffer = await cache.GetAsync(key); if (buffer == null || buffer.Length == 0) return null; var stream = new MemoryStream(buffer); try { var data = await JsonSerializer.DeserializeAsync(stream, Options); return data; } catch { return null; } } public static async Task GetAsyncValue(this IDistributedCache cache, string key) where T : struct { var buffer = await cache.GetAsync(key); if (buffer == null || buffer.Length == 0) return null; var stream = new MemoryStream(buffer); try { var data = await JsonSerializer.DeserializeAsync(stream, Options); return data; } catch { return null; } } public static async Task FetchAsync( this IDistributedCache cache, string key, TimeSpan ttl, Func> fetcher ) where T : class { var hit = await cache.GetAsync(key); if (hit != null) return hit; var fetched = await fetcher(); await cache.SetAsync(key, fetched, ttl); return fetched; } public static async Task FetchAsync( this IDistributedCache cache, string key, TimeSpan ttl, Func fetcher ) where T : class { return await FetchAsync(cache, key, ttl, () => Task.FromResult(fetcher())); } public static async Task FetchAsyncValue( this IDistributedCache cache, string key, TimeSpan ttl, Func> fetcher ) where T : struct { var hit = await cache.GetAsyncValue(key); if (hit.HasValue) return hit.Value; var fetched = await fetcher(); await cache.SetAsync(key, fetched, ttl); return fetched; } public static async Task FetchAsyncValue( this IDistributedCache cache, string key, TimeSpan ttl, Func fetcher ) where T : struct { return await FetchAsyncValue(cache, key, ttl, () => Task.FromResult(fetcher())); } public static async Task SetAsync(this IDistributedCache cache, string key, T data, TimeSpan ttl) { using var stream = new MemoryStream(); await JsonSerializer.SerializeAsync(stream, data, Options); stream.Position = 0; var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl }; await cache.SetAsync(key, stream.ToArray(), options); } }