using System; using System.Collections; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace FlickrNet { /// /// Internal Cache class /// internal sealed class Cache { private class CacheException : Exception { public CacheException(string message) : base(message) {} } private static PersistentCache _downloads; /// /// A static object containing the list of cached downloaded files. /// public static PersistentCache Downloads { get { lock(lockObject) { if( _downloads == null ) _downloads = new PersistentCache(Path.Combine(CacheLocation, "downloadCache.dat"), new PictureCacheItemPersister(), CacheSizeLimit); return _downloads; } } } private static PersistentCache _responses; /// /// A static object containing the list of cached responses from Flickr. /// public static PersistentCache Responses { get { lock(lockObject) { if( _responses == null ) _responses = new PersistentCache(Path.Combine(CacheLocation, "responseCache.dat"), new ResponseCacheItemPersister(), CacheSizeLimit); return _responses; } } } private Cache() { } private static object lockObject = new object(); private enum Tristate { Null, True, False } private static Tristate _cacheDisabled; internal static bool CacheDisabled { get { if( _cacheDisabled == Tristate.Null && FlickrConfigurationManager.Settings != null ) _cacheDisabled = (FlickrConfigurationManager.Settings.CacheDisabled?Tristate.True:Tristate.False); if( _cacheDisabled == Tristate.Null ) _cacheDisabled = Tristate.False; return (_cacheDisabled==Tristate.True); } set { _cacheDisabled = value?Tristate.True:Tristate.False; } } private static string _cacheLocation; internal static string CacheLocation { get { if( _cacheLocation == null && FlickrConfigurationManager.Settings != null ) _cacheLocation = FlickrConfigurationManager.Settings.CacheLocation; if( _cacheLocation == null ) { try { _cacheLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FlickrNet"); } catch(System.Security.SecurityException) { // Permission to read application directory not provided. throw new CacheException("Unable to read default cache location. Please cacheLocation in configuration file or set manually in code"); } } if( _cacheLocation == null ) throw new CacheException("Unable to determine cache location. Please set cacheLocation in configuration file or set manually in code"); return _cacheLocation; } set { _cacheLocation = value; } } internal static long CacheSizeLimit { get { if( CacheSettings.ContainsKey("SizeLimit") ) return (long)CacheSettings["SizeLimit"]; else return 50 * 1024 * 1024; } set { if( CacheSettings.ContainsKey("SizeLimit") ) CacheSettings["SizeLimit"] = value; else CacheSettings.Add("SizeLimit", value); } } internal static long CacheSize { get { if( CacheSettings.ContainsKey("CurrentSize") ) return (long)CacheSettings["CurrentSize"]; else return 0; } set { if( CacheSettings.ContainsKey("CurrentSize") ) CacheSettings["CurrentSize"] = value; else CacheSettings.Add("CurrentSize", value); } } // Default cache timeout is 1 hour private static TimeSpan _cachetimeout = new TimeSpan(0, 1, 0, 0, 0); /// /// The default timeout for cachable objects within the cache. /// public static TimeSpan CacheTimeout { get { return _cachetimeout; } set { _cachetimeout = value; } } private static Hashtable _cacheSettings; private static Hashtable CacheSettings { get { lock(lockObject) { if( _cacheSettings == null ) LoadSettings(); return _cacheSettings; } } } private static void SaveSettings() { System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); System.IO.Stream stream; lock(CacheSettings.SyncRoot) { if( CacheLocation == null ) return; stream = new FileStream(Path.Combine(CacheLocation, "cacheSettings.bin"), FileMode.OpenOrCreate, FileAccess.Write); try { formatter.Serialize(stream, CacheSettings); } finally { stream.Close(); } } } private static void LoadSettings() { if( !Directory.Exists(CacheLocation) ) Directory.CreateDirectory(CacheLocation); System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); System.IO.Stream stream = null; if( CacheLocation != null ) { stream = new FileStream(Path.Combine(CacheLocation, "cacheSettings.bin"), FileMode.OpenOrCreate); } if( stream == null ) { _cacheSettings = new Hashtable(); return; } try { _cacheSettings = (Hashtable)formatter.Deserialize(stream); } catch(InvalidCastException) { _cacheSettings = new Hashtable(); } catch(System.Runtime.Serialization.SerializationException) { _cacheSettings = new Hashtable(); } finally { stream.Close(); } } internal static void FlushCache(string url) { Responses[url] = null; Downloads[url] = null; } internal static void FlushCache() { Responses.Flush(); Downloads.Flush(); } } /// /// A cache item containing details of a REST response from Flickr. /// [Serializable] public class ResponseCacheItem : ICacheItem { private string url; private string response; private DateTime creationTime; /// /// Gets or sets the original URL of the request. /// public string Url { get { return url; } set { url = value; } } /// /// Gets or sets the XML response. /// public string Response { get { return response; } set { response = value; } } /// /// Gets or sets the time the cache item was created. /// public DateTime CreationTime { get { return creationTime; } set { creationTime = value; } } /// /// Gets the filesize of the request. /// public long FileSize { get { return (response==null?0:response.Length); } } void ICacheItem.OnItemFlushed() { } } internal class ResponseCacheItemPersister : CacheItemPersister { public override ICacheItem Read(Stream inputStream) { string s = Utils.ReadString(inputStream); string response = Utils.ReadString(inputStream); string[] chunks = s.Split('\n'); // Corrupted cache record, so throw IOException which is then handled and returns partial cache. if( chunks.Length != 2 ) throw new IOException("Unexpected number of chunks found"); string url = chunks[0]; DateTime creationTime = new DateTime(long.Parse(chunks[1])); ResponseCacheItem item = new ResponseCacheItem(); item.Url = url; item.CreationTime = creationTime; item.Response = response; return item; } public override void Write(Stream outputStream, ICacheItem cacheItem) { ResponseCacheItem item = (ResponseCacheItem) cacheItem; StringBuilder result = new StringBuilder(); result.Append(item.Url + "\n"); result.Append(item.CreationTime.Ticks.ToString("0")); Utils.WriteString(outputStream, result.ToString()); Utils.WriteString(outputStream, item.Response); } } /// /// An item that can be stored in a cache. /// public interface ICacheItem { /// /// The time this cache item was created. /// DateTime CreationTime { get; } /// /// Gets called back when the item gets flushed /// from the cache. /// void OnItemFlushed(); /// /// The size of this item, in bytes. Return 0 /// if size management is not important. /// long FileSize { get; } } /// /// An interface that knows how to read/write subclasses /// of ICacheItem. Obviously there will be a tight /// coupling between concrete implementations of ICacheItem /// and concrete implementations of ICacheItemPersister. /// public abstract class CacheItemPersister { /// /// Read a single cache item from the input stream. /// public abstract ICacheItem Read(Stream inputStream); /// /// Write a single cache item to the output stream. /// public abstract void Write(Stream outputStream, ICacheItem cacheItem); } /// /// Contains details of image held with the Flickr.Net cache. /// [Serializable] public class PictureCacheItem : ICacheItem { #region [ Internal Variables ] internal string url; internal DateTime creationTime; internal string filename; internal long fileSize; #endregion #region [ Public Properties ] /// /// The URL of the original image on Flickr. /// public string Url { get { return url; } } /// /// The that the cache item was created. /// public DateTime CreationTime { get { return creationTime; } } /// /// The filesize in bytes of the image. /// public long FileSize { get { return fileSize; } } /// /// The Flickr photo id of the image. /// public string PhotoId { get { if( url == null ) return null; else { int begin = url.LastIndexOf("/"); int end = url.IndexOf("_"); return url.Substring(begin + 1, (end - begin) - 1); } } } #endregion #region [ Public Methods ] void ICacheItem.OnItemFlushed() { File.Delete(filename); } #endregion } /// /// Persists PictureCacheItem objects. /// internal class PictureCacheItemPersister : CacheItemPersister { public override ICacheItem Read(Stream inputStream) { string s = Utils.ReadString(inputStream); string[] chunks = s.Split('\n'); string url = chunks[0]; DateTime creationTime = new DateTime(long.Parse(chunks[1])); string filename = chunks[2]; long fileSize = long.Parse(chunks[3]); PictureCacheItem pci = new PictureCacheItem(); pci.url = url; pci.creationTime = creationTime; pci.filename = filename; pci.fileSize = fileSize; return pci; } public override void Write(Stream outputStream, ICacheItem cacheItem) { PictureCacheItem pci = (PictureCacheItem) cacheItem; StringBuilder output = new StringBuilder(); output.Append(pci.url + "\n"); output.Append(pci.creationTime.Ticks + "\n"); output.Append(pci.filename + "\n"); output.Append(pci.fileSize + "\n"); Utils.WriteString(outputStream, output.ToString()); } } }