Main Page | Class Hierarchy | Class List | File List | Class Members

Cache.cs

00001 using System;
00002 using System.IO;
00003 using System.Text;
00004 using System.Collections;
00005 using System.Collections.Specialized;
00006 using System.Security.Cryptography;
00007 using System.Runtime.Serialization;
00008 using System.Runtime.Serialization.Formatters.Binary;
00009 
00010 
00011 namespace Common {
00012         
00013         [Serializable]
00014         public class CacheIndex : ListDictionary {
00015                 private readonly string BASE_PATH;
00016                 private readonly string EXT;
00017                 private long maxCacheSize;
00018 
00019                 public CacheIndex(string basePath, string ext, long maxCacheSize) {
00020                         this.BASE_PATH = basePath;
00021                         this.EXT = ext;
00022                         this.maxCacheSize = maxCacheSize;
00023                         UpdateFromDisk();       
00024                 }
00025 
00026                 public static CacheIndex Deserialise(byte[] srcCompressed) {
00027                         MemoryStream compressedStream = new MemoryStream(srcCompressed);
00028                         byte[] srcUncompressed = EncodedData.StaticUnzip(ref compressedStream);
00029                         compressedStream.Close();
00030                         IFormatter formatter = new BinaryFormatter(); 
00031                         return (CacheIndex)formatter.Deserialize(new MemoryStream(srcUncompressed));
00032                 }
00033 
00034                 public byte[] Serialise() {
00035                         IFormatter formatter = new BinaryFormatter();
00036                         MemoryStream bufferStream = new MemoryStream();
00037                         formatter.Serialize(bufferStream, this);
00038                         byte[] result = EncodedData.StaticZip(bufferStream);
00039                         bufferStream.Close();
00040                         return result;
00041                 }
00042 
00043                 private void UpdateFromDisk() {
00044                         lock (SyncRoot) {
00045                                 Console.Write("Populating Cache Index... ");
00046                                 string[] searchResult = Directory.GetFiles(BASE_PATH, "*" + EXT);
00047                                 foreach(string filename in searchResult) {
00048                                         FileStream fs = File.OpenRead(filename);
00049                                         CacheIndexEntry cie = new CacheIndexEntry(Cache.ChkFromString(Path.GetFileNameWithoutExtension(filename)), Path.GetFileNameWithoutExtension(filename), (int)fs.Length, CacheEntry.DeserialiseDate(fs));
00050                                         fs.Close();
00051                                         this.Add(cie);
00052                                 }
00053                                 Console.WriteLine("Done.");
00054                         }
00055                 }
00056                 public CacheIndexEntry this[byte[] chk] {
00057                         get { 
00058                                 lock(SyncRoot) {
00059                                         return (CacheIndexEntry)base[Cache.StringFromChk(chk)]; 
00060                                 }
00061                         }
00062                         set { 
00063                                 lock(SyncRoot) {
00064                                         base[Cache.StringFromChk(chk)] = value; 
00065                                 }
00066                         }
00067                 }
00068 
00069                 public bool Contains(byte[] chk){
00070                         lock (SyncRoot) {
00071                                 return this.Contains(Cache.StringFromChk(chk));
00072                         }
00073                 }
00074 
00075                 public void Remove(byte[] chk) {
00076                         lock (SyncRoot) {
00077                                 base.Remove(Cache.StringFromChk(chk));
00078                         }
00079                 }
00080 
00081                 public void Add(CacheIndexEntry cie) {
00082                         lock (SyncRoot) {
00083                                 this.Add(Cache.StringFromChk(cie.chk), cie);
00084                         }
00085                 }
00086 
00093                 public CacheIndexEntry LeastRecentlyUsed() {
00094                         lock (SyncRoot) {
00095                                 CacheIndexEntry evictionCandidate = null;
00096                                 if (this.Count != 0) {
00097                                         DateTime minLRU = DateTime.MaxValue;
00098                                         foreach(object o in this) {
00099                                                 CacheIndexEntry cie = (CacheIndexEntry)((DictionaryEntry)o).Value;
00100                                                 if (cie.lastUsed <= minLRU) {
00101                                                         minLRU = cie.lastUsed;
00102                                                         evictionCandidate = cie;
00103                                                 }
00104                                         }
00105                                 }
00106                                 return evictionCandidate;
00107                         }
00108                 }
00109 
00110                 public long GetSpaceRemaining() {
00111                         lock (SyncRoot) {
00112                                 long totalSize = 0;
00113                                 foreach(object o in this.Values) {
00114                                         CacheIndexEntry cie = (CacheIndexEntry)o;
00115                                         totalSize += cie.size;
00116                                 }
00117                                 return maxCacheSize - totalSize;
00118                         }
00119                 }
00120         }
00121 
00122         /*************************************************************************/
00123         [Serializable]
00124         public class CacheIndexEntry {
00125                 public byte[] chk;
00126                 public string filename;
00127                 public int size;
00128                 public DateTime lastUsed;
00129 
00130                 public CacheIndexEntry(byte[] chk, string filename, int size, DateTime lastUsed) {
00131                         this.chk = chk;
00132                         this.filename = filename;
00133                         this.size = size;
00134                         this.lastUsed = lastUsed;
00135                 }
00136         }
00137 
00138         /*************************************************************************/
00139         public class Cache {
00140                 CommonSettings settings;  
00141                 readonly string BASE_PATH;  
00142                 readonly string EXT;
00143                 
00144                 SHA1Managed sha1;
00145                 CacheIndex index;
00146                 Object SyncRoot = new Object();
00147                 long maxSize;
00148                 long spaceUsed;
00149                 internal CacheManager cacheManager;
00150 
00151                 public Cache(long maxSize, CacheManager cacheManager) {
00152                         this.cacheManager = cacheManager;
00153                         this.maxSize = maxSize;
00154                         settings = new CommonSettings();
00155                         BASE_PATH = settings.CacheLocation;
00156                         EXT = settings.CacheFileExtension;
00157                         sha1 = new SHA1Managed();
00158                         index = new CacheIndex(BASE_PATH, EXT, maxSize);
00159                         spaceUsed = CheckSpaceUsed();
00160                 }
00161 
00166                 long CheckSpaceUsed() {
00167                         lock (SyncRoot) {
00168                                 DirectoryInfo dirInfo = new DirectoryInfo(BASE_PATH);
00169                                 long totalSize = 0;
00170                                 FileSystemInfo[] fiArr = dirInfo.GetFileSystemInfos("*" + EXT);
00171                                 foreach (FileInfo fi in fiArr) {
00172                                         totalSize += fi.Length;
00173                                 }
00174                                 Console.WriteLine("Cache files take up {0} bytes on disk.", totalSize);
00175                                 return totalSize;
00176                         }
00177                 }
00178 
00184                 public byte[] Put(HTTPBody body) {
00185                         lock (SyncRoot) {
00186                                 byte[] chk = null; 
00187                                 if (body != null) {
00188                                         chk = sha1.ComputeHash(body.data);
00189                                         /* check to see if CHK already exists:
00190                                          *  1) Check index. If match, double check file existance(if no exist,
00191                                          *  delete from index), then return. Error?
00192                                          *  2) check filesystem itself. If match, add it to the index, return. Error? */
00193                                         string prefix = "";
00194                                         bool alreadyExists = false;
00195                                         if (index.Contains(chk)) {
00196                                                 CacheIndexEntry currentEntry = index[chk];
00197                                                 prefix = currentEntry.filename;
00198                                                 if (File.Exists(BASE_PATH + currentEntry.filename + EXT)) { // all well and good.
00199                                                         alreadyExists = true;
00200                                                 } else {
00201                                                         index.Remove(chk); // since it didn't actually exist in the first place.
00202                                                 }
00203                                         } else { // TODO: Tidy this up
00204                                                 prefix = Cache.StringFromChk(chk);
00205                                                 if (File.Exists(BASE_PATH + prefix + EXT)) { // all well and good.
00206                                                         alreadyExists = true;
00207                                                 }
00208                                         }
00209                                         if (!alreadyExists) {
00210                                                 // create the cache entry
00211                                                 CacheEntry ce = new CacheEntry(body);
00212                                                 byte[] buffer = ce.Serialise();
00213                                                 if (buffer.Length + spaceUsed > maxSize) {
00214                                                         Purge(maxSize - buffer.Length); // purge cache until at most maxSize - bufferLength remains (ie enough for buffer length
00215                                                 }
00216                                                 // create the index entry (CHK, Filename, Size, LastUsed)
00217                                                 CacheIndexEntry cie = new CacheIndexEntry(chk, prefix, buffer.Length, DateTime.Now);
00218                                                 // create the file
00219                                                 FileStream newEntry = File.Create(BASE_PATH + prefix + EXT);
00220                                                 // write the serialised cache entry             
00221                                                 newEntry.Write(buffer, 0, buffer.Length);
00222                                                 // close everything that needs to be closed                                     
00223                                                 newEntry.Close();
00224                                                 // write to the index
00225                                                 index.Add(cie);
00226                                                 spaceUsed += cie.size;
00227                                         } 
00228                                 } // end if body.data != null;
00229                                 return chk;
00230                         } // unlockl
00231                 }
00232 
00238                 public HTTPBody Get(byte[] chk) {
00239                         lock (SyncRoot) {
00240                                 HTTPBody result;
00241                                 CacheIndexEntry indexEntry = index[chk];
00242                                 FileStream fs;
00243                                 try {
00244                                         fs = File.Open(BASE_PATH + indexEntry.filename + EXT, FileMode.Open, FileAccess.ReadWrite);
00245                                 } catch (Exception ex) {
00246                                         throw new CacheNotFoundException(String.Format("Unable to find {0}", Cache.StringFromChk(chk)), ex);
00247                                 }
00248                                 // reads entry
00249                                 /* As part of new 1.1 eviction policy, we don't want to update LRU on read, only on write
00250                                  * Thus LRU becomes more like a FIFO. Except it's still called LRU because I am too lazy
00251                                  * to rename variables
00252                                  * */
00253                                 CacheEntry cacheEntry = new CacheEntry(fs);
00254                                 result = cacheEntry.data;
00255 //                              cacheEntry.lastUsed = DateTime.Now;
00256 //                              fs.Seek(0, SeekOrigin.Begin);
00257 //                              byte[] dateBuffer = cacheEntry.SerialiseDate();
00258 //                              fs.Write(dateBuffer, 0, dateBuffer.Length);
00259                                 fs.Close();
00260 //                              indexEntry.lastUsed = cacheEntry.lastUsed;
00261 //                              index[chk] = indexEntry;
00262                                 return result;
00263                         }
00264                 }
00265 
00270                 public void Delete(byte[] chk) {
00271                         lock (SyncRoot) {
00272                                 CacheIndexEntry indexEntry = index[chk];
00273                                 if (File.Exists(BASE_PATH + indexEntry.filename + EXT)) {
00274                                         File.Delete(BASE_PATH + indexEntry.filename + EXT);
00275                                 }
00276                                 index.Remove(chk);
00277                                 spaceUsed -= indexEntry.size;
00278                         }
00279                 }
00280 
00285                 public void Purge(long targetUsage) {
00286                         lock (SyncRoot) {
00287                                 long bytesRemaining = spaceUsed - targetUsage;
00288                                 while (bytesRemaining > 0) {
00289                                         byte[] evictionTargetCHK = index.LeastRecentlyUsed().chk;
00290                                         Delete(evictionTargetCHK);
00291                                         cacheManager.uriMapper.RemoveAssociatedUris(evictionTargetCHK);
00292                                         bytesRemaining = spaceUsed - targetUsage;
00293                                 }
00294                         }
00295                 }
00296 
00297                 public bool Contains(byte[] chk) {
00298                         lock (SyncRoot) {
00299                                 return File.Exists(BASE_PATH + Cache.StringFromChk(chk) + EXT);
00300                         }
00301                 }
00306                 public void Delete(string chk) {
00307                         Delete(Cache.ChkFromString(chk));
00308                 }
00309 
00315                 public static string StringFromChk(byte[] chk) {
00316                         string result = "";
00317                         foreach (byte b in chk) {
00318                                 result += getHex(b);
00319                         }
00320                         return result;
00321                 }
00322 
00328                 public static string getHex(byte src) {
00329                         return src.ToString("X2");
00330                 }
00331 
00337                 public static byte[] ChkFromString(string src) {
00338                         byte[] result = new byte[20];
00339                         if (src.Length == 40) {
00340                                 string chunk;
00341                                 for (int i = 0; i < src.Length; i+=2) {
00342                                         chunk = src.Substring(i, 2);
00343                                         result[i/2] = Byte.Parse(chunk, System.Globalization.NumberStyles.HexNumber);
00344                                 }
00345                         } else {
00346                                 throw new ApplicationException("Expected string of length 40. Got length " + src.Length);
00347                         }
00348                         return result;
00349                 }
00350 
00355                 public CacheIndex GetCacheIndex() {
00356                         return index;
00357                 }
00358         }
00359 
00360         /*************************************************************************/
00361         public class CacheEntry {
00362                 public HTTPBody data;
00363                 public DateTime lastUsed;
00364 
00365                 public CacheEntry(HTTPBody data) {
00366                         this.data = data;
00367                         this.lastUsed = DateTime.MinValue; 
00368                 }
00369 
00370                 public CacheEntry(FileStream fs) {
00371                         byte[] buffer = new byte[fs.Length];
00372                         fs.Read(buffer, 0, (int)fs.Length);
00373                         Deserialise(buffer);
00374                 }
00375 
00376                 public byte[] Serialise() {
00377                         byte[] lastUsedBuffer = BitConverter.GetBytes(lastUsed.Ticks);
00378                         byte[] buffer = new byte[lastUsedBuffer.Length + data.data.Length];
00379                         Array.Copy(lastUsedBuffer, 0, buffer, 0, lastUsedBuffer.Length);
00380                         Array.Copy(data.data, 0, buffer, lastUsedBuffer.Length, data.data.Length);
00381                         return buffer;
00382                 }
00383 
00384                 public void Deserialise(byte[] src) {
00385                         int dateTimeLength = BitConverter.GetBytes(DateTime.Now.Ticks).Length;
00386                         byte[] bodyBuffer = new byte[src.Length - dateTimeLength];
00387                         Array.Copy(src, dateTimeLength, bodyBuffer, 0, src.Length - dateTimeLength);
00388                         
00389                         this.data = new HTTPBody(bodyBuffer);
00390                         this.lastUsed = new DateTime(BitConverter.ToInt64(src, 0));
00391                 }
00392 
00393                 public static DateTime DeserialiseDate(FileStream fs) {
00394                         byte[] dtBuffer = new byte[8]; // a long/date is 8 bytes (64 bits)
00395                         fs.Seek(0, SeekOrigin.Begin);
00396                         fs.Read(dtBuffer, 0, dtBuffer.Length);
00397                         return new DateTime(BitConverter.ToInt64(dtBuffer, 0));
00398                 }
00399 
00400                 public byte[] SerialiseDate() {
00401                         return BitConverter.GetBytes(lastUsed.Ticks);
00402                 }
00403 
00404                 public static int SizeOfSerialised(HTTPBody data) {
00405                         return (BitConverter.GetBytes(DateTime.Now.Ticks).Length + data.data.Length);
00406                 }
00407                 
00408         }
00409         
00410         
00411         /*************************************************************************/
00412         public class URIMapper : Hashtable {
00413                 public URIMapper() : base() {}
00414 
00415                 public void Add(Uri key, URIMappingEntry data) {
00416                         lock (this) {
00417                                 base.Add(key, data);
00418                         }
00419                 }
00420 
00421                 public URIMappingEntry this[Uri key] {
00422                         get { return (URIMappingEntry)base[key];}
00423                         set { lock (this){
00424                                           base[key] = value; 
00425                                   }
00426                         }
00427                 }
00428 
00429                 public void Remove(Uri key) {
00430                         lock (this) {
00431                                 base.Remove(key);
00432                         }
00433                 }
00434 
00439                 public void RemoveAssociatedUris(byte[] chk) {
00440                         ArrayList killList = new ArrayList();
00441                         lock (this) {
00442                                 foreach (DictionaryEntry de in this) {
00443                                         if (Cache.StringFromChk(((URIMappingEntry)de.Value).CHK) == Cache.StringFromChk(chk)) {
00444                                                 killList.Add(de.Key);
00445                                         }
00446                                 }
00447                                 foreach (object o in killList) {
00448                                         this.Remove((Uri)o);
00449                                 }
00450                         }
00451                 }
00452 
00453                 public bool IsFresh(Uri key) { // TODO: Some more complex testing for freshness
00454                         return !this[key].HasExpired;
00455                 }
00456 
00457                 public bool Contains(Uri key) {
00458                         return base.Contains(key);
00459                 }
00460         }
00461         /*************************************************************************/
00462         public class URIMappingEntry {
00463                 Uri key;
00464                 byte[] chk;
00465                 DateTime creationStamp;
00466                 string headers;
00467 
00468                 public URIMappingEntry(Uri key, byte[] chk, string headers) { // is timestamp expiry date, or entry date? Make it entry...
00469                         this.key = key;
00470                         this.chk = chk;
00471                         this.headers = headers;
00472                         creationStamp = DateTime.Now;
00473                 }
00474 
00475                 public bool HasExpired {
00476                         get { return (DateTime.Now - creationStamp) > TimeSpan.FromMinutes(15); } // all mappings expire after 5 mins.
00477                 }
00478 
00479                 public byte[] CHK {
00480                         get { return chk; }
00481                 }
00482 
00483                 public string Headers {
00484                         get { return headers; }
00485                 }
00486 
00487                 public void Freshen(byte[] newChk, string newHeaders) {
00488                         creationStamp = DateTime.Now;
00489                         if (headers != newHeaders) {
00490                                 headers = newHeaders;
00491                         } 
00492                         if (chk != null && newChk != null && Cache.StringFromChk(chk) != Cache.StringFromChk(newChk)) {
00493                                 chk = newChk;
00494                         }
00495                 }
00496         }
00497         /*************************************************************************/
00498         public class CacheNotFoundException : ApplicationException {
00499                 public CacheNotFoundException(string message, Exception innerException) : base (message, innerException) {
00500                 }
00501                 public CacheNotFoundException(string message) : base (message) {
00502                 }
00503                 public CacheNotFoundException(): base() {
00504                 }
00505         }
00506 }

Generated on Mon May 8 22:07:27 2006 by  doxygen 1.3.9.1