Click here to Skip to main content
15,886,036 members
Articles / Programming Languages / C#

Pfz.Caching - ViewIds instead of ViewStates

Rate me:
Please Sign up or sign in to vote.
4.85/5 (13 votes)
10 May 2010CPOL8 min read 42.8K   239   25  
Framework for caching data that includes the possibility to store ViewStates in files, reutilizing identical files as an way to avoid too much HD usage
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Principal;
using System.Threading;
using Pfz.Extensions.DisposeExtensions;
using Pfz.Threading;

namespace Pfz.Caching
{
	/// <summary>
	/// The default persister used by CacheManager.
	/// </summary>
	public sealed class FileCachePersister:
		ICachePersister
	{
		/// <summary>
		/// Creates the FileCachePersister using the given directory as it's working directory.
		/// CAUTION - All files and this directory will eventually be deleted.
		/// </summary>
		public FileCachePersister(string path):
			this(path, new TimeSpan(0, 30, 0), new TimeSpan(4, 0, 0), WindowsIdentity.GetCurrent())
		{
		}
		
		/// <summary>
		/// Creates the FileCachePersister with all parameters.
		/// </summary>
		public FileCachePersister(string path, TimeSpan cleaningInterval, TimeSpan cleanFilesOlderThan, WindowsIdentity cleaningThreadImpersonation)
		{
			if (string.IsNullOrEmpty(path))
				throw new ArgumentNullException("path", "If you want to use the current directory, use a dot \".\".");
				
			fCleanFilesOlderThan = cleanFilesOlderThan;
			fCleaningInterval = cleaningInterval;
			fCleaningThreadImpersonation = cleaningThreadImpersonation;
		
			if (path[path.Length-1] != '\\')
				path += '\\';
			
			BasePath = path;
			BuffersPath = path + "Buffers\\";
			ViewStatesPath = path + "ViewStates\\";
			
			if (fCleanFilesOlderThan < fCleaningInterval)
				throw new CacheException("CleanFilesOlderThan must be at least equal to CleaningInterval");
			
			if (fCleaningThreadImpersonation == null)
				fCleaningThreadImpersonation = WindowsIdentity.GetCurrent(false);

			if (fCleaningInterval != TimeSpan.Zero)
			{
				Thread cleanerThread = new Thread(p_Clean);
				cleanerThread.Name = "Pfz.Caching.FileCachePersister.CleanerThread - cleans unused cache files";
				cleanerThread.Priority = ThreadPriority.Lowest;
				cleanerThread.IsBackground = true;
				cleanerThread.Start();
			}
		}
		
		#region Properties
			#region CleaningInterval
				private static TimeSpan fCleaningInterval;
			
				/// <summary>
				/// Gets the time in which cleaning thread executes to delete old files. This is
				/// the interval between executions, not how old files can be.
				/// The default interval is 30 minutes.
				/// If CleaningInterval is set to Zero, the automatic cleaning will
				/// never be executed. Set it to Zero only if you do your own cleaning.
				/// </summary>
				public static TimeSpan CleaningInterval
				{
					get
					{
						return fCleaningInterval;
					}
				}
			#endregion
			#region CleanFilesOlderThan
				private static TimeSpan fCleanFilesOlderThan;
			
				/// <summary>
				/// Gets or sets how old files can be and kept alive. Older will be deleted at the
				/// next execution of Cleaner Thread.
				/// The default value is 4 hours.
				/// </summary>
				public static TimeSpan CleanFilesOlderThan
				{
					get
					{
						return fCleanFilesOlderThan;
					}
				}
			#endregion
			#region CleaningThreadImpersonation
				private static WindowsIdentity fCleaningThreadImpersonation;
			
				/// <summary>
				/// Gets or sets the impersonation used by the cleaning thread.
				/// If this value is null, the impersonation of the thread that 
				/// calls Start() will be used.
				/// </summary>
				public static WindowsIdentity CleaningThreadImpersonation
				{
					get
					{
						return fCleaningThreadImpersonation;
					}
				}
			#endregion

			/// <summary>
			/// Gets the path used to create this FileCachePersister.
			/// </summary>
			public string BasePath { get; private set; }

			/// <summary>
			/// Gets the path used by this FileCachePersister to store it's cache buffers.
			/// </summary>
			public string BuffersPath { get; private set; }
			
			/// <summary>
			/// Gets the path used by this FileCachePersister to store it's viewstates.
			/// </summary>
			public string ViewStatesPath { get; private set; }
		#endregion
		
		#region Buffers
			/// <summary>
			/// Reads the buffer using the hashCode as a directory and the id as the filename.
			/// </summary>
			/// <returns>The read buffer or null.</returns>
			public byte[] GetBuffer(long hashCode, long id)
			{
				string filePath = string.Format("{0}{1}\\{2}", BuffersPath, hashCode, id);
				
				if (!File.Exists(filePath))
					return null;
				
				try
				{
					FileStream stream = null;
					try
					{
						AbortSafe.Run(()=> stream = File.OpenRead(filePath));

						int length = (int)stream.Length;
						byte[] bytes = new byte[length];
						
						if (stream.Read(bytes, 0, length) != length)
							return null;
						
						return bytes;
					}
					finally
					{
						stream.CheckedDispose();
					}
				}
				catch
				{
					return null;
				}
			}
			
			/// <summary>
			/// Saves the buffer using the hashCode as a sub-directory and the id as the file name.
			/// </summary>
			public long SaveBuffer(byte[] buffer, long hashCode)
			{
				if (buffer == null)
					throw new ArgumentNullException("buffer");
					
				int length = buffer.Length;
				long id;
				
				string directoryPath = BuffersPath + hashCode;
				DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
				if (directoryInfo.Exists)
				{
					try
					{
						FileInfo[] files = directoryInfo.GetFiles();
						byte[] otherBuffer = null;
						foreach(FileInfo file in files)
						{
							if (file.Length != length)
								continue;
							
							try
							{
								if (otherBuffer == null)
									otherBuffer = new byte[length];
								
								FileStream stream = null;
								try
								{
									AbortSafe.Run(()=>stream = file.OpenRead());
									if (stream.Read(otherBuffer, 0, length) != length)
										continue;
									
									if (buffer.SequenceEqual(otherBuffer))
									{
										id = long.Parse(file.Name);
										return id;
									}
								}
								finally
								{
									stream.CheckedDispose();
								}
							}
							catch
							{
							}
						}
					}
					catch
					{
					}
				}
				
				id = TicksOrIncrement.GetNext();
				string path = directoryPath + '\\' + id;
				Directory.CreateDirectory(directoryPath);
				AbortSafe.WriteAllBytes(path, buffer);
				return id;
			}
			
			/// <summary>
			/// Resets the file LastWriteTime.
			/// </summary>
			public bool KeepBufferAlive(long hashCode, long id)
			{
				try
				{
					string path = string.Format("{0}{1}\\{2}", BuffersPath, hashCode, id);
					if (!File.Exists(path))
						return false;
						
					File.SetLastWriteTime(path, DateTime.Now);
					return true;
				}
				catch
				{
					return false;
				}
			}

			/// <summary>
			/// Recreates a buffer that is still in memory, but was deleted.
			/// </summary>
			public void RecreateBuffer(long hashCode, long id, byte[] buffer)
			{
				string directoryPath = BuffersPath + hashCode;
				Directory.CreateDirectory(directoryPath);
				string filePath = directoryPath + '\\' + id;
				AbortSafe.WriteAllBytes(filePath, buffer);
			}
		#endregion
		#region ViewStates
			/// <summary>
			/// Gets a view state.
			/// </summary>
			public byte[] GetViewState(string sessionId, long viewId)
			{
				try
				{
					string path = string.Format("{0}{1}\\{2}", ViewStatesPath, sessionId, viewId);
					return AbortSafe.ReadAllBytes(path);
				}
				catch
				{
					return null;
				}
			}

			/// <summary>
			/// Saves a view state.
			/// </summary>
			public long SaveViewState(string sessionId, byte[] bytes)
			{
				string directoryPath = ViewStatesPath + sessionId;
				Directory.CreateDirectory(directoryPath);
				
				long viewId = TicksOrIncrement.GetNext();
				string path = string.Format("{0}\\{1}", directoryPath, viewId);
				AbortSafe.WriteAllBytes(path, bytes);
				return viewId;
			}

			/// <summary>
			/// Resets the file LastWriteTime.
			/// </summary>
			/// <param name="sessionId"></param>
			/// <param name="key"></param>
			/// <returns></returns>
			public bool KeepViewStateAlive(string sessionId, long key)
			{
				try
				{
					string path = string.Format("{0}{1}\\{2}", ViewStatesPath, sessionId, key);
					if (!File.Exists(path))
						return false;
						
					File.SetLastWriteTime(path, DateTime.Now);
					return true;
				}
				catch
				{
					return false;
				}
			}

			/// <summary>
			/// Recreates a given viewstate that is in memory, but deleted.
			/// </summary>
			/// <param name="sessionId"></param>
			/// <param name="key"></param>
			/// <param name="bytes"></param>
			public void RecreateViewState(string sessionId, long key, byte[] bytes)
			{
				string directoryPath = ViewStatesPath + sessionId;
				Directory.CreateDirectory(directoryPath);
				string filePath = directoryPath + '\\' + key;
				AbortSafe.WriteAllBytes(filePath, bytes);
			}
		#endregion
		#region Interlocked
			private Dictionary<string, long> fInterlocked = new Dictionary<string, long>();
			
			/// <summary>
			/// Gets an interlocked variable value or, if one does not exists, create
			/// it with the given value, also doing an atomic add immediatelly.
			/// </summary>
			public long InterlockedGetOrCreateAndAdd(string interlockedName, long value, int addAmount)
			{
				long result = 0;

				AbortSafe.UnabortableLock
				(
					fInterlocked,
					delegate
					{
						if (!fInterlocked.TryGetValue(interlockedName, out result))
							result = value;
						
						result += addAmount;
						fInterlocked[interlockedName] = result;
					}
				);
				
				return result;
			}

			/// <summary>
			/// Adds the given amount to an interlocked variable.
			/// Returns null if the variable does not exist.
			/// </summary>
			public long? InterlockedAdd(string interlockedName, int amount)
			{
				long? result = null;

				AbortSafe.UnabortableLock
				(
					fInterlocked,
					delegate
					{
						long longResult;
						if (!fInterlocked.TryGetValue(interlockedName, out longResult))
							return;
							
						longResult += amount;
						fInterlocked[interlockedName] = longResult;
						
						result = longResult;
					}
				);
				
				return result;
			}
		#endregion

		#region p_Clean
			private void p_Clean()
			{
				if (fCleaningThreadImpersonation != null)
					fCleaningThreadImpersonation.Impersonate();
				
				while(true)
				{
					DirectoryInfo directoryInfo = new DirectoryInfo(BasePath);
					p_Clean(directoryInfo);
					
					Thread.Sleep(fCleaningInterval);
				}
			}
			private static void p_Clean(DirectoryInfo directoryInfo)
			{
				try
				{
					DirectoryInfo[] subDirectories = directoryInfo.GetDirectories();
					foreach(DirectoryInfo subDirectory in subDirectories)
						p_Clean(subDirectory);
						
					DateTime old = DateTime.Now - fCleanFilesOlderThan;
					FileInfo[] files = directoryInfo.GetFiles();
					foreach(FileInfo file in files)
					{
						try
						{
							if (file.LastWriteTime < old)
								file.Delete();
						}
						catch(SecurityException){}
						catch(IOException){}
						catch(UnauthorizedAccessException){}
					}
					
					directoryInfo.Delete();
				}
				catch
				{
				}
			}
		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions