/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.VNCache * File: VNCacheExtensions.cs * * VNCacheExtensions.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.IO; using System.Threading.Tasks; using System.Runtime.CompilerServices; using VNLib.Hashing; using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Utils.Resources; using VNLib.Utils.Extensions; using VNLib.Data.Caching; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.VNCache.DataModel; namespace VNLib.Plugins.Extensions.VNCache { /// /// Contains extension methods for aquiring a Plugin managed /// global cache provider. /// public static class VNCacheExtensions { internal const string CACHE_CONFIG_KEY = "cache"; internal const string EXTERN_CACHE_LIB_PATH = "assembly_name"; /// /// Loads from an external asset assembly package /// /// /// The path to the assembly that exports the global cache provider instance /// The directory search option /// The loaded instance public static IGlobalCacheProvider LoadCacheLibrary(this PluginBase plugin, string asmDllPath, SearchOption search = SearchOption.AllDirectories) => plugin.CreateServiceExternal(asmDllPath, search, null); /// /// Gets the configuration assigned global cache provider, if defined. If the configuration does not /// define a cache provider, this method returns null. This method loads a singleton instance. /// /// /// The assgined global cache provider or null if undefined public static IGlobalCacheProvider? GetDefaultGlobalCache(this PluginBase plugin) { if (plugin.TryGetConfig(CACHE_CONFIG_KEY) == null) { return null; } return LoadingExtensions.GetOrCreateSingleton(plugin, SingletonCacheLoader); } private static IGlobalCacheProvider SingletonCacheLoader(PluginBase plugin) { //Get the cache configuration IConfigScope config = plugin.GetConfig(CACHE_CONFIG_KEY); string dllPath = config.GetRequiredProperty(EXTERN_CACHE_LIB_PATH, p => p.GetString()!); plugin.Log.Verbose("Loading external cache library: {cl}", dllPath); IGlobalCacheProvider _client = plugin.LoadCacheLibrary(dllPath); //Try to call an init method if it exists ManagedLibrary.TryGetMethod(_client, "Init")?.Invoke(); //Try an async version Func? asyncInit = ManagedLibrary.TryGetMethod>(_client, "InitAsync"); //Schedule the async init if it exists if (asyncInit != null) { _ = plugin.ObserveWork(asyncInit, 100); } return _client; } /// /// Gets a simple scoped cache based on an entity prefix. The prefix is appended /// to the object id on each cache operation /// /// /// The simple prefix string to append to object ids before computing hashes /// The algorithm used to hash the combined object-ids /// The string encoding method used to encode the hash output /// The instance that will use the prefix to compute object ids /// public static ScopedCache GetPrefixedCache(this IGlobalCacheProvider cache, string prefix, HashAlg digest = HashAlg.SHA1, HashEncodingMode encoding = HashEncodingMode.Base64) { _ = cache ?? throw new ArgumentNullException(nameof(cache)); _ = prefix ?? throw new ArgumentNullException(nameof(prefix)); //Create simple cache key generator SimpleCacheKeyImpl keyProv = new(prefix, digest, encoding); //Create the scoped cache from the simple provider return cache.GetScopedCache(keyProv); } private sealed class SimpleCacheKeyImpl : ICacheKeyGenerator { private readonly string Prefix; private readonly HashAlg Digest; private readonly HashEncodingMode Encoding; public SimpleCacheKeyImpl(string prefix, HashAlg digest, HashEncodingMode encoding) { Prefix = prefix; Digest = digest; Encoding = encoding; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeBufferSize(string id) => id.Length + Prefix.Length; string ICacheKeyGenerator.ComputedKey(string entityId) { //Compute the required character buffer size int bufferSize = ComputeBufferSize(entityId); if(bufferSize < 128) { //Stack alloc a buffer Span buffer = stackalloc char[bufferSize]; //Writer to accumulate data ForwardOnlyWriter writer = new(buffer); //Append prefix and entity id writer.Append(Prefix); writer.Append(entityId); //Compute the simple hash of the combined values return ManagedHash.ComputeHash(writer.AsSpan(), Digest, Encoding); } else { //Alloc heap buffer for string concatination using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(bufferSize, true); //Writer to accumulate data ForwardOnlyWriter writer = new(buffer); //Append prefix and entity id writer.Append(Prefix); writer.Append(entityId); //Compute the simple hash of the combined values return ManagedHash.ComputeHash(writer.AsSpan(), Digest, Encoding); } } } } }