|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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.
The ideaThe 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):
Directory processingThe classThe class that implements the directory processing is called
As you can see, this class exposes two methods and two events. Let's take a closer look. The methods:
The events:
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 implementationSo, 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();
// Processes the files in the current directory
foreach(FileInfo f in files)
FoundFile(f);
files = null;
// Processes the subdirs in the current directory
DirectoryInfo[] subDirs = CurrentDir.GetDirectories();
foreach(DirectoryInfo dir in subDirs)
{
// Announces the directory
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 interfaceThe interfaceThe interface that is used to handle the events is called
The interface consists of:
By itself, this interface is quite useless. However, when implemented, it will be of immense help. The implementationsThe implementation of the interface allows me to write small scripts to handle the Another thing I must point out is that you have to overload the Let’s now briefly present the plug-in that I wrote. The ToLowerCase plug-inIt 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: /// <SUMMARY>
/// This function is called when a file is found
/// </SUMMARY>
/// The file name that was just found
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 pluginThis does the same thing as the other plug-in except that it makes them all upper case. The MP3 get detailed information plug-inThis 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 useOk. 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 Loading the plug-insWhen 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 ///<SUMMARY>
///Loads the plug-in from the configuration file.
///</SUMMARY>
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
{
//
// We try to create an instance of an object that contains an
// implementation of the IPlugin interface.
// The object to load is specified in the app.config file.
//
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 Registering plug-ins to handle eventsWhen 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 When the user clicks the Process button, the handlers that are contained within the private void ProcessDir_Click(object sender, System.EventArgs e)
{
Dir = new SDK.DirParser();
//
// We catch these events in order to show them in the GUI
//
Dir.FoundFile+=new SDK.AnnounceFile(Dir_FoundFile);
Dir.FoundDir+=new SDK.AnnounceDirectory(Dir_FoundDir);
//
// Adds the handlers to the class that actually runs through the directories
//
foreach(object k in SelectedHandlers.Items)
{
Plugin.IPlugin plg = (Plugin.IPlugin)k;
//
// For each plug-in, we register a method to handle the information
// coming from the plug-in
//
plg.WriteOutput+=OutputWriter;
//
// We add each handler to the class that processes the directories
//
Dir.FoundFile+=new SDK.AnnounceFile(plg.ProcessFile);
}
Dir.ProcessDir(Dir2Process.Text);
//
// Removes the handle for the messages from the plug-in. This is done
// because we do not need it anymore.
//
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 extensionsWell, for further extensions I was thinking about implementing an interface that handles the Sum up and FeedbackI 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 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
|
|||||||||||||||||||||||||||||||||||||||||||||||