/*
<File>
<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
<License see="prj:///Documentation/License.txt"/>
<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
<CreationDate>2007-12-13 22:19:43Z</CreationDate>
<LastSubmissionDate>$Date: $</LastSubmissionDate>
<Version>$Revision: $</Version>
</File>
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace Orpius.GridComputing.Collections
{
/// <summary>
/// A dictionary collection that automatically
/// removes items from its own contents once
/// a specified duration has passed.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
class ExpiringDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
readonly Dictionary<TKey, TValue> innerDictionary = new Dictionary<TKey, TValue>();
readonly List<KeyValuePair<TKey, DateTime>> expiries = new List<KeyValuePair<TKey, DateTime>>();
TimeSpan itemLiveTime;
readonly object syncLock = new object();
readonly Timer timer = new Timer();
/// <summary>
/// Gets the sync lock, used for asynchronous access
/// to the inner collection.
/// </summary>
/// <value>The sync lock.</value>
public object SyncLock
{
get
{
return syncLock;
}
}
/// <summary>
/// Gets or sets the time that an item should remain
/// in the collection before it is automatically removed.
/// </summary>
/// <value>The item live time.</value>
public TimeSpan ItemLiveTime
{
get
{
return itemLiveTime;
}
set
{
itemLiveTime = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpiringDictionary<TKey, TValue>"/> class.
/// </summary>
/// <param name="itemLiveTime">The item live time. <seealso cref="ItemLiveTime"/></param>
/// <param name="interval">The interval. How frequently to scan
/// for items to eject.</param>
public ExpiringDictionary(TimeSpan itemLiveTime, TimeSpan interval)
{
this.itemLiveTime = itemLiveTime;
timer.Interval = interval.TotalMilliseconds;
timer.Elapsed += RemoveExpiredItems;
timer.Start();
}
void RemoveExpiredItems(object sender, ElapsedEventArgs e)
{
lock (SyncLock)
{
List<TKey> itemsToBeRemoved = new List<TKey>();
DateTime expiryTime = DateTime.Now - itemLiveTime;
foreach (KeyValuePair<TKey, DateTime> pair in expiries)
{
if (pair.Value < expiryTime)
{
itemsToBeRemoved.Add(pair.Key);
}
else
{
break;
}
}
foreach (TKey key in itemsToBeRemoved)
{
Remove(key);
}
}
}
public bool ContainsKey(TKey key)
{
return innerDictionary.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
lock (SyncLock)
{
innerDictionary.Add(key, value);
expiries.Add(new KeyValuePair<TKey, DateTime>(key, DateTime.Now));
}
}
public bool Remove(TKey key)
{
lock (SyncLock)
{
bool result = innerDictionary.Remove(key);
return result;
}
}
public bool TryGetValue(TKey key, out TValue value)
{
return innerDictionary.TryGetValue(key, out value);
}
public TValue this[TKey key]
{
get
{
return innerDictionary[key];
}
set
{
lock (SyncLock)
{
innerDictionary[key] = value;
expiries.RemoveAll(delegate(KeyValuePair<TKey, DateTime> k)
{
return EqualityComparer<TKey>.Default.Equals(k.Key, key);
});
if (!EqualityComparer<TValue>.Default.Equals(value))
{
expiries.Add(new KeyValuePair<TKey, DateTime>(key, DateTime.Now));
}
}
}
}
public ICollection<TKey> Keys
{
get
{
return innerDictionary.Keys;
}
}
public ICollection<TValue> Values
{
get
{
return innerDictionary.Values;
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
lock (SyncLock)
{
innerDictionary.Add(item.Key, item.Value);
if (!EqualityComparer<KeyValuePair<TKey, TValue>>.Default.Equals(item))
{
expiries.Add(new KeyValuePair<TKey, DateTime>(item.Key, DateTime.Now));
}
}
}
public void Clear()
{
lock (SyncLock)
{
innerDictionary.Clear();
expiries.Clear();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
if (EqualityComparer<KeyValuePair<TKey, TValue>>.Default.Equals(item))
{
return false;
}
return innerDictionary.ContainsKey(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new ArgumentOutOfRangeException("arrayIndex", "Must be greater than zero.");
}
if (array.Length - arrayIndex < Count)
{
throw new ArgumentException("array.Length must be greater than arrayIndex.");
}
lock (SyncLock)
{
foreach (KeyValuePair<TKey, TValue> pair in innerDictionary)
{
array[arrayIndex++] = new KeyValuePair<TKey, TValue>(pair.Key, pair.Value);
}
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (EqualityComparer<KeyValuePair<TKey, TValue>>.Default.Equals(item))
{
return false;
}
lock (SyncLock)
{
bool result = innerDictionary.Remove(item.Key);
return result;
}
}
public int Count
{
get
{
return innerDictionary.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return innerDictionary.GetEnumerator();
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator();
}
/// <summary>
/// Updates the expiry time on an item in the collection;
/// thus renewing it so that the time it will remain in the
/// collection will be increased.
/// </summary>
/// <param name="key">The key.</param>
public void Touch(TKey key)
{
lock (SyncLock)
{
this[key] = this[key]; /* Forces DateTime update. */
}
}
}
}