using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; namespace NzbDrone.Common.Cache { public class CachedDictionary : ICachedDictionary { private readonly Func> _fetchFunc; private readonly TimeSpan? _ttl; private DateTime _lastRefreshed = DateTime.MinValue; private ConcurrentDictionary _items = new ConcurrentDictionary(); public CachedDictionary(Func> fetchFunc = null, TimeSpan? ttl = null) { _fetchFunc = fetchFunc; _ttl = ttl; } public bool IsExpired(TimeSpan ttl) { return _lastRefreshed.Add(ttl) < DateTime.UtcNow; } public void RefreshIfExpired() { if (_ttl.HasValue && _fetchFunc != null) { RefreshIfExpired(_ttl.Value); } } public void RefreshIfExpired(TimeSpan ttl) { if (IsExpired(ttl)) { Refresh(); } } public void Refresh() { if (_fetchFunc == null) { throw new InvalidOperationException("Cannot update cache without data source."); } Update(_fetchFunc()); ExtendTTL(); } public void Update(IDictionary items) { _items = new ConcurrentDictionary(items); ExtendTTL(); } public void ExtendTTL() { _lastRefreshed = DateTime.UtcNow; } [DebuggerBrowsable(DebuggerBrowsableState.Never)] public ICollection Values { get { RefreshIfExpired(); return _items.Values; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] public int Count { get { RefreshIfExpired(); return _items.Count; } } public TValue Get(string key) { RefreshIfExpired(); if (!_items.TryGetValue(key, out var result)) { throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key)); } return result; } public TValue Find(string key) { RefreshIfExpired(); _items.TryGetValue(key, out var result); return result; } public void Clear() { _items.Clear(); _lastRefreshed = DateTime.MinValue; } public void ClearExpired() { if (!_ttl.HasValue) { throw new InvalidOperationException("Checking expiry without ttl not possible."); } if (IsExpired(_ttl.Value)) { Clear(); } } public void Remove(string key) { _items.TryRemove(key, out _); } } }