1
votes

EDIT 8/8/2012: I've made some significant changes to the code I'm using and would like some fresh help on one last problem I'm having. I'm going to rewrite most of this question.

I have a small program which iterates recursively through each file and folder under a target directory checking the names for specific characters. It works just fine but I'm looking for help on how to make a specific method work faster.

Here's the code I'm currently using. This is just a couple of lines from the method that kicks everything off:

if(getFullList(initialPathTB.Text))
            SearchFolder();  

And these are the two methods you need to see:

private void SearchFolder()
{
    int      newRow;
    int      numItems = 0;

    numItems = itemsMaster.Length;

    for (int x = 0; x < numItems; x++)
    {
        if (hasIllegalChars(itemsMaster[x]) == true)
        {
            newRow = dataGridView1.Rows.Add();
            dataGridView1.Rows[newRow].Cells[0].Value = itemsMaster[x];
            filesFound++;
        }
    }
}

private bool getFullList(string folderPath)
{
    try
    {
        if (checkBox17.Checked)
            itemsMaster = Directory.GetFileSystemEntries(folderPath, "*", SearchOption.AllDirectories);
        else
            itemsMaster = Directory.GetFileSystemEntries(folderPath, "*", SearchOption.TopDirectoryOnly);
        return true;
    }
    catch (UnauthorizedAccessException e)
    {
        if(folderPath[folderPath.Length - 1] != '\\')
            folderPath += @"\";
        if (e.Message == "Access to the path '" + folderPath + "' is denied.")
        {
            MessageBox.Show("You do not have read permission for the following directory:\n\n\t" + folderPath + "\n\nPlease select another folder or log in as a user with read access to this folder.", "Access Denied", MessageBoxButtons.OK, MessageBoxIcon.Error);
            folderPath = folderPath.Substring(0, folderPath.Length - 1);
        }
        else
        {
            if (accessDenied == null)
                accessDenied = new StringBuilder("");
            accessDenied.AppendLine(e.Message.Substring(20, e.Message.Length - 32));
        }
        return false;
    }
}

initialPathTB.Text is populated with something like "F:\COMMON\Administration".

Here's my problem. When the top level that's passed to folderPath is one which the user does not have read access everything works fine. When the top level and all subordinate directories are folders which the user has read access everything works fine again. The issue lies with directories where the user has read access to the top level but does not for some child folder deeper within. This is why getFullList() is a bool; if there are any UnauthorizedAccessExceptions then itemsMaster remains empty and SearchFolder() fails on numItems = itemsMaster.Length;.

What I'd like is to populate itemsMaster with every item within folderPath and simply skip the items for which the user doesn't have read access but I don't know how to do that without recursively crawling and checking each directory.

This code works so much faster than my old method so I'd rather not abandon it for something else entirely. Is there any way to make the Directory.GetFileSystemEntries() method do what I want?

2
How long is it taking your program to count, compared to doing Folder Properties in Windows Explorer? Just a guess, but I'd expect that you won't be able to do it faster than Windows itself, which can be pretty slow on something that large. - Joe Enos
Is the ToString() on items[x] even necessary? Just an unneeded function call that will cause lag, especially in a loop. - Cole Tobin
@JoeEnos - My method definitely takes much longer than if I check the folder properties via Windows Explorer. - newuser
Just out of interest, what does your program do, what does "checking the names for specific character" mean? If you're checking the file names, it's probably almost as expensive to execute the whole program as to count the files. - Tim Schmelter
@TimSchmelter - Actually that's a good point. It might be faster overall to count the contents of each sub-folder with my method above and save all of those items' paths to an array, then check the array for illegal characters rather than accessing the file system again... - newuser

2 Answers

2
votes
Directory.GetFileSystemEntries(folderPath, "*", SearchOption.AllDirectories).Length

or another option (with this option, keep in mind the first 3-5 elements in fullstring will be garbage text from the output which you should remove):

Process process = new Process();
List<string> fullstring = new List<string>();

process.StartInfo.FileName = "cmd.exe";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.UseShellExecute = false;
process.OutputDataReceived += (sender, args2) => fullstring.Add(args2.Data);

process.Start();

process.StandardInput.WriteLine(@"dir /b /s c:\temp | find """" /v");
process.BeginOutputReadLine();

process.WaitForExit(10000); //or whatever is appropriate time

process.Close();

If you want to track down errors better, make these changes:

Declare List<string> fullstring = new List<string>(); globally, then change the event handler of OutputDataReceived like below:

    process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
}

static void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    try
    {
        fullstring.Add(e.Data);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.ToString());
        //log exception
    }
}
1
votes

You write that "I'd like the progress bar to actually indicate how far through the list of items the program is, so therefore I need a number of items to set the ProgressBar.Maximum property to."

This is kind of a specific desire, and I'm not sure its worthwhile in the given case. If your ProgressBar is (say) 800 px wide, 1 percentage point would be 0.125px wide. For a list "over a hundred thousand items" -- let's make that a minimum of 100,000 -- you have to process 8,000 items to move the bar to move a single pixel. How much time does it take for your program to process 8,000 items? That will help you understand what kind of actual feedback you're giving the user. If it takes too long, it might look like things have hung, even if it's working.

If you're looking to give good user feedback, I'd suggest setting your ProgressBar's style to Marquee and providing a "Now checking file #x" textual indicator.