FileCache
FileCache.cs
/*
3Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com)
This file is part of FileCache (http://github.com/acarteas/FileCache).
FileCache is distributed under the Apache License 2.0.
Consult "LICENSE.txt" included in this package for the Apache License 2.0.
*/
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Threading.Tasks;
namespace System.Runtime.Caching
{
public clast FileCache : ObjectCache
{
private static int _nameCounter = 1;
private string _name = "";
private SerializationBinder _binder;
private static FileCacheManagers _defaultManager = FileCacheManagers.Basic;
private string _cacheSubFolder = "cache";
private string _policySubFolder = "policy";
private TimeSpan _cleanInterval = new TimeSpan(0, 0, 0, 0); // default to 1 week
private const string LastCleanedDateFile = "cache.lcd";
private const string CacheSizeFile = "cache.size";
// this is a file used to prevent multiple processes from trying to "clean" at the same time
private const string SemapreplacedFile = "cache.sem";
private long _currentCacheSize = 0;
public string CacheDir { get; protected set; }
///
/// Allows for the setting of the default cache manager so that it doesn't have to be
/// specified on every instance creation.
///
public static FileCacheManagers DefaultCacheManager
{
get
{
return _defaultManager;
}
set
{
_defaultManager = value;
}
}
///
/// Used to abstract away the low-level details of file management. This allows
/// for multiple file formatting schemes based on use case.
///
public FileCacheManager CacheManager { get; protected set; }
///
/// Used to store the default region when accessing the cache via [] calls
///
public string DefaultRegion { get; set; }
///
/// Used to set the default policy when setting cache values via [] calls
///
public CacheItemPolicy DefaultPolicy { get; set; }
///
/// Specified how the cache payload is to be handled.
///
public enum PayloadMode
{
///
/// Treat the payload a a serializable object.
///
Serializable,
///
/// Treat the payload as a file name. File content will be copied on add, while get returns the file name.
///
Filename,
///
/// Treat the paylad as raw bytes. A byte[] and readable streams are supported on add.
///
RawBytes
}
///
/// Specified whether the payload is deserialized or just the file name.
///
public PayloadMode PayloadReadMode { get; set; } = PayloadMode.Serializable;
///
/// Specified how the payload is to be handled on add operations.
///
public PayloadMode PayloadWriteMode { get; set; } = PayloadMode.Serializable;
///
/// The amount of time before expiry that a filename will be used as a payoad. I.e.
/// the amount of time the cache's user can safely use the file delivered as a payload.
/// Default 10 minutes.
///
public TimeSpan FilenameAsPayloadSafetyMargin = TimeSpan.FromMinutes(10);
///
/// Used to determine how long the FileCache will wait for a file to become
/// available. Default (00:00:00) is indefinite. Should the timeout be
/// reached, an exception will be thrown.
///
public TimeSpan AccessTimeout
{
get
{
return CacheManager.AccessTimeout;
}
set
{
CacheManager.AccessTimeout = value;
}
}
///
/// Used to specify the disk size, in bytes, that can be used by the File Cache
///
public long MaxCacheSize { get; set; }
///
/// Returns the approximate size of the file cache
///
public long CurrentCacheSize
{
get
{
// if this is the first query, we need to load the cache size from somewhere
if (_currentCacheSize == 0)
{
// Read the system file for cache size
long cacheSize;
if (CacheManager.ReadSysValue(CacheSizeFile, out cacheSize))
{
// Did we successfully get data from the file? Write it to our member var.
_currentCacheSize = cacheSize;
}
}
return _currentCacheSize;
}
private set
{
// no need to do a pointless re-store of the same value
if (_currentCacheSize != value || value == 0)
{
CacheManager.WriteSysValue(CacheSizeFile, value);
_currentCacheSize = value;
}
}
}
///
/// Event that will be called when is reached.
///
public event EventHandler MaxCacheSizeReached = delegate { };
public event EventHandler CacheResized = delegate { };
///
/// The default cache path used by FC.
///
private string DefaultCachePath
{
get
{
return Path.Combine(Directory.GetCurrentDirectory(), "FileCache");
}
}
#region constructors
///
/// Creates a default instance of the file cache using the supplied file cache manager.
///
///
public FileCache(FileCacheManagers manager)
{
Init(manager, false, new TimeSpan(), true, true);
}
///
/// Creates a default instance of the file cache. Don't use if you plan to serialize custom objects
///
/// If true, will calcualte the cache's current size upon new object creation.
/// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on
/// use case.
///
/// If supplied, sets the interval of time that must occur between self cleans
public FileCache(
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan()
)
{
// CT note: I moved this code to an init method because if the user specified a cache root, that needs to
// be set before checking if we should clean (otherwise it will look for the file in the wrong place)
Init(DefaultCacheManager, calculateCacheSize, cleanInterval, true, true);
}
///
/// Creates an instance of the file cache using the supplied path as the root save path.
///
/// The cache's root file path
/// If true, will calcualte the cache's current size upon new object creation.
/// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on
/// use case.
///
/// If supplied, sets the interval of time that must occur between self cleans
public FileCache(
string cacheRoot,
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan())
{
CacheDir = cacheRoot;
Init(DefaultCacheManager, calculateCacheSize, cleanInterval, false, true);
}
///
/// Creates an instance of the file cache.
///
/// The SerializationBinder used to deserialize cached objects. Needed if you plan
/// to cache custom objects.
///
/// If true, will calcualte the cache's current size upon new object creation.
/// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on
/// use case.
///
/// If supplied, sets the interval of time that must occur between self cleans
public FileCache(
SerializationBinder binder,
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan()
)
{
_binder = binder;
Init(DefaultCacheManager, calculateCacheSize, cleanInterval, true, false);
}
///
/// Creates an instance of the file cache.
///
/// The cache's root file path
/// The SerializationBinder used to deserialize cached objects. Needed if you plan
/// to cache custom objects.
/// If true, will calcualte the cache's current size upon new object creation.
/// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on
/// use case.
///
/// If supplied, sets the interval of time that must occur between self cleans
public FileCache(
string cacheRoot,
SerializationBinder binder,
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan()
)
{
_binder = binder;
CacheDir = cacheRoot;
Init(DefaultCacheManager, calculateCacheSize, cleanInterval, false, false);
}
///
/// Creates an instance of the file cache.
///
///
/// The cache's root file path
/// The SerializationBinder used to deserialize cached objects. Needed if you plan
/// to cache custom objects.
/// If true, will calcualte the cache's current size upon new object creation.
/// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on
/// use case.
///
/// If supplied, sets the interval of time that must occur between self cleans
public FileCache(
FileCacheManagers manager,
string cacheRoot,
SerializationBinder binder,
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan()
)
{
_binder = binder;
CacheDir = cacheRoot;
Init(manager, calculateCacheSize, cleanInterval, false, false);
}
#endregion
#region custom methods
private void Init(
FileCacheManagers manager,
bool calculateCacheSize = false,
TimeSpan cleanInterval = new TimeSpan(),
bool setCacheDirToDefault = true,
bool setBinderToDefault = true
)
{
_name = "FileCache_" + _nameCounter;
_nameCounter++;
DefaultRegion = null;
DefaultPolicy = new CacheItemPolicy();
MaxCacheSize = long.MaxValue;
// set default values if not already set
if (setCacheDirToDefault)
{
this.CacheDir = this.DefaultCachePath;
}
if (setBinderToDefault)
{
this._binder = new FileCacheBinder();
}
// if it doesn't exist, we need to make it
if (!Directory.Exists(CacheDir))
{
Directory.CreateDirectory(this.CacheDir);
}
// only set the clean interval if the user supplied it
if (cleanInterval > new TimeSpan())
{
_cleanInterval = cleanInterval;
}
//set up cache manager
CacheManager = FileCacheManagerFactory.Create(manager);
CacheManager.CacheDir = CacheDir;
CacheManager.CacheSubFolder = _cacheSubFolder;
CacheManager.PolicySubFolder = _policySubFolder;
CacheManager.Binder = _binder;
CacheManager.AccessTimeout = new TimeSpan();
//check to see if cache is in need of immediate cleaning
if (ShouldClean())
{
CleanCacheAsync();
}
else if (calculateCacheSize || CurrentCacheSize == 0)
{
// This is in an else if block, because CleanCacheAsync will
// update the cache size, so no need to do it twice.
UpdateCacheSizeAsync();
}
MaxCacheSizeReached += FileCache_MaxCacheSizeReached;
}
private void FileCache_MaxCacheSizeReached(object sender, FileCacheEventArgs e)
{
Task.Factory.StartNew((Action) (() =>
{
// Shrink the cache to 75% of the max size
// that way there's room for it to grow a bit
// before we have to do this again.
long newSize = ShrinkCacheToSize((long)(MaxCacheSize*0.75));
}));
}
// Returns the cleanlock file if it can be opened, otherwise it is being used by another process so return null
private FileStream GetCleaningLock()
{
try
{
return File.Open(Path.Combine(CacheDir, SemapreplacedFile), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception)
{
return null;
}
}
// Determines whether or not enough time has pasted that the cache should clean itself
private bool ShouldClean()
{
try
{
// if the file can't be found, or is corrupt this will throw an exception
DateTime lastClean;
if (!CacheManager.ReadSysValue(LastCleanedDateFile, out lastClean))
{
//AC: rewrote to be safer in cases where no value obtained.
return true;
}
// return true if the amount of time between now and the last clean is greater than or equal to the
// clean interval, otherwise return false.
return DateTime.Now - lastClean >= _cleanInterval;
}
catch (Exception)
{
return true;
}
}
///
/// Shrinks the cache until the cache size is less than
/// or equal to the size specified (in bytes). This is a
/// rather expensive operation, so use with discretion.
///
/// The new size of the cache
public long ShrinkCacheToSize(long newSize, string regionName = null)
{
long originalSize = 0, amount = 0, removed = 0;
//lock down other treads from trying to shrink or clean
using (FileStream cLock = GetCleaningLock())
{
if (cLock == null)
{
return -1;
}
// if we're shrinking the whole cache, we can use the stored
// size if it's available. If it's not available we calculate it and store
// it for next time.
if (regionName == null)
{
if (CurrentCacheSize == 0)
{
CurrentCacheSize = GetCacheSize();
}
originalSize = CurrentCacheSize;
}
else
{
originalSize = GetCacheSize(regionName);
}
// Find out how much we need to get rid of
amount = originalSize - newSize;
// CT note: This will update CurrentCacheSize
removed = DeleteOldestFiles(amount, regionName);
// unlock the semapreplaced for others
cLock.Close();
}
// trigger the event
CacheResized(this, new FileCacheEventArgs(originalSize - removed, MaxCacheSize));
// return the final size of the cache (or region)
return originalSize - removed;
}
public void CleanCacheAsync()
{
Task.Factory.StartNew((Action) (() =>
{
CleanCache();
}));
}
///
/// Loop through the cache and delete all expired files
///
/// The amount removed (in bytes)
public long CleanCache(string regionName = null)
{
long removed = 0;
//lock down other treads from trying to shrink or clean
using (FileStream cLock = GetCleaningLock())
{
if (cLock == null)
return 0;
IEnumerable regions =
string.IsNullOrEmpty(regionName)
? CacheManager.GetRegions()
: new List(1) { regionName };
foreach (var region in regions)
{
foreach (string key in GetKeys(region))
{
CacheItemPolicy policy = GetPolicy(key, region);
if (policy.AbsoluteExpiration < DateTime.Now)
{
try
{
string cachePath = CacheManager.GetCachePath(key, region);
string policyPath = CacheManager.GetPolicyPath(key, region);
CacheItemReference ci = new CacheItemReference(key, region, cachePath, policyPath);
Remove(key, region); // CT note: Remove will update CurrentCacheSize
removed += ci.Length;
}
catch (Exception) // skip if the file cannot be accessed
{ }
}
}
}
// mark that we've cleaned the cache
CacheManager.WriteSysValue(LastCleanedDateFile, DateTime.Now);
// unlock
cLock.Close();
}
return removed;
}
///
/// Delete the oldest items in the cache to shrink the chache by the
/// specified amount (in bytes).
///
/// The amount of data that was actually removed
private long DeleteOldestFiles(long amount, string regionName = null)
{
// Verify that we actually need to shrink
if (amount 0)
{
//remove oldest item
CacheItemReference oldest = cacheReferences.Dequeue();
removedBytes += oldest.Length;
Remove(oldest.Key, oldest.Region);
}
return removedBytes;
}
///
/// This method calls GetCacheSize on a separate thread to
/// calculate and then store the size of the cache.
///
public void UpdateCacheSizeAsync()
{
Task.Factory.StartNew((Action) (() =>
{
CurrentCacheSize = GetCacheSize();
}));
}
//AC Note: From MSDN / SO (http://stackoverflow.com/questions/468119/whats-the-best-way-to-calculate-the-size-of-a-directory-in-net)
///
/// Calculates the size, in bytes of the file cache
///
/// The region to calculate. If NULL, will return total size.
///
public long GetCacheSize(string regionName = null)
{
long size = 0;
//AC note: First parameter is unused, so just past in garbage ("DummyValue")
string policyPath = Path.GetDirectoryName(CacheManager.GetPolicyPath("DummyValue", regionName));
string cachePath = Path.GetDirectoryName(CacheManager.GetCachePath("DummyValue", regionName));
size += CacheSizeHelper(new DirectoryInfo(policyPath));
size += CacheSizeHelper(new DirectoryInfo(cachePath));
return size;
}
///
/// Helper method for public .
///
///
///
private long CacheSizeHelper(DirectoryInfo root)
{
long size = 0;
// Add file sizes.
var fis = root.EnumerateFiles();
foreach (FileInfo fi in fis)
{
size += fi.Length;
}
// Add subdirectory sizes.
var dis = root.EnumerateDirectories();
foreach (DirectoryInfo di in dis)
{
size += CacheSizeHelper(di);
}
return size;
}
///
/// Clears all FileCache-related items from the disk. Throws an exception if the cache can't be
/// deleted.
///
public void Clear()
{
//Before we can delete the entire file tree, we have to wait for any latent writes / reads to finish
//To do this, we wait for access to our cacheLock file. When we get access, we have to immediately
//release it (can't delete a file that is open!), which somewhat muddies our condition of needing
//exclusive access to the FileCache. However, the time between closing and making the call to
//delete is so small that we probably won't run into an exception most of the time.
FileStream cacheLock = null;
TimeSpan totalTime = new TimeSpan(0);
TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
TimeSpan timeToWait = AccessTimeout;
if (AccessTimeout == new TimeSpan())
{
//if access timeout is not set, make really large wait time
timeToWait = new TimeSpan(5, 0, 0);
}
while (cacheLock == null && timeToWait > totalTime)
{
cacheLock = GetCleaningLock();
Thread.Sleep(interval);
totalTime += interval;
}
if (cacheLock == null)
{
throw new TimeoutException("FileCache AccessTimeout reached when attempting to clear cache.");
}
cacheLock.Close();
//now that we've waited for everything to stop, we can delete the cache directory.
Directory.Delete(CacheDir, true);
}
///
/// Flushes the file cache using DateTime.Now as the minimum date
///
///
public void Flush(string regionName = null)
{
Flush(DateTime.Now, regionName);
}
///
/// Flushes the cache based on last access date, filtered by optional region
///
///
///
public void Flush(DateTime minDate, string regionName = null)
{
// prevent other threads from altering stuff while we delete junk
using (FileStream cLock = GetCleaningLock())
{
if (cLock == null)
{
return;
}
IEnumerable regions =
string.IsNullOrEmpty(regionName)
? CacheManager.GetRegions()
: new List(1) { regionName };
foreach (var region in regions)
{
IEnumerable keys = CacheManager.GetKeys(region);
foreach (string key in keys)
{
string policyPath = CacheManager.GetPolicyPath(key, region);
string cachePath = CacheManager.GetCachePath(key, region);
// Update the Cache size before flushing this item.
CurrentCacheSize = GetCacheSize();
//if either policy or cache are stale, delete both
if (File.GetLastAccessTime(policyPath) < minDate || File.GetLastAccessTime(cachePath) < minDate)
{
CurrentCacheSize -= CacheManager.DeleteFile(key, region);
}
}
}
// unlock
cLock.Close();
}
}
///
/// Returns the policy attached to a given cache item.
///
/// The key of the item
/// The region in which the key exists
///
public CacheItemPolicy GetPolicy(string key, string regionName = null)
{
CacheItemPolicy policy = new CacheItemPolicy();
FileCachePayload payload = CacheManager.ReadFile(PayloadMode.Filename, key, regionName) as FileCachePayload;
if (payload != null)
{
try
{
policy.SlidingExpiration = payload.Policy.SlidingExpiration;
policy.AbsoluteExpiration = payload.Policy.AbsoluteExpiration;
}
catch (Exception)
{
}
}
return policy;
}
public IEnumerable GetKeys(string regionName = null)
{
return CacheManager.GetKeys(regionName);
}
#endregion
#region private helpers
private void WriteHelper(PayloadMode mode, string key, FileCachePayload data, string regionName = null, bool policyUpdateOnly = false)
{
CurrentCacheSize += CacheManager.WriteFile(mode, key, data, regionName, policyUpdateOnly);
//check to see if limit was reached
if (CurrentCacheSize > MaxCacheSize)
{
MaxCacheSizeReached(this, new FileCacheEventArgs(CurrentCacheSize, MaxCacheSize));
}
}
#endregion
#region ObjectCache overrides
public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
{
string path = CacheManager.GetCachePath(key, regionName);
object oldData = null;
//pull old value if it exists
if (File.Exists(path))
{
try
{
oldData = Get(key, regionName);
}
catch (Exception)
{
oldData = null;
}
}
SerializableCacheItemPolicy cachePolicy = new SerializableCacheItemPolicy(policy);
FileCachePayload newPayload = new FileCachePayload(value, cachePolicy);
WriteHelper(PayloadWriteMode, key, newPayload, regionName);
//As docameented in the spec (http://msdn.microsoft.com/en-us/library/dd780602.aspx), return the old
//cached value or null
return oldData;
}
public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
{
object oldData = AddOrGetExisting(value.Key, value.Value, policy, value.RegionName);
CacheItem returnItem = null;
if (oldData != null)
{
returnItem = new CacheItem(value.Key)
{
Value = oldData,
RegionName = value.RegionName
};
}
return returnItem;
}
public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
{
CacheItemPolicy policy = new CacheItemPolicy();
policy.AbsoluteExpiration = absoluteExpiration;
return AddOrGetExisting(key, value, policy, regionName);
}
public override bool Contains(string key, string regionName = null)
{
string path = CacheManager.GetCachePath(key, regionName);
return File.Exists(path);
}
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null)
{
throw new NotImplementedException();
}
public override DefaultCacheCapabilities DefaultCacheCapabilities
{
get
{
//AC note: can use boolean OR "|" to set multiple flags.
return DefaultCacheCapabilities.CacheRegions
|
DefaultCacheCapabilities.AbsoluteExpirations
|
DefaultCacheCapabilities.SlidingExpirations
;
}
}
public override object Get(string key, string regionName = null)
{
FileCachePayload payload = CacheManager.ReadFile(PayloadReadMode, key, regionName) as FileCachePayload;
string cachedItemPath = CacheManager.GetCachePath(key, regionName);
DateTime cutoff = DateTime.Now;
if (PayloadReadMode == PayloadMode.Filename)
{
cutoff += FilenameAsPayloadSafetyMargin;
}
//null payload?
if (payload.Policy != null && payload.Payload != null)
{
//did the item expire?
if (payload.Policy.AbsoluteExpiration < cutoff)
{
//set the payload to null
payload.Payload = null;
//delete the file from the cache
try
{
// CT Note: I changed this to Remove from File.Delete so that the coresponding
// policy file will be deleted as well, and CurrentCacheSize will be updated.
Remove(key, regionName);
}
catch (Exception)
{
}
}
else
{
//does the item have a sliding expiration?
if (payload.Policy.SlidingExpiration > new TimeSpan())
{
payload.Policy.AbsoluteExpiration = DateTime.Now.Add(payload.Policy.SlidingExpiration);
WriteHelper(PayloadWriteMode, key, payload, regionName, true);
}
}
}
else
{
//remove null payload
Remove(key, regionName);
//create dummy one for return
payload = new FileCachePayload(null);
}
return payload.Payload;
}
public override CacheItem GetCacheItem(string key, string regionName = null)
{
object value = Get(key, regionName);
CacheItem item = new CacheItem(key);
item.Value = value;
item.RegionName = regionName;
return item;
}
public override long GetCount(string regionName = null)
{
if (regionName == null)
{
regionName = "";
}
string path = Path.Combine(CacheDir, _cacheSubFolder, regionName);
if (Directory.Exists(path))
return Directory.GetFiles(path).Count();
else
return 0;
}
///
/// Returns an enumerator for the specified region (defaults to base-level cache directory).
/// This function *WILL NOT* recursively locate files in subdirectories.
///
///
///
public IEnumerator GetEnumerator(string regionName = null)
{
string region = "";
if (string.IsNullOrEmpty(regionName) == false)
{
region = regionName;
}
//AC: This seems inefficient. Wouldn't it be better to do this using a cursor?
List enumerator = new List();
var keys = CacheManager.GetKeys(regionName);
foreach (string key in keys)
{
enumerator.Add(new KeyValuePair(key, this.Get(key, regionName)));
}
return enumerator.GetEnumerator();
}
///
/// Will return an enumerator with all cache items listed in the root file path ONLY. Use the other
/// if you want to specify a region
///
///
protected override IEnumerator GetEnumerator()
{
return GetEnumerator(null);
}
public override IDictionary GetValues(IEnumerable keys, string regionName = null)
{
Dictionary values = new Dictionary();
foreach (string key in keys)
{
values[key] = Get(key, regionName);
}
return values;
}
public override string Name
{
get { return _name; }
}
public override object Remove(string key, string regionName = null)
{
object valueToDelete = null;
if (Contains(key, regionName) == true)
{
// Because of the possibility of multiple threads accessing this, it's possible that
// while we're trying to remove something, another thread has already removed it.
try
{
//remove cache entry
// CT note: calling Get from remove leads to an infinite loop and stack overflow,
// so I replaced it with a simple CacheManager.ReadFile call. None of the code here actually
// uses this object returned, but just in case someone else's outside code does.
FileCachePayload fcp = CacheManager.ReadFile(PayloadMode.Filename, key, regionName);
valueToDelete = fcp.Payload;
string path = CacheManager.GetCachePath(key, regionName);
CurrentCacheSize -= new FileInfo(path).Length;
File.Delete(path);
//remove policy file
string cachedPolicy = CacheManager.GetPolicyPath(key, regionName);
CurrentCacheSize -= new FileInfo(cachedPolicy).Length;
File.Delete(cachedPolicy);
}
catch (IOException)
{
}
}
return valueToDelete;
}
public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
{
Add(key, value, policy, regionName);
}
public override void Set(CacheItem item, CacheItemPolicy policy)
{
Add(item, policy);
}
public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
{
Add(key, value, absoluteExpiration, regionName);
}
public override object this[string key]
{
get
{
return this.Get(key, DefaultRegion);
}
set
{
this.Set(key, value, DefaultPolicy, DefaultRegion);
}
}
#endregion
private clast LocalCacheBinder : System.Runtime.Serialization.SerializationBinder
{
public override Type BindToType(string astemblyName, string typeName)
{
Type typeToDeserialize = null;
String currentastembly = astembly.Getastembly(typeof(LocalCacheBinder)).FullName;
astemblyName = currentastembly;
// Get the type using the typeName and astemblyName
typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, astemblyName));
return typeToDeserialize;
}
}
// CT: This private clast is used to help shrink the cache.
// It computes the total size of an entry including it's policy file.
// It also implements IComparable functionality to allow for sorting based on access time
private clast CacheItemReference : IComparable
{
public readonly DateTime LastAccessTime;
public readonly long Length;
public readonly string Key;
public readonly string Region;
public CacheItemReference(string key, string region, string cachePath, string policyPath)
{
Key = key;
Region = region;
FileInfo cfi = new FileInfo(cachePath);
FileInfo pfi = new FileInfo(policyPath);
cfi.Refresh();
LastAccessTime = cfi.LastAccessTime;
Length = cfi.Length + pfi.Length;
}
public int CompareTo(CacheItemReference other)
{
int i = LastAccessTime.CompareTo(other.LastAccessTime);
// It's possible, although rare, that two different items will have
// the same LastAccessTime. So in that case, we need to check to see
// if they're actually the same.
if (i == 0)
{
// second order should be length (but from smallest to largest,
// that way we delete smaller files first)
i = -1 * Length.CompareTo(other.Length);
if (i == 0)
{
i = string.Compare(Region, other.Region);
if (i == 0)
{
i = Key.CompareTo(other.Key);
}
}
}
return i;
}
public static bool operator >(CacheItemReference lhs, CacheItemReference rhs)
{
if(lhs.CompareTo(rhs) > 0)
{
return true;
}
return false;
}
public static bool operator