|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
|
using Connected.Interop;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Caching;
|
|
|
|
|
|
|
|
|
|
internal class Entries
|
|
|
|
|
{
|
|
|
|
|
private readonly Lazy<ConcurrentDictionary<string, IEntry>> _items = new();
|
|
|
|
|
|
|
|
|
|
private ConcurrentDictionary<string, IEntry> Items => _items.Value;
|
|
|
|
|
public ImmutableList<string> Keys => Items.Keys.ToImmutableList();
|
|
|
|
|
public int Count => Items.Count;
|
|
|
|
|
|
|
|
|
|
public bool Any()
|
|
|
|
|
{
|
|
|
|
|
return Items.Any();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Scave()
|
|
|
|
|
{
|
|
|
|
|
var expired = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
foreach (var i in Items)
|
|
|
|
|
{
|
|
|
|
|
var r = i.Value;
|
|
|
|
|
|
|
|
|
|
if (r is null || r.Expired)
|
|
|
|
|
expired.Add(i.Key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var i in expired)
|
|
|
|
|
Remove(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ImmutableList<T> All<T>()
|
|
|
|
|
{
|
|
|
|
|
var r = new List<T>();
|
|
|
|
|
var expired = Items.Where(f => f.Value.Expired);
|
|
|
|
|
|
|
|
|
|
foreach (var e in expired)
|
|
|
|
|
Remove(e.Value.Id);
|
|
|
|
|
|
|
|
|
|
var instances = Items.Select(f => f.Value.Instance);
|
|
|
|
|
|
|
|
|
|
foreach (var i in instances)
|
|
|
|
|
{
|
|
|
|
|
if (TypeConversion.TryConvert(i, out T? result) && result is not null)
|
|
|
|
|
r.Add(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r.ToImmutableList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Remove(string key)
|
|
|
|
|
{
|
|
|
|
|
if (Items.IsEmpty)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (Items.TryRemove(key, out IEntry? v))
|
|
|
|
|
v.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Set(string key, object? instance, TimeSpan duration, bool slidingExpiration)
|
|
|
|
|
{
|
|
|
|
|
Items[key] = new Entry(key, instance, duration, slidingExpiration);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerator<T> GetEnumerator<T>()
|
|
|
|
|
{
|
|
|
|
|
return new EntryEnumerator<T>(Items);
|
|
|
|
|
}
|
|
|
|
|
public IEntry? Get(string key)
|
|
|
|
|
{
|
|
|
|
|
return Find(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEntry? First()
|
|
|
|
|
{
|
|
|
|
|
if (!Any())
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
return Items.First().Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEntry? Get<T>(Func<T, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
return Find(predicate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEntry? Get<T>(Func<dynamic, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
return Find<T>(predicate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ImmutableList<string>? Remove<T>(Func<T, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
if (Where(predicate) is not ImmutableList<string> ds || ds.IsEmpty)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
var result = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
foreach (var i in ds)
|
|
|
|
|
{
|
|
|
|
|
var key = Items.FirstOrDefault(f => InstanceEquals(f.Value?.Instance, i)).Key;
|
|
|
|
|
|
|
|
|
|
RemoveInternal(key);
|
|
|
|
|
|
|
|
|
|
result.Add(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.ToImmutableList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ImmutableList<T>? Where<T>(Func<T, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
var values = Items.Select(f => f.Value.Instance).Cast<T>();
|
|
|
|
|
|
|
|
|
|
if (values is null || !values.Any())
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
var filtered = values.Where(predicate);
|
|
|
|
|
|
|
|
|
|
if (filtered is null || !filtered.Any())
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
var r = new List<T>();
|
|
|
|
|
|
|
|
|
|
foreach (var i in filtered)
|
|
|
|
|
{
|
|
|
|
|
var ce = Items.FirstOrDefault(f => InstanceEquals(f.Value?.Instance, i));
|
|
|
|
|
|
|
|
|
|
if (ce.Value is null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (ce.Value.Expired)
|
|
|
|
|
{
|
|
|
|
|
RemoveInternal(ce.Value.Id);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ce.Value.Hit();
|
|
|
|
|
r.Add(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r.ToImmutableList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveInternal(string key)
|
|
|
|
|
{
|
|
|
|
|
if (Items.TryRemove(key, out IEntry? d))
|
|
|
|
|
d.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEntry? Find<T>(Func<T, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
var instances = Items.Select(f => f.Value?.Instance).Cast<T>();
|
|
|
|
|
|
|
|
|
|
if (instances is null || !instances.Any())
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (instances.FirstOrDefault(predicate) is not T instance)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
var ce = Items.Values.FirstOrDefault(f => InstanceEquals(f.Instance, instance));
|
|
|
|
|
|
|
|
|
|
if (ce is null)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (ce.Expired)
|
|
|
|
|
{
|
|
|
|
|
RemoveInternal(ce.Id);
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ce.Hit();
|
|
|
|
|
|
|
|
|
|
return ce;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEntry? Find<T>(Func<dynamic, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
var instances = Items.Select(f => f.Value?.Instance).Cast<dynamic>();
|
|
|
|
|
|
|
|
|
|
if (instances is null || !instances.Any())
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (instances.FirstOrDefault(predicate) is not T instance)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (Items.Values.FirstOrDefault(f => InstanceEquals(f.Instance, instance)) is not IEntry ce)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (ce.Expired)
|
|
|
|
|
{
|
|
|
|
|
RemoveInternal(ce.Id);
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ce.Hit();
|
|
|
|
|
|
|
|
|
|
return ce;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEntry? Find(string key)
|
|
|
|
|
{
|
|
|
|
|
if (!Items.ContainsKey(key))
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
if (Items.TryGetValue(key, out IEntry? d))
|
|
|
|
|
{
|
|
|
|
|
if (d.Expired)
|
|
|
|
|
{
|
|
|
|
|
RemoveInternal(key);
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.Hit();
|
|
|
|
|
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RemoveInternal(key);
|
|
|
|
|
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Exists(string key)
|
|
|
|
|
{
|
|
|
|
|
return Find(key) is not null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
foreach (var i in Items)
|
|
|
|
|
Remove(i.Key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool InstanceEquals(object? left, object? right)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* TODO: implement IEquality check
|
|
|
|
|
*/
|
|
|
|
|
if (left is null || right is null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (left.GetType().IsPrimitive)
|
|
|
|
|
return left == right;
|
|
|
|
|
|
|
|
|
|
if (left is string && right is string)
|
|
|
|
|
return string.Compare(left.ToString(), right.ToString(), false) == 0;
|
|
|
|
|
|
|
|
|
|
if (left.GetType().IsValueType && right.GetType().IsValueType)
|
|
|
|
|
return left.Equals(right);
|
|
|
|
|
|
|
|
|
|
return ReferenceEqualityComparer.Instance.Equals(left, right);
|
|
|
|
|
}
|
|
|
|
|
}
|