Introduction
In this article, I would like to present to you a small application I wrote. This application uses a class that runs through the files and folders of a directory structure and for each file and folder found, triggers an event that can be handled by a plug-in.
A little background to explain the reason for writing this application. A few weeks ago, I had to do some processing work on a bunch of files. The work consisted in some complicated work that could be done more easily by code, not by scripts. The idea was to reuse the code I wrote to process those files for further use.
So, I decided to write a class to process a structure of directories and to announce me when it finds a file or a folder. I would also write a bunch of plug-ins for each kind of work I had to perform. This way I could reuse my code and build additional plug-ins very quickly.
What does the files contain?
I attached to this article a number of four files.
- Source files: the source files to the entire project.
- Demo project: only the application discussed here (strong named by me).
- The interface: the interface needed to build your own plug-ins (strong named by me).
- The SDK: the class that does the processing work (not the plug-in handling) (strong named by me).
The idea
The idea to process directories came from this great article that I read sometime ago. I decided to “take it to the next level” and to design my own class and add some events to the class that handled the file and directory discovery.
I also wanted to be able to write new plug-ins very quickly and to be able to do that without having to recompile the entire application. The answer to this approach is to create an application that will use plug-ins, and these plug-ins will do the processing on the files within the directory structure.
The solution is divided into four projects (three Class libraries and one Windows application):
- The SDK project is used to create the directory processing functions. This class will expose some events for finding files and directories.
- The IPlugin project is the project that defines the plug-in interface that needs to be implemented.
- The Plugins project is the project used to implement three plug-ins.
- The TestApp project is the application that combines all of these projects into a working application.
Directory processing
The class
The class that implements the directory processing is called DirParser
. The UML diagram that represents this class is shown below:
As you can see, this class exposes two methods and two events. Let's take a closer look.
The methods:
ProcessDir(string RootDir)
: This methods initiates the processing of the directory specified in RootDir
.
Abort()
: This method aborts the processing of the current directory structure.
The events:
FoundFile()
: This event is triggered when a file is found.
FoundDir()
: This event is triggered when a directory is found.
As you can see from the diagram, the two events use two different delegates. This is done because the parameters for the two events need to be different.
The implementation
So, the class design is quite simple and so is the code. The code to process the directory is very similar to recursive back-tracking. The pseudo code of the algorithm used is this:
For the current folder:
1. Get the list of files in the directory
2. For each file
2.1. Trigger the FoundFile event
3. Get the list of directories in the current dir
4. For each of the directories
4.1. Trigger the FoundDir event
4.2. Call this function
The C# code looks like this (stripped for visibility):
FileInfo[] files = CurrentDir.GetFiles();
foreach(FileInfo f in files)
FoundFile(f);
files = null;
DirectoryInfo[] subDirs = CurrentDir.GetDirectories();
foreach(DirectoryInfo dir in subDirs)
{
FoundDir(dir);
processDir(dir);
}
For more information please read the article written by Andrew Boisen.
I know that, by now, you might think: “Ok, but that’s a piece of cake”. Yes, you are right. This part is very easy to build. However, the true power of what I did was to take these events and to produce an interface that will handle them.
In doing so, I wanted to be able to extend the functionality of my application by simply implementing the given interface and so to create a plug-in. More on that in the next section.
The plug-in interface
The interface
The interface that is used to handle the events is called IPlugin
. The UML diagram that represents this interface is shown below:
The interface consists of:
- One event
WriteOutput
which is used by the plug-in to send results to the outside world. (in our case, these events will be captured by the test application).
- A function
ProcessFile
that will contain the actual implementation of the event handler. We will use this method to handle the FoundDir
event.
- A function
Description
which is used to provide a small description of the plug-in that was created. This small function is used within the test application.
By itself, this interface is quite useless. However, when implemented, it will be of immense help.
The implementations
The implementation of the interface allows me to write small scripts to handle the FoundFile
event. I only discussed FoundFile
event simply because I didn’t need support for directory processing. However, this type of handling may appear in a later version, depending on your feedback.
Another thing I must point out is that you have to overload the ToString()
so that your plug-in will return the name you give it.
Let’s now briefly present the plug-in that I wrote.
The ToLowerCase plug-in
It simply converts all file names into lower case (A -> a). This is simply a teaching example that might not have any actual/real use.
The code that does this is this:
public void ProcessFile(FileInfo file)
{
if (WriteOutput!=null)
WriteOutput(this,"Renaming file "+file.FullName);
try
{
System.IO.File.Move(file.FullName,file.FullName.ToLower());
}
catch{}
}
Believe that this code is so trivial that I won't insist on it.
The ToUpperCase plugin
This does the same thing as the other plug-in except that it makes them all upper case.
The MP3 get detailed information plug-in
This plug-in retrieves information about MP3 files. How it does, will not be covered by this article. The source code includes comments so that you can figure out for yourself what it does. If you can’t, ask me.
The one important thing to remember about this plug-in is that it creates an Excel file that contains all the information. The file is created in the same folder as the application and it is called mp3info.csv.
Test Application or SDK in use
Ok. Now that you have some idea on how the little pieces work, let’s look at the big picture.
I have written a working application that uses both the SDK and the plug-ins. A screen shot of the application is here:
The application allows you to select a root directory and to add or remove the plug-in to run on the files in that structure. The output from the plug-ins is displayed in a ListView
control.
Loading the plug-ins
When the application starts, it will load all the plug-ins from the configuration file (the .config). We assume that all the plug-ins are located in the same directory as the application. The file looks like this, and each new plug-in should add an entry to this file:
The .config file:
configuration
appSettings
add key="Plugin_NamesToUpper"
value="Plugin.dll, Plugins.ProcessNamesToUpperCase"/add
add key="Plugin_NamesToLower"
value="Plugin.dll, Plugins.ProcessNamesToLowerCase"/add
add key="Plugin_Mp3GetInfo"
value="Plugin.dll, Plugins.ProcessMp3FileGetInfo"/add
/appSettings
/configuration
This is actually an XML document. Each key will register a plug-in. The key attribute constitutes the name of the plug-in and the value attribute constitutes the assembly DLL and the strong name of the class. This information is used by the Activator
class to load the plug-in. The code that does this is displayed below:
private void LoadPlugins()
{
char[] sep = new char[]{','};
foreach(string s in ConfigurationSettings.AppSettings.AllKeys)
{
if (s.StartsWith("Plugin"))
{
string[] temp = ConfigurationSettings.AppSettings.GetValues(s);
string[] var = temp[0].Split(sep);
System.Runtime.Remoting.ObjectHandle obj = null;
try
{
obj = Activator.CreateInstanceFrom(Application.StartupPath + "\\"+
var[0].Trim(),var[1].Trim());
Plugin.IPlugin plug = (Plugin.IPlugin)obj.Unwrap();
AvailableHandlers.Items.Add(plug);
}
catch (Exception e)
{
MessageBox.Show (this,e.Message,"Error");
}
}
}
}
Basically, this code will process through each entry in the configuration file and for each entry that starts with the word plug-in will create an object of the type specified in the configuration file, and load it into the AvailableHandlers
list.
Registering plug-ins to handle events
When the user selects a specific handler to be used in order to process the file within a file structure, the list item that is the plug-in will be moved to the SelectedHandlers
list.
When the user clicks the Process button, the handlers that are contained within the SelectedHandlers
list will be registered to the FileFound
event of the class that processes the files. The code is shown below:
private void ProcessDir_Click(object sender, System.EventArgs e)
{
Dir = new SDK.DirParser();
Dir.FoundFile+=new SDK.AnnounceFile(Dir_FoundFile);
Dir.FoundDir+=new SDK.AnnounceDirectory(Dir_FoundDir);
foreach(object k in SelectedHandlers.Items)
{
Plugin.IPlugin plg = (Plugin.IPlugin)k;
plg.WriteOutput+=OutputWriter;
Dir.FoundFile+=new SDK.AnnounceFile(plg.ProcessFile);
}
Dir.ProcessDir(Dir2Process.Text);
foreach(object k in SelectedHandlers.Items)
{
Plugin.IPlugin plg = (Plugin.IPlugin)k;
plg.WriteOutput-=OutputWriter;
}
Dir=null;
MessageBox.Show(this,"Done.");
CurrentDir.Text = "";
CurrentFile.Text = "";
}
And these are the key points in the application. If you want to know more about it, please look into the source files, or e-mail me!
Future extensions
Well, for further extensions I was thinking about implementing an interface that handles the DirectoryFound
event as well. Also, I was thinking about writing some new plug-ins, but with real effect, not just to show how they can be done.
Sum up and Feedback
I would like to sum up and tell you what this article is all about. It shows an application capable of running custom methods on files within a directory structure. The custom methods are encapsulated within plug-ins and each plug-in implements the IPlugin
interface. By doing so, you will end up having a lot of useful plug-ins for different types of files that will accomplish custom actions against your files.
At least this was the desired functionality of this application. I hope that you find this article useful.
Special thanks to my girlfriend Maria L. and my good friend Monica V.
Happy coding!
Bibliography
- For information about creating a plug-in framework click here.
- For information about recursing through files click here.
I am working on the C# compiler at Microsoft since 2007.
Microsoft Certified Professional since 2006
Interests: C#, ASP.NET, LINQ