Click here to Skip to main content
15,891,372 members
Articles / Programming Languages / C#

Class Library to Automatically Maintain Persistent Variables, Properties and Data Between Sessions

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
11 Jan 2008CPOL15 min read 44.1K   591   43  
A class library that maintains persistent variables, properties and data between sessions in XML files. It supports default values for new variables and events when the values in the XML file are changed
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Xml;
using System.IO;

// Part of RJConfig V1.3

// Class for the FileConfig. This class contains all FileSections, FileItems and FileVariables for one
// file.
//
// FileConfig.cs, FileVariable.cs, FileItem.cs, FileSection.cs contains classes to read and write 
// data to an XML file as named variables with a value. The variables are ordered in Sections which
// contains Items which in turn contains the named Variables and Values. One file can contain several
// Sections where each Section must have a unique name. The name for the Items in the Sections and
// Variables in the Items does not need to be unique though.
//
// The FileConfig classes reads the XML file and create linked lists and dictionaries for the
// Variables, Items and Sections, making them easily accessible to other classes. The FileConfig
// classes has functions to modify, add and remove Variables, Items and Sections, which then can
// be written back to the XML file.
//
// The FileConfig classes also contains mechanisms to automatically reload the variables and raise
// events when the XML file is changed from an external source.
//
// Use the FileConfig classes to save data that where the Variables themselves or how many of them are
// not known at compile time. This could for example be dynamic lists for List or Combo boxes, changed
// at run time or lists of objects representing entities handled by the application (employes for example).
// When handling objects, an entire object could be converted to a single string value or one object
// could be represented by an Item where the Variables are members.
//
// If, on the other hand, the Items and Variables are known at runtime, for properties or options for
// an application, for example, then use the Config classes instead.
//
// The class hierarchy for the FileConfig classes:
//
//	FileVariable
//
//	LinkedList<FileVariable>
//		FileVariableList
//			FileItem
//
//	LinkedList<FileItem>
//		FileItemList
//			FileSection
//
//	Dictionary<Section name, FileSection>
//		FileSectionDict
//			FileConfig
//
//	FileConfigEventArgs
//
//	FileChanged delegate
//		OnFileChanged event			

namespace RJConfig
{
	/// <summary>
	/// Signature for FileChanged event raised when the config file is changed from outside the 
	/// RJFileConfig class or from another instance of the RJFileConfig class that is associated
	/// with the same file.
	/// </summary>
	/// <param name="sender">The RJFileConfig object that has raised the event.</param>
	/// <param name="e">Event arguments.</param>
	public delegate void FileChanged(object sender,FileConfigEventArgs e);
	/// <remarks>
	/// The RJFileConfig object which is based on the FileSectionDict and thus contains all FileSections, 
	/// FileItems and FileVariables for this config file.
	/// </remarks>
	public class FileConfig : FileSectionDict {
		/// <summary>
		/// The internal protected member for the file name which this FileConfig is associated with.
		/// </summary>
		protected string mFileName;
		/// <summary>
		/// The FileSystemWatcher object used to get notifications for change on the associated
		/// XML file.
		/// <seealso cref="FileChangeWatcherEnabled"/>
		/// <seealso cref="OnFileChanged"/>
		/// </summary>
		protected FileSystemWatcher fsw=null;
		/// <summary>
		/// The event that will be raised when the associated XML file has been changed by someone
		/// else than this RJFileConfig object.
		/// </summary>
		public event FileChanged OnFileChanged;
		/// <summary>
		/// Public read only property for the file name which this FileConfig is associated with.
		/// </summary>
		public string FileName
		{
			get
			{
				return mFileName;
			}
		}
		/// <summary>
		/// Public constructor.
		/// </summary>
		/// <param name="FileName">The name of the file for which this FileConfig is to ba associated to.
		/// This can be an existing file or not. If the file isn't existing, there will be no FileSection,
		/// FileItems or FileVariables created for it. If the FileConfig is associated with a Config 
		/// object, new Varaibles will be created with default values. These can then be written back
		/// to the file or not.</param>
		public FileConfig(string FileName)
		{
			mFileName = FileName;
		}
		/// <summary>
		/// Save the entire contents of this FileConfig object to the associated XML file.
		/// </summary>
		public void SaveToFile()
		{
			bool FileNotification = FileChangeWatcherEnabled;
			FileChangeWatcherEnabled = false;	// Make sure file change notification events aren't raised by 
												// writing from self.
			XmlDocument xd = new XmlDocument();
			ToXmlDoc(xd);
			XmlWriter xmlw = null;
			XmlWriterSettings xws = new XmlWriterSettings();
			xws.Indent = true;
			xws.IndentChars = "\t";
			xws.OmitXmlDeclaration = false;
			try {
				xmlw = XmlWriter.Create(mFileName, xws);
				xd.WriteTo(xmlw);
				xmlw.Flush();
			} finally {
				if (xmlw != null) {
					xmlw.Close();
				}
				if (FileNotification) {	// Reenable file change notification events.
					FileChangeWatcherEnabled = true;
				}
			}
		}
		/// <summary>
		/// Populate a newly created FileConfig object with variables, items and sections from its
		/// associated XML file.
		/// </summary>
		/// <returns>True if successful.</returns>
		public bool LoadFromFile()
		{
			Clear();
			XmlReaderSettings xrs = new XmlReaderSettings();
			xrs.ConformanceLevel = ConformanceLevel.Fragment;
			xrs.IgnoreComments = true;
			xrs.IgnoreWhitespace = true;
			bool bOk = true;
			XmlReader xr = null;
			XmlNode config;
			XmlNodeList sections;
			XmlDocument xd = new XmlDocument();

			try {
				xr = XmlReader.Create(mFileName, xrs);
				xd.Load(xr);
				xr.Close();
				config = xd.SelectSingleNode("Config");	// Only one config node.
				if (config != null) {
					sections = config.SelectNodes("Section");
					string sectName;
					foreach (XmlNode section in sections) {
						sectName = section.Attributes["Name"].Value;
						if (!string.IsNullOrEmpty(sectName)) {
							if (!ContainsKey(sectName))	// Section name has to be unique
							{
								FileSection fs = new FileSection(sectName);
								if (fs.Load(section)) {
									Add(sectName, fs);
								}
							}
						}
					}
				}
			} catch {
				bOk = false;
			}
			return bOk;
		}
		/// <summary>
		/// Public property for the file change notification for the underlying XML file for this
		/// FileConfig object.
		/// The OnFileChanged event will be raised when the associated XML file is changed by someone else
		/// than this object and FileChangeWatcherEnabled is set to true.
		/// </summary>
		/// <exception cref="Exception">Thrown if the filename for the associated XML file is not
		/// set (null or empty) when the notification is enabled.</exception>
		public bool FileChangeWatcherEnabled
		{
			get
			{
				if (fsw != null) {
					return fsw.EnableRaisingEvents;
				}
				return false;
			}
			set
			{
				if (value) {
					if (!FileChangeWatcherEnabled) {	// Not already enabled.
						if (fsw == null) {
							fsw = new FileSystemWatcher();
						}
						if (String.IsNullOrEmpty(mFileName)) {
							throw new Exception("Filename is null or empty!");
						}
						string fullpath = Path.GetFullPath(mFileName);
						fsw.Path = Path.GetDirectoryName(fullpath);
						fsw.Filter = Path.GetFileName(fullpath);
						fsw.NotifyFilter = NotifyFilters.LastWrite;
						fsw.Changed += new FileSystemEventHandler(fsw_Changed);
						fsw.EnableRaisingEvents = true;
					}
				} else {
					if (fsw != null) {
						fsw.EnableRaisingEvents = false;
						fsw.Changed -= new FileSystemEventHandler(fsw_Changed);
					}
				}
			}
		}

		/// <summary>
		/// Since there can be several file change events raised when the underlying 
		/// file is changed, the lastfiletime member keeps track of the filetime when
		/// the file change event was handled in order to check if consecutive events 
		/// are for the same change.
		/// </summary>
		protected DateTime lastfiletime=DateTime.MinValue;
		/// <summary>
		/// The file changed event handler triggered by the FileSystemWatcher for the
		/// XML file associated with this FileConfig.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		/// <exception cref="Exception">Thrown if could not reload from the file"</exception>
		protected virtual void fsw_Changed (object sender, FileSystemEventArgs e)
		{
			DateTime fdt=File.GetLastWriteTime(mFileName);
			if (fdt > lastfiletime) {	// Two change events are rised when the file
																// is saved.
				lastfiletime = fdt;
				FileStream fs=null;						// Have to wait until the file is really closed.
				int tries = 0;
				while (fs == null && (tries < 10)) {	// Wait maximum 1000ms (theoretical...)
					try {
						fs = File.Open(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None);
					} catch {
					}
					if (fs == null) {
						System.Threading.Thread.Sleep(100); // This is done in a background thread so it won't affect the GUI.
					}
					tries++;
				}
				if (fs != null) {
					fs.Close();
					if (!LoadFromFile()) {
						throw new Exception("Reload from file failed!");
					}
					if (OnFileChanged != null) { // Raise the file changed event.
						OnFileChanged(this, new FileConfigEventArgs());
					}
				} else {
					lastfiletime = DateTime.MinValue;		// Could not handle file change.
															// For now the change is skipped.
				}
			}
		}
	}

	/// <remarks>
	/// Event arguments for events raised by RJFileConfig.
	/// Nothing added for now but will be better prepared for future additions if done like this.
	/// </remarks>
	public class FileConfigEventArgs : EventArgs
	{
	}
}

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) Svep DesignCenter
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions