Practical DiskDiff in C#: Comparing Directories, File Manipulation and Directory Operations

After a long trip, you’ve finally arrived at the point of getting something useful done with DiskDiff. In this chapter, you’ll start hooking up some useful functions and get DiskDiff ready for deployment.

1. Comparing Directories

Since you can now save and retrieve the state of a directory tree, it’s possible to compare the saved state of a directory tree with the current state.

To do this, add code to the file-loading code so it will load the tree to a separate structure, scan the directories to get the current state of that tree, and then perform a comparison between the two trees. The code in the open handler is as follows:

// deserialize the save version, and read in the new version

this.statusBar1.Text = “Opening…”;

directoryNodeBaseline = DirectoryNode.Deserialize(dialog.FileName);

directoryNode = directoryNodeBaseline;

rootDirectory = directoryNodeBaseline.Root;

DoTree();

The directoryNodeBaseline variable holds the baseline version, and DoTree() is called to get the current state of that directory. In this code, we then modified the DoTreeDone() function to call a comparison function.

You then add the comparison function CompareTrees() to the DirectoryNode class. This function matches file and directory entries in the current and baseline trees. For each match, it computes the ratio of the space used in the trees and stores the ratio as part of the object. The ratio is then displayed as part of the file and directory entries, and the icon displayed for an entry is red if the entry is bigger than the baseline entry.

The DiskDiff application now looks like Figure 37-1.

This baseline view is of the project directory. We then added the a. a file to the directory and loaded the baseline, creating this view. Note that a.a is flagged as a new file and is presented using an icon with a red background. The root level shows the increase in size of the entire tree and also appears with a red background.

You can now use DiskDiff for its intended use: to identify which directories have increased usage. In the current version, however, you have no way to perform the file operations to clean up the issues.

2. File Manipulation

To be able to clean up the extra junk on a drive, you need to be able to delete files from your interface. The usual user interface to use in this situation is a context menu.

Adding a context menu to a TreeView is simple. Drag a context menu control from the Toolbox to the form. Then, select the TreeView object, go to the property window, and set the ContextMenu property to the context menu you just added.

Next, you’ll need to set up the contents of the menu. You can do this in the form constructor after the call to InitializeComponent(). In this case, you add a helper function to add items to the menu, since all the menu items have the same handler:

void AddContextMenuItem(string itemString)

{

MenuItem menuItem;

menuItem = new MenuItem(itemString,

new EventHandler(treeContextMenuClick));

treeContextMenu.MenuItems.Add(menuItem);

}

Next, add the following lines to the constructor for the form:

AddContextMenuItem(“Filename”);

AddContextMenuItem(“Delete”);

AddContextMenuItem(“Delete Contents”);

AddContextMenuItem(“-“);

AddContextMenuItem(“View in Notepad”);

AddContextMenuItem(“Launch”);

The fourth line gives you a divider in the menu. You should also define some constants so you know what the indices of the menu items are:

const int menuIndexFilename = 0;

const int menuIndexDelete = 1;

const int menuIndexDeleteContents = 2;

const int menuIndexViewInNotepad = 4;

const int menuIndexLaunch = 5;

That gets the initial plumbing hooked up. Now it’s time to write some code to customize the menu before it opens. In this case, you want to add the name of the file or directory as the first entry of the menu and enable/disable the menu items based on the item that was selected. Here’s the code we wrote initially:

protected void treeContextMenu_Popup (object sender, System.EventArgs e)

{

MyTreeNode treeNode = (MyTreeNode) treeView1.SelectedNode;

string filename = null;

bool deleteContents = false;

bool viewInNotepad = false;

bool launch = false;

if (treeNode.Node is DirectoryNode)

{

DirectoryNode directoryNode = (DirectoryNode) treeNode.Node;

filename = directoryNode.Name;

deleteContents = true;

viewInNotepad = false;

}

if (treeNode.Node is FileNode)

{

FileNode fileNode = (FileNode) treeNode.Node;

filename = fileNode.Name;

deleteContents = false;

viewInNotepad = true;

}

treeContextMenu.MenuItems[menuIndexFilename].Text = filename;

treeContextMenu.MenuItems[menuIndexDeleteContents].Enabled = deleteContents;

treeContextMenu.MenuItems[menuIndexViewInNotepad].Enabled = viewInNotepad;

}

This code works, but the two if blocks are a good example of a poor design choice. The code for DirectoryNode and FileNode is essentially identical, but you still have to write separate code for each type of entry.

Now is a good time to define an abstract class for the properties that are shared. It will be called BaseNode and will have abstract properties for the properties that DirectoryNode and FileNode share. A few properties will have the same implementation in BaseNode and DirectoryNode, which is the reason an abstract class is used instead of an interface. It turns out there are quite a few of them, which is another good indication that this is a good change to make:

public abstract class BaseNode {

public abstract long Size { get; }

public abstract string Name { get; }

public abstract string FullName {get; }

public abstract string NameSize { get; }

public abstract bool FlagRed { get; }

public abstract bool EnableDeleteContents { get; }

public abstract bool EnableViewInNotepad { get; }

}

Modify the FileNode and DirectoryNode classes to override these properties. This reduces the code in the handler from 18 lines to only 4:

protected void treeContextMenu_Popup (object sender, System.EventArgs e)

{

BaseNode baseNode = ((MyTreeNode)treeView1.SelectedNode).Node;

treeContextMenu.MenuItems[menuIndexFilename].Text = baseNode.Name;

treeContextMenu.MenuItems[menuIndexDeleteContents].Enabled =

baseNode.EnableDeleteContents;

treeContextMenu.MenuItems[menuIndexViewInNotepad].Enabled =

baseNode.EnableViewInNotepad;

}

3. File and Directory Operations

Now it’s time to actually implement the file and directory operation. You do this in the event handler for the context menu’s items. Here’s the initial version:

protected void treeContextMenuClick(object sender, EventArgs e)

{

MenuItem menuItem = (MenuItem) sender;

BaseNode baseNode = ((MyTreeNode) treeView1.SelectedNode).Node;

switch (menuItem.Index)

{

case menuIndexDelete:

baseNode.Delete();

break;

case menuIndexDeleteContents:

((DirectoryNode)baseNode).DeleteContents();

break;

case menuIndexViewInNotepad:

ProcessStartInfo processStartInfo = new ProcessStartInfo();

processStartInfo.FileName = “notepad”;

processStartInfo.Arguments = baseNode.FullName;

Process.Start(processStartInfo);

break;

case menuIndexLaunch:

try {

processStartInfo = new ProcessStartInfo();

processStartInfo.FileName = baseNode.FullName;

Process.Start(processStartInfo);

}

catch (Exception exc)

{

MessageBox.Show(exc.ToString());

}

break;

}

}

We’ll cover each of these menu items in a little more detail in the following sections:

  • Delete: The Delete item calls a virtual function in the FileNode and DirectoryNode classes to delete the file or directory. This virtual function will call either File.Delete() or Directory.Delete().
  • Delete Contents: The Delete Contents item is done by a function in the DirectoryNode class.
  • View in Notepad and Launch: These two items are handled by starting a separate process. The process is started by using the Process class in the System.Diagnostics namespace, as discussed in Chapter 34.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *