Logo Search packages:      
Sourcecode: f-spot version File versions

PersistentCache.cs

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
//using System.Runtime.Serialization.Formatters.Binary;

namespace FlickrNet
{
      /// <summary>
      /// A threadsafe cache that is backed by disk storage.
      ///
      /// All public methods that read or write state must be
      /// protected by the lockFile.  Private methods should
      /// not acquire the lockFile as it is not reentrant.
      /// </summary>
00016       internal sealed class PersistentCache
      {
            // The in-memory representation of the cache.
            // Use SortedList instead of Hashtable only to maintain backward
            // compatibility with previous serialization scheme.  If we
            // abandon backward compatibility, we should switch to Hashtable.
            private Hashtable dataTable = new Hashtable();

            private readonly CacheItemPersister persister;

            // true if dataTable contains changes vs. on-disk representation
            private bool dirty;

            // The persistent file representation of the cache.
            private readonly FileInfo dataFile;
            private DateTime timestamp;  // last-modified time of dataFile when cache data was last known to be in sync
            private long length;         // length of dataFile when cache data was last known to be in sync
            private long maxSize;

            // The file-based mutex.  Named (dataFile.FullName + ".lock")
            private readonly LockFile lockFile;

            public PersistentCache(string filename, CacheItemPersister persister, long maxSize)
            {
                  this.maxSize = maxSize;
                  this.persister = persister;
                  this.dataFile = new FileInfo(filename);
                  this.lockFile = new LockFile(filename + ".lock");
            }

            /// <summary>
            /// Return all items in the cache.  Works similarly to
            /// ArrayList.ToArray(Type).
            /// </summary>
00050             public ICacheItem[] ToArray(Type valueType)
            {
                  using (lockFile.Acquire())
                  {
                        Refresh();
                        string[] keys;
                        Array values;
                        InternalGetAll(valueType, out keys, out values);
                        return (ICacheItem[]) values;
                  }
            }

            public int Count
            {
                  get
                  {
                        using (lockFile.Acquire())
                        {
                              Refresh();
                              return dataTable.Count;
                        }
                  }
            }

            /// <summary>
            /// Gets the maximum size for the persistent cache.
            /// </summary>
            public long MaxSize
00078             {
                  get { return maxSize; }
            }

            /// <summary>
            /// Gets or sets cache values.
            /// </summary>
            public ICacheItem this[string key]
00086             {
                  get
                  {
                        if (key == null)
                              throw new ArgumentNullException("key");

                        using (lockFile.Acquire())
                        {
                              Refresh();
                              return InternalGet(key);
                        }
                  }
                  set
                  {
                        if (key == null)
                              throw new ArgumentNullException("key");

                        ICacheItem oldItem;

                        using (lockFile.Acquire())
                        {
                              Refresh();
                              oldItem = InternalSet(key, value);
                              Persist();
                        }
                        if (oldItem != null)
                              oldItem.OnItemFlushed();
                  }
            }

            public ICacheItem Get(string key, TimeSpan maxAge, bool removeIfExpired)
            {
                  Debug.Assert(maxAge > TimeSpan.Zero || maxAge == TimeSpan.MinValue, "maxAge should be positive, not negative");

                  ICacheItem item;
                  bool expired;
                  using (lockFile.Acquire())
                  {
                        Refresh();

                        item = InternalGet(key);
                        expired = item != null && Expired(item.CreationTime, maxAge);
                        if (expired)
                        {
                              if (removeIfExpired)
                              {
                                    item = RemoveKey(key);
                                    Persist();
                              }
                              else
                                    item = null;
                        }
                  }

                  if (expired && removeIfExpired)
                        item.OnItemFlushed();

                  return expired ? null : item;
            }

            public void Flush()
            {
                  Shrink(0);
            }

            public void Shrink(long size)
            {
                  if (size < 0)
                        throw new ArgumentException("Cannot shrink to a negative size", "size");

                  ArrayList flushed = new ArrayList();

                  using (lockFile.Acquire())
                  {
                        Refresh();

                        string[] keys;
                        Array values;
                        InternalGetAll(typeof(ICacheItem), out keys, out values);
                        long totalSize = 0;
                        foreach (ICacheItem cacheItem in values)
                              totalSize += cacheItem.FileSize;
                        for (int i = 0; i < keys.Length; i++)
                        {
                              if (totalSize <= size)
                                    break;
                              ICacheItem cacheItem = (ICacheItem) values.GetValue(i);
                              totalSize -= cacheItem.FileSize;
                              flushed.Add(RemoveKey(keys[i]));
                        }

                        Persist();
                  }

                  foreach (ICacheItem flushedItem in flushed)
                  {
                        Debug.Assert(flushedItem != null, "Flushed item was null--programmer error");
                        if (flushedItem != null)
                              flushedItem.OnItemFlushed();
                  }
            }

            private bool Expired(DateTime test, TimeSpan age)
            {
                  if (age == TimeSpan.MinValue)
                        return true;
                  else if (age == TimeSpan.MaxValue)
                        return false;
                  else
                        return test < DateTime.UtcNow - age;
            }


            private void InternalGetAll(Type valueType, out string[] keys, out Array values)
            {
                  if (!typeof(ICacheItem).IsAssignableFrom(valueType))
                        throw new ArgumentException("Type " + valueType.FullName + " does not implement ICacheItem", "valueType");

                  keys = (string[]) new ArrayList(dataTable.Keys).ToArray(typeof(string));
                  values = Array.CreateInstance(valueType, keys.Length);
                  for (int i = 0; i < keys.Length; i++)
                        values.SetValue(dataTable[keys[i]], i);

                  Array.Sort(values, keys, new CreationTimeComparer());
            }

            private ICacheItem InternalGet(string key)
            {
                  if (key == null)
                        throw new ArgumentNullException("key");

                  return (ICacheItem) dataTable[key];
            }

            /// <returns>The old value associated with <c>key</c>, if any.</returns>
00221             private ICacheItem InternalSet(string key, ICacheItem value)
            {
                  if (key == null)
                        throw new ArgumentNullException("key");

                  ICacheItem flushedItem;

                  flushedItem = RemoveKey(key);
                  if (value != null)  // don't ever let nulls get in
                        dataTable[key] = value;

                  dirty = dirty || !object.ReferenceEquals(flushedItem, value);

                  return flushedItem;
            }

            private ICacheItem RemoveKey(string key)
            {
                  ICacheItem cacheItem = (ICacheItem) dataTable[key];
                  if (cacheItem != null)
                  {
                        dataTable.Remove(key);
                        dirty = true;
                  }
                  return cacheItem;
            }

            private void Refresh()
            {
                  Debug.Assert(!dirty, "Refreshing even though cache is dirty");

                  DateTime newTimestamp = DateTime.MinValue;
                  long newLength = -1;
                  if (dataFile.Exists)
                  {
                        dataFile.Refresh();
                        newTimestamp = dataFile.LastWriteTime;
                        newLength = dataFile.Length;
                  }

                  if (timestamp != newTimestamp || length != newLength)
                  {
                        // file changed
                        if (!dataFile.Exists)
                              dataTable.Clear();
                        else
                        {
                              Debug.WriteLine("Loading cache from disk");
                              using (FileStream inStream = dataFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
                              {
                                    dataTable = Load(inStream);
                              }
                        }
                  }

                  timestamp = newTimestamp;
                  length = newLength;
                  dirty = false;
            }

            private void Persist()
            {
                  if (!dirty)
                        return;

                  Debug.WriteLine("Saving cache to disk");
                  using (FileStream outStream = dataFile.Open(FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
                  {
                        Store(outStream, dataTable);
                  }

                  dataFile.Refresh();
                  timestamp = dataFile.LastWriteTime;
                  length = dataFile.Length;

                  dirty = false;
            }

            private Hashtable Load(Stream s)
            {
                  Hashtable table = new Hashtable();
                  int itemCount = Utils.ReadInt32(s);
                  for (int i = 0; i < itemCount; i++)
                  {
                        try
                        {
                              string key = Utils.ReadString(s);
                              ICacheItem val = persister.Read(s);
                              if( val == null ) // corrupt cache file
                                    return table;

                              table[key] = val;
                        }
                        catch(IOException)
                        {
                              return table;
                        }
                  }
                  return table;
            }

            private void Store(Stream s, Hashtable table)
            {
                  Utils.WriteInt32(s, table.Count);
                  foreach (DictionaryEntry entry in table)
                  {
                        Utils.WriteString(s, (string) entry.Key);
                        persister.Write(s, (ICacheItem) entry.Value);
                  }
            }

            private class CreationTimeComparer : IComparer
            {
                  public int Compare(object x, object y)
                  {
                        return ((ICacheItem)x).CreationTime.CompareTo(((ICacheItem)y).CreationTime);
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index