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