Fastest way to search specific file in subdirectories - c#

I'm trying to search files in a folder that has a lot of folders, with a name that contain specific string.
I'm able to do it but it's taking me about 2 minutes and this is too much time for me.
This is the function:
private void Search()
{
foreach (var file in Directory.EnumerateFiles(#"P:\system\mail\", "*" + textBox1.Text + "*.pdf*", SearchOption.AllDirectories))
{
this.Invoke(new MethodInvoker(delegate ()
{
listBoxControl1.Items.Add(file);
}));
if (XtraMessageBox.Show("Open the file: " + file + " ?", "Information", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
{
Process.Start(file);
}
}
}
And this is where I'm using the function:
private async void simpleButton1_Click(object sender, EventArgs e)
{
labelControl1.Text = "Status: Please wait . . .";
Stopwatch watch = new Stopwatch();
watch.Start();
await Task.Run(() => Search());
watch.Stop();
labelControl1.Text = "The process done in " + watch.Elapsed.TotalMinutes.ToString() + " minutes.";
}
The goal is to do it like the search in windows that takes me 4-7 seconds.

To compare your search to Windows own search functionality is somewhat invalid because Windows search takes advantage of indexing the filesystem, while in your current implementation, you do not.
But there's good news: You can do it, too.
There are several ways to achieve similar response times, some are faster, some are more precise.
For example you could:
Perform searches in fixed intervals and use the results. Drawback: List may be outdated. OR...
Have an initial search on App start, then use FileSystemWatcher to get notified about FileSystem events (File new, File deleted, File moved ...) to update your internal index. Use that index as your source of information. Drawback: FSW can be a pain to deal with.
Find a way to take advantage of windows own indexes. See:
SO Answer https://stackoverflow.com/a/34340288/982149 utilizes OLE DB Api and may be outdated!
Windows Search Developer's Guide
I don't know if 3. is working for you. 1. may be out of the race because you probably don't want potentially outdated data. So I'd go with 2, but give 3. a shot, too.

Try to use Directory.GetFiles:
string[] files = Directory.GetFiles(filePath, "*.pdf", SearchOption.AllDirectories);

Related

How not to allow running some parts of a script by different users at the exact moment of time?

everyone!
I do a small project for my company and I use C#. I have a script for my project. But before this day, my colleagues and I had an idea that the script would be used by users one by one. For example, if there are a user A and user B, there can be the order where the user B runs the script and only then the user A can run the script.
Today the decision was made to give the users the possibility to run the script freely without the predetermined order. And now I have some thoughts. Here the part of the script:
if (Directory.Exists(#"H:\" + doc_number + #"\detached") == false)
{
Directory.CreateDirectory(#"H:\" + doc_number + #"\detached");
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\1.cms", signature_bytes);
}
else
{
string[] files = Directory.GetFiles(#"H:\" + doc_number + #"\detached"); int files_number = files.Length;
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\" + Convert.ToString(files_number + 1) + ".cms", signature_bytes);
}
Firstly, there is a check of the existence of a directory. If it doesn't exist, the directory will be created and the first file will be added there. Otherwise, we just count the number of files in the directory and then create a new file with a name which is the number of the files in the folder plus one.
However, I'm thinking about the situation when the user A and the user B were at the beginning of this part of the script at the same time and the condition for both would be positive so it wouldn't be executed correctly. Or if one of them started running this part earlier but his or her PC was less powerful so while creating the directory another user would go through the condition, counting files and start creating a file before the first user which would be also incorrect.
I don't know how likely one of these situations are. if so, how can I solve it?
Indeed, you can run into concurrency issues. And you are correct that you can't rely on the existence of a directory to decide what branch to take in your if statement because you might have operations execute in this order:
User A: Checks for directory. Does not exist.
User B: Checks for directory. Does not exist.
User A: Creates directory, enters if branch.
User B: Creates directory, enters if branch.
If the code was running in one process on one machine but in multiple threads, you could use a lock statement.
If the code was running on different processes on the same machine, you could use a cross-process coordination method such as a Mutex.
The question implies that the code runs on different computers but accesses the same file system. In this case, a lock file is a common mechanism to coordinate access to a shared resource. In this approach, you would attempt to create a file and lock it. If that file already exists and is locked by another process, you know someone else got there first. Depending on your needs, a common scenario is to wait for the lock on the file to go away then acquire the lock yourself and continue.
This strategy also works for the other 2 cases above, though is less efficient.
For information about how to create a file with a lock, see
How to lock a file with C#?
There are some issues with your code. For example, what would happen if a file is deleted? The number of files in the directory would be different than the number of the last file, and you can end up trying to write a file that already exists. Also, please use Path.Combine to create paths, it is safer. You also don't need to check if the directory exists, since Directory.Create will do nothing if it already exists.
Common for all solutions bellow:
string baseDir = Path.Combine("H:",doc_number, "detached");
Directory.Create(baseDir);
If you just want any number of users to create files in the same directory, some solutions that are more safe:
Use a GUID:
var guid = Guid.NewGuid();
var file = Path.Combine(baseDir, $"{guid}.cms");
File.WriteAllBytes(file, signature_bytes);
Iterate, trying to create a new file:
bool created = false;
int index = 1;
while(!created)
{
//Check first if the file exists, and gets the next available index
var file = Path.Combine(baseDir, $"{index}.cms");
while(File.Exists(file))
{
file = Path.Combine(baseDir, $"{++index}.cms");
}
//Handle race conditions, if the file was created after we checked
try
{
//Try create the file, not allowing others to acess it while open
using var stream = File.Open(file,FileMode.CreateNew,FileAccess.Write,FileShare.None);
stream.Write(signature_bytes);
created = true;
}
catch (IOException) //If the file already exists, try the next index
{
++index;
}
}

Reading only a certain part of the Filename in C#

I'm new to this Page just now but already got a Question to ask.
Ok, I'm right now on a bigger Project (for me atleast) for a Server Interface of round about 3 Minecraft Servers on one machine.
Now I got to the point where I no longer want to call the startfiles manually, so i created a function that creates a Savefile with the location of the "servers" startfile (it's a simple batchfile) called "(name_of_server)_SAVEFILE(number_of_server).txt".
And now i want the Program to show me (best on startup) how many Servers actually have been saved by asking for the number talked about earlier.
I also want to implement a lock so the filenumber already created can't be saved with another name, but that's a different story there.
I did it like this:
private void checkForServer_button_Click(object sender, EventArgs e)
{
if (File.Exists(#"C:\Users\" + system_user + #"\Desktop\savedServers\*1.txt") == true)
{
string server1_location = File.ReadAllText(#"C:\Users\" + system_user + #"\Desktop\savedServers\*_SAVEFILE1.txt");
checkForServer_response.Text = "There are Servers!";
onlyInfo.Clear();
onlyInfo.Text = "[CONSOLE] Found existing Server! Found at: " + server1_location;
}
}
onlyInfo is a RichTextBox used as a dummy output atm, might stay in the final version to show what the saved batchfiles look like.
Yes, so basically my code does nothing when I click the button.
And another Question, how do I set the "*" properly for this type of usage.
Thanks in advance!
File.Exists does not support wild characters in the file name. And neither does File.ReadAllText.
string[] files = System.IO.Directory.GetFiles(#"C:\Users\" + system_user +
#"\Desktop\savedServers\", "*1.txt");
if (files.Length > 0)
{
string server1_location = File.ReadAllText(files[0]);
...
}

Debugging a live ASP.net website

I have a C# ASP.net website. Locally I can run it in debug and step through the code to see why things arent working but when its hosted on my live site I cannot do this.
What is the best way to debug what is going on with my website?
Should I add debut/output/trace statements?
If so, which and how do I view the output of these? Can I view them in Chrome-->Developer Tools somehow?
For example, right now I can register a user on my site so I know the database connection is good, but I cannot login a registered user and want to figure out why.
Thanks
You may add trace and debug logs on your app. For ease, you may use logging frameworks like
http://nlog-project.org/
https://serilog.net/
You can actually write your own logging mechanism in which you can create a log class and some functions in it eg
public class Log
{
internal static bool RecordLog(string strSource, string strMethodName, string strStatement)//additional params you think appropriate for your logs
{
List<string> lstInfo = new List<string>();
string strProductName = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location.ToString()).ProductName.ToString();
string strProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location.ToString()).ProductVersion.ToString();
try
{
strProductName = FileVersionInfo.GetVersionInfo(Assembly.GetCallingAssembly().Location.ToString()).ProductName.ToString();
strProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetCallingAssembly().Location.ToString()).ProductVersion.ToString();
}
catch
{
}
try
{
lstInfo.Add("** Date=" + DateTime.Now.ToString("d MMM yy, H:mm:ss") + ", " + strProductName + " v" + strProductVersion);
lstInfo.Add("Source=" + strSource + ", Server=" + strServerIP + ""); //add more info in list as per rquirement
bool flag = blnWriteLog("LogFilename", lstInfo);
}
catch (Exception objEx)
{
//exception handling
}
return true;
}
private static bool blnWriteLog(string strProductName, List<string> lstInfo)
{
string strPath = strGetLogFileName(strProductName);
using StreamReader write in the log file received
return true;
}
private static string strGetLogFileName(string strFilePrefix)
{
//logic to check your file name, if it exists return name else create one
return strFile;
}
}
and then you can use the same from your file
Log.RecordLog()// Values as per your code and requirement
Note : Above is just a suggested way to do it, there can be many other and efficient ways also
You can use the built-in Microsoft Intellitrace feature to step through code from the generated intellitrace logs. This link https://msdn.microsoft.com/en-us/library/dn449058.aspx gives instructions on how to achieve the following;
"If you are using Microsoft Monitoring Agent to control IntelliTrace,
you also need to set up set up application performance monitoring on
your web server. This records diagnostic events while your app runs
and saves the events to an IntelliTrace log file. You can then look at
the events in Visual Studio Enterprise (but not Professional or
Community editions), go to the code where an event happened, look at
the recorded values at that point in time, and move forwards or
backwards through the code that ran. After you find and fix the
problem, repeat the cycle to build, release, and monitor your release
so you can resolve future potential problems earlier and faster."

Can't Access a xml file at random by C# console application

I have a C# console application which creates, parses and deletes multiple xml files at runtime. The application used to run fine in Windows 2003 server with .Net 2.0.
Recently, the Application framework was upgraded to >net 4.0 and the Windows Server OS to Windows 2008 64-bit.
Since then, the application encounters the following exception at random:
Access to the path 'D:\Content\iSDC\GDCOasis\GATE_DATA\LOG\635125008068192773\635125008074911566\SOD\AllRespId.xml' is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.Delete(String path)
at ProcessGateFile.SOD.saveFile(String psFile, String psXMLString, Boolean isNonAscii)
The code for the creation, parsing and deletion is as follows:
saveFile(tmpPath + "\\SOD\\AllRespId.xml", "<?xml version= \"1.0\" ?><XML>" + sbldDistinctResp.ToString() + "</XML>", isChinese);
//Save list of Distinct responsibilities for User
sbldDistinctResp.Remove(0, sbldDistinctResp.Length);
xmlCase.Load(tmpPath + "\\SOD\\AllRespId.xml");
arrResps.Clear();
//Start preparing Responsibility selection criteria
RespNodes = xmlCase.SelectNodes("//row");
sRespCriteria = "";
if (RespNodes.Count > 0)
{
foreach (XmlNode RespNode in RespNodes)
{
string RespName = RespNode.Attributes.GetNamedItem("RespId").Value.ToString();
if (!arrResps.Contains(RespName))
{
arrResps.Add(RespName);
}
}
for (int i = 0; i < arrResps.Count; i++)
{
sbldDistinctResp.Append("(#RespId = '" + arrResps[i].ToString() + "') or ");
}
sbldDistinctResp.Remove(sbldDistinctResp.Length - 4, 4);
sRespCriteria = sbldDistinctResp.ToString();
if (!sRespCriteria.Equals(""))
{
sRespCriteria = "(" + sRespCriteria + ")";
}
}
File.Delete(tmpPath + "\\SOD\\AllRespId.xml");
I repeat, the error is happening at random, i.e. it works at times and does not at other times during the same process.
Any idea what might be causing this and how to resolve?
Just a couple of observations:
Why are you saving and then immediately loading the file again? In fact, why do you even need to save this file - you already have all the information you need in the sbldDistinctResp variable to generate the XML you need to work with (as evidenced by the saveFile call at the start of the code) - couldn't you just make a copy of it, surround it with the same XML as you did during saveFile, and work with that?
"It happens randomly" is a very subjective observation :). You should profile this (run it 10,000 times in a loop for example) and record the pattern of errors. You may well be surprised that what seems random at first actually shows a clear pattern over a large number of runs. This may help you to make a connection between the problem and some other apparently unrelated event on the server; or it may confirm that it truly is random and therefore outside of your control.
If you really can't find the problem and you go with the idea of anti-virus, etc, then you could wrap the loading code in a try/catch and re-try a couple of times if you get the error. It's hacky but it would work, assuming you have accepted that the initial error is beyond your control.

Delete files older than a date

I am currently working on a c# program where I check the creation time of a file and delete it if the file is older than 2 days. I have the following code snippet that should be achieving this.
DateTime creationTime = file.CreationTime.Date;
if (creationTime < DateTime.Now.AddDays(-logAge) && file.Name != currentLog)
{
File.Delete(string.Format("{0}/{1}", directory, file));
}
While my program is running it is constantly creating new files and a separate thread checks that the files are no older than say 2 days. If I have my PC's date set to the 24th April the files are created and kept as expected, if I then change the PC's date to the 25th April I would expect the files to remain as they are not older than 2 days, however, this is not the case as they are being deleted.
Log age is set to so I wouldn't have expected files to be deleted until after I had changed the date to be the 26th April.
What am I doing wrong, I've looked at many examples including another question on Stackoverflow Delete files older than 3 months old in a directory using .NET but its not doing what I would expect it to.
You forced to consider only the date part of the creation time-stamp then condition is satisfied and file will be deleted (earlier) anyway I suggest a few modifications to that code:
static class Helpers {
public static void DeleteOldFiles(string folderPath, uint maximumAgeInDays,
params string[] filesToExclude) {
DateTime minimumDate = DateTime.Now.AddDays(-maximumAgeInDays);
var filesToDelete = Directory.EnumerateFiles(folderPath)
.Where(x => !IsExcluded(x, filesToExclude));
foreach (var eligibleFileToDelete in filesToDelete)
DeleteFileIfOlderThan(eligibleFileToDelete, minimumDate);
}
private const int RetriesOnError = 3;
private const int DelayOnRetry = 1000;
private static bool IsExcluded(string item, string[] exclusions) {
return exclusions.Contains(item, StringComparer.CurrentCultureIgnoreCase);
}
private static void DeleteFileIfOlderThan(string path, DateTime date)
{
for (int i = 0; i < RetriesOnError; ++i) {
try {
var file = new FileInfo(path);
if (file.CreationTime < date)
file.Delete();
}
catch (IOException) {
System.Threading.Thread.Sleep(DelayOnRetry);
}
catch (UnauthorizedAccessException) {
System.Threading.Thread.Sleep(DelayOnRetry);
}
}
}
}
Notes
I'm still using DateTime.Now, I guess for this kind of operations you do not need any precision measurement (and you're talking about days so your thread may have a scheduled time of hours).
If your application uses multiple log files you can specify them all as parameters and they'll be ignored.
If you call DeleteOldFiles with 0 for maximumAgeInDays then you'll delay all log files not in use (as specified in the exclusion list).
Sometimes files can be in use (even if this should happen seldom in your case). The DeleteFileIfOlderThan function will retry to delete them after a short delay (it mimics Explorer.exe behavior).
You can call this function in this way:
Helpers.DeleteOldFiles(#"c:\mypath\", logAge, currentLog);
Few more notes:
This code doesn't combine path and file name but if you have to do it you should use Path.Combine(), I guess you do not want to reinvent the wheel each time to check if a path ends with a trailing backslash or not.
I/O operations can fail! Always check for exceptions.
file.Delete does make more sense than File.Delete(path) and Path.Combine() makes a lot more sense than using string.Format.
I've stumbled across this answer, don't know why I didn't find it before hand after spending ages on google, but this appears to have fixed the problem. DateTime.Compare how to check if a date is less than 30 days old?. The other problem was that I was using the file creation time but for my scenario it made more sense to use lastWriteTime.date.
I guess an additional problem must be in
File.Delete(string.Format("{0}/{1}", directory, file));
Your file is of type FileSystemInfo. Maybe you wanted to use file.Name.
Example: let's say directory is "c:\" and file points to "c:\myfile.log", your code will try to delete "c:/c:\myfile.log". It's hard for me to guess what exactly you have in these variables.
Correct replacement is suggested by #HenkHolterman:
file.Delete();

Categories