using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using NzbDrone.Common.EnsureThat; namespace NzbDrone.Common.Cache { public class Cached : ICached { private class CacheItem { public T Object { get; private set; } public DateTime? ExpiryTime { get; private set; } public CacheItem(T obj, TimeSpan? lifetime = null) { Object = obj; if (lifetime.HasValue) { ExpiryTime = DateTime.UtcNow + lifetime.Value; } } public bool IsExpired() { return ExpiryTime.HasValue && ExpiryTime.Value < DateTime.UtcNow; } } private readonly ConcurrentDictionary _store; private readonly TimeSpan? _defaultLifeTime; private readonly bool _rollingExpiry; public Cached(TimeSpan? defaultLifeTime = null, bool rollingExpiry = false) { _store = new ConcurrentDictionary(); _defaultLifeTime = defaultLifeTime; _rollingExpiry = rollingExpiry; } public void Set(string key, T value, TimeSpan? lifeTime = null) { Ensure.That(key, () => key).IsNotNullOrWhiteSpace(); _store[key] = new CacheItem(value, lifeTime ?? _defaultLifeTime); } public T Find(string key) { if (!_store.TryGetValue(key, out var cacheItem)) { return default(T); } if (cacheItem.IsExpired()) { if (TryRemove(key, cacheItem)) { return default(T); } if (!_store.TryGetValue(key, out cacheItem)) { return default(T); } } if (_rollingExpiry && _defaultLifeTime.HasValue) { _store.TryUpdate(key, new CacheItem(cacheItem.Object, _defaultLifeTime.Value), cacheItem); } return cacheItem.Object; } public void Remove(string key) { _store.TryRemove(key, out _); } public int Count => _store.Count; public T Get(string key, Func function, TimeSpan? lifeTime = null) { Ensure.That(key, () => key).IsNotNullOrWhiteSpace(); lifeTime = lifeTime ?? _defaultLifeTime; if (_store.TryGetValue(key, out var cacheItem) && !cacheItem.IsExpired()) { if (_rollingExpiry && lifeTime.HasValue) { _store.TryUpdate(key, new CacheItem(cacheItem.Object, lifeTime), cacheItem); } } else { var newCacheItem = new CacheItem(function(), lifeTime); if (cacheItem != null && _store.TryUpdate(key, newCacheItem, cacheItem)) { cacheItem = newCacheItem; } else { cacheItem = _store.GetOrAdd(key, newCacheItem); } } return cacheItem.Object; } public void Clear() { _store.Clear(); } public void ClearExpired() { var collection = (ICollection>)_store; foreach (var cached in _store.Where(c => c.Value.IsExpired()).ToList()) { collection.Remove(cached); } } public ICollection Values { get { return _store.Values.Select(c => c.Object).ToList(); } } private bool TryRemove(string key, CacheItem value) { var collection = (ICollection>)_store; return collection.Remove(new KeyValuePair(key, value)); } } }