using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Runtime.Caching; namespace ZelWiki.Caching { public class WikiCache { public enum Category { User, Page, Search, Emoji, Configuration } public static int DefaultCacheSeconds { get; set; } private static MemoryCache? _memCache; public static ulong CachePuts { get; set; } public static ulong CacheGets { get; set; } public static ulong CacheHits { get; set; } public static ulong CacheMisses { get; set; } public static int CacheItemCount => MemCache.Count(); public static double CacheMemoryLimit => MemCache.CacheMemoryLimit; public static MemoryCache MemCache => _memCache ?? throw new Exception("Cache has not been initialized."); public static void Initialize(int cacheMemoryLimitMB, int defaultCacheSeconds) { DefaultCacheSeconds = defaultCacheSeconds; var config = new NameValueCollection { //config.Add("pollingInterval", "00:05:00"); //config.Add("physicalMemoryLimitPercentage", "0"); { "CacheMemoryLimitMegabytes", cacheMemoryLimitMB.ToString() } }; _memCache?.Dispose(); _memCache = new MemoryCache("TightWikiCache", config); } /// /// Gets an item from the cache. /// /// /// /// public static T? Get(IWikiCacheKey cacheKey) { CacheGets++; var result = (T)MemCache.Get(cacheKey.Key); if (result == null) { CacheMisses++; } CacheHits++; return result; } /// /// Determines if the cache contains a given key. /// /// /// /// public static bool Contains(IWikiCacheKey cacheKey) { CacheGets++; if (MemCache.Contains(cacheKey.Key)) { CacheHits++; return true; } CacheMisses++; return false; } /// /// Gets an item from the cache. /// /// /// /// public static bool TryGet(IWikiCacheKey cacheKey, [NotNullWhen(true)] out T result) { CacheGets++; if ((result = (T)MemCache.Get(cacheKey.Key)) == null) { CacheMisses++; return false; } CacheHits++; return true; } /// /// Adds an item to the cache. If the item is already in the cache, this will reset its expiration. /// /// /// /// public static void Put(IWikiCacheKey cacheKey, object value, int? seconds = null) { CachePuts++; seconds ??= DefaultCacheSeconds; if (seconds <= 0) { return; } var policy = new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(seconds ?? DefaultCacheSeconds) }; MemCache.Add(cacheKey.Key, value, policy); } public static void Put(IWikiCacheKey cacheKey, object value, CacheItemPolicy policy) { CachePuts++; MemCache.Add(cacheKey.Key, value, policy); } /// /// Removes all entries from the cache. /// public static void Clear() { if (_memCache == null) { return; } var items = MemCache.ToList(); foreach (var a in items) { MemCache.Remove(a.Key); } } /// /// Removes cache entries that begin with the given cache key. /// /// public static void ClearCategory(WikiCacheKey cacheKey) { var keys = new List(); foreach (var item in MemCache) { if (item.Key.StartsWith(cacheKey.Key)) { keys.Add(item.Key); } } keys.ForEach(o => MemCache.Remove(o)); } /// /// Removes cache entries in a given category. /// /// public static void ClearCategory(Category category) { var cacheKey = WikiCacheKey.Build(category); var keys = new List(); foreach (var item in MemCache) { if (item.Key.StartsWith(cacheKey.Key)) { keys.Add(item.Key); } } keys.ForEach(o => MemCache.Remove(o)); } } }