using System; using System.Data.HashFunction.FNV; using System.IO; using System.IO.Abstractions; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; using Trash.Config; namespace Trash.Cache { public class ServiceCache : IServiceCache { private static readonly Regex AllowedObjectNameCharacters = new(@"^[\w-]+$", RegexOptions.Compiled); private readonly IServiceConfiguration _config; private readonly IFileSystem _fileSystem; private readonly IFNV1a _hash; private readonly ICacheStoragePath _storagePath; public ServiceCache(IFileSystem fileSystem, ICacheStoragePath storagePath, IServiceConfiguration config, ILogger log) { _fileSystem = fileSystem; _storagePath = storagePath; _config = config; Log = log; _hash = FNV1aFactory.Instance.Create(FNVConfig.GetPredefinedConfig(32)); } private ILogger Log { get; } public T? Load() where T : class { var path = PathFromAttribute(); if (!_fileSystem.File.Exists(path)) { return null; } var json = _fileSystem.File.ReadAllText(path); try { return JObject.Parse(json).ToObject(); } catch (JsonException e) { Log.Error("Failed to read cache data, will proceed without cache. Reason: {Msg}", e.Message); } return null; } public void Save(T obj) where T : class { var path = PathFromAttribute(); _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.File.WriteAllText(path, JsonConvert.SerializeObject(obj, Formatting.Indented)); } private static string GetCacheObjectNameAttribute() { var attribute = typeof(T).GetCustomAttribute(); if (attribute == null) { throw new ArgumentException($"{nameof(CacheObjectNameAttribute)} is missing on type {nameof(T)}"); } return attribute.Name; } private string BuildServiceGuid() { return _hash.ComputeHash(Encoding.ASCII.GetBytes(_config.BaseUrl)).AsHexString(); } private string PathFromAttribute() { var objectName = GetCacheObjectNameAttribute(); if (!AllowedObjectNameCharacters.IsMatch(objectName)) { throw new ArgumentException($"Object name '{objectName}' has unacceptable characters"); } return Path.Combine(_storagePath.Path, BuildServiceGuid(), objectName + ".json"); } } }