Click here to Skip to main content
15,896,269 members
Articles / Programming Languages / C#

A Flexible Plugin System

Rate me:
Please Sign up or sign in to vote.
4.98/5 (25 votes)
3 Sep 2008LGPL34 min read 131.7K   1.8K   163  
A generic plugin system used to load and manage plugins
using System;
using System.IO;
using System.Timers;

namespace Fadd.Globalization.Yaml
{
    /// <summary>
    /// Class to subscribe to changeevents on a file and update a languagehierarchy every time the file changes.
    /// </summary>
    /// <remarks>
    /// If the file being watched is deleted or renamed no more changes will be made to the languagenode.
    /// Also be observant on the fact that the class cannot Remove entries from a LanguageNode, only add or modify entries.
    /// This restriction is needed because a single YamlWatcher only watches One file and a LanguageNode can be connected to several
    /// watchers and files, deleting anything could clear things for another file.
    /// </remarks>
    public class YamlWatcher : IDisposable
    {
        public static char PathSeparator = '\\';
        private readonly string _fileName;
        private readonly string _path;
        private readonly string _fullName;
        private FileSystemWatcher _watcher;
        private readonly LanguageNode _languageNode;
		
        /// <summary>A timer to use as delay if the file is currently in use</summary>
        private readonly Timer _readTimer = new Timer(500);

        /// <summary>
        /// Instantiates the class to listen to changes in a file, also reads the file and fills the languageNode with language data
        /// </summary>
        /// <param name="languageNode">LanguageNode to fill</param>
        /// <param name="filename">The filename to watch</param>
        public YamlWatcher(LanguageNode languageNode, string filename)
        {
            if (!File.Exists(filename))
                throw new FileNotFoundException(filename + " do not exist.", filename);

            _path = Path.GetDirectoryName(Path.GetFullPath(filename));
            _fileName = Path.GetFileName(filename);
            _fullName = _path + "\\" + _fileName;
            _languageNode = languageNode;

            LoadFile(filename, _languageNode);
            Watch();

            _readTimer.Elapsed += OnTryRead;
        }

        /// <summary>
        /// Callback for when the file should be read again
        /// </summary>
        void OnTryRead(object sender, ElapsedEventArgs e)
        {
            _readTimer.Stop();
            LoadFile();
        }

        /// <summary>
        /// Sets the class to watch the specified file
        /// </summary>
        private void Watch()
        {
            _watcher = new FileSystemWatcher(_path, _fileName);
            _watcher.EnableRaisingEvents = true;
            _watcher.Changed += OnFileChanged;
        }

        /// <summary>
        /// Callback for when the file changes
        /// </summary>
        private void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            LoadFile();
        }

        /// <summary>
        /// Private loading of file to wrap read failure control
        /// </summary>
        private void LoadFile()
        {
            try
            {
                LoadFile(_fullName, _languageNode);
            }
            catch (IOException)
            {
                _readTimer.Start();
            }
        }

        /// <summary>
        /// Fill the rootNode with languages and categories from the specified file
        /// </summary>
        /// <param name="fullPath">Full file path</param>
        /// <param name="rootNode">The rootNode to fill</param>
        /// <exception cref="ArgumentException">If rootNode is of type Empty</exception>
        /// <exception cref="ArgumentNullException">If rootNode or fullPath is null</exception>
        public static void LoadFile(string fullPath, LanguageNode rootNode)
        {
            if (string.IsNullOrEmpty(fullPath))
                throw new ArgumentNullException("fullPath");

            if (rootNode == null)
                throw new ArgumentNullException("rootNode");

            if (rootNode is EmptyLanguageNode)
                throw new ArgumentException("rootNode must not be of type Empty", "rootNode");

            // Clear out any old information in the language nodes
            // rootNode.ClearHierarchy(); // todo, decide what to do with this

            //TextReader reader = new StreamReader(fullPath);
            using (Stream stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.None))
            {
                TextReader reader = new StreamReader(stream);
                YamlLight yaml = YamlLight.Parse(reader);

                // go through all languages
                foreach (YamlLight language in yaml)
                {
                    int lcid;
                    if (int.TryParse(language.Name, out lcid))
                    {
                        // go through all textstrings / categories for each language
                        foreach (YamlLight childNode in language)
                        {
                            // if the entry contains more than one line of text it is a sub root node and not just an entry in the root
                            if (childNode.Count > 0)
                            {
                                // try to retrieve the node from the root in case it's been instantiated before otherwise create it then expand
                                LanguageNode languageNode = rootNode.GetNode(childNode.Name);
                                if (languageNode is EmptyLanguageNode)
                                {
                                    languageNode = rootNode.AddNode(childNode.Name);
                                }
                                foreach (YamlLight child in childNode)
                                    Expand(child, languageNode, lcid);

                                if (!string.IsNullOrEmpty(childNode.Value))
                                    rootNode.Set(childNode.Name, lcid, childNode.Value);
                            }
                            else
								rootNode.Set(childNode.Name, lcid, childNode.Value);
                        }

                    }
                }

                reader.Dispose();
            }
        }

        private static void Expand(YamlLight node, LanguageNode parent, int lcid)
        {
            if(node.Count > 0)
            {
                LanguageNode langNode = parent.GetNode(node.Name);
                if (langNode is EmptyLanguageNode)
                    langNode = parent.AddNode(node.Name);

                if (!string.IsNullOrEmpty(node.Value))
					parent.Set(node.Name, lcid, node.Value);

                foreach (YamlLight subNode in node)
                    Expand(subNode, langNode, lcid);
            }
            else
                parent.Set(node.Name, lcid, node.Value);
        }

        #region IDisposable members
        /// <summary>
        /// Function to stop the watcher
        /// </summary>
        public void Dispose()
        {
            if (_watcher != null)
            {
                _watcher.Dispose();
                _watcher = null;
            }
        }
        #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 GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions