I am using the code below start at a path (root) provided by a GET variable and recursively go into every sub folder and display it's contents as list items. The path I'm using has about 3800 files and 375 sub folders. I takes about 45 seconds to render the page, is there any way I can cut this time down as this is unacceptable for my users.
string output;
protected void Page_Load(object sender, EventArgs e) {
getDirectoryTree(Request.QueryString["path"]);
itemWrapper.InnerHtml = output;
}
private void getDirectoryTree(string dirPath) {
try {
System.IO.DirectoryInfo rootDirectory = new System.IO.DirectoryInfo(dirPath);
foreach (System.IO.DirectoryInfo subDirectory in rootDirectory.GetDirectories()) {
output = output + "<ul><li><a>" + Regex.Replace(subDirectory.Name, "_", " ");
if (subDirectory.GetFiles().Length != 0 || subDirectory.GetDirectories().Length != 0) {
output = output + " +</a>";
} else {
output = output + "</a>";
}
getDirectoryTree(subDirectory.FullName);
if (subDirectory.GetFiles().Length != 0) {
output = output + "<ul>";
foreach (System.IO.FileInfo file in subDirectory.GetFiles()) {
output = output + "<li><a href='" + file.FullName + "'>" + file.Name + "</a></li>";
}
output = output + "</ul>";
}
output = output + "</li></ul>";
}
} catch (System.UnauthorizedAccessException) {
//This throws when we don't have access.
}
}
You should use System.Text.StringBuilder (Good performance) instead of string concatenate(Immutable) Bad performance.
You should use normal string replace function is not using complex search. subDirectory.Name.replace("_", " ");
Main reason for slowness in your code is most likely multiple calls to GetFiles and GetDirectories. You are calling them over and over again in if conditions as well as in your initial lookups. You only need the counts only once. Also, adding strings aren't helping the cause.
Following code was able to run through my simple usb-drive in 300ms and return with over 400 folders and 11000 files. On slow network drive, it was able to return in 9 seconds for 4000 files in 300 folders. It can probably be further optimized with Parallel.ForEach during recursion.
protected void Page_Load(object sender, EventArgs e) {
itemWrapper.InnerHtml = GetDirectory(Request.QueryString["path"]);
}
static string GetDirectory(string path)
{
StringBuilder output = new StringBuilder();
var subdir = System.IO.Directory.GetDirectories(path);
var files = System.IO.Directory.GetFiles(path);
output.Append("<ul><li><a>");
output.Append(path.Replace("_", " "));
output.Append(subdir.Length > 0 || files.Length > 0 ? "+</a>" : "</a>");
foreach(var sb in subdir)
{
output.Append(GetDirectory(sb));
}
if (files.Length > 0)
{
output.Append("<ul>");
foreach (var file in files)
{
output.AppendFormat("<li>{1}</li>", file, System.IO.Path.GetFileName(file));
}
output.Append("</ul>");
}
output.Append("</ul>");
return output.ToString();
}
Related
New programmer working on a little file mover. Not sure why my while statement is not working. I am trying to have the program check if the file exists in the directory and if so, counter++ until it comes up with an original name e.g. 2018 Picture(45) and so on...
private void btnMove_Click(object sender, EventArgs e)
{
string sourcePath = #"C:\Users\David\Desktop\Personal Pictures & Videos\fromme";
string destinationPath = #"C:\Users\David\Desktop\Personal Pictures & Videos\practicefolder";
if (!Directory.Exists(destinationPath))
{
Directory.CreateDirectory(destinationPath);
}
string[] sourcefiles = Directory.GetFiles(sourcePath);
//looks at each file with its path attached.
int counter = 1;
foreach (string sourcefile in sourcefiles)
{
if (sourcefile.EndsWith(".jpeg"))
{
string destFile = Path.Combine(destinationPath, "2018 Picture" + "(" + counter + ")" + ".jpeg");
MessageBox.Show(destFile);
while (Directory.Exists(destFile))
{
counter++;
}
//renames and moves files from sourcePath to destinationPath
File.Move(sourcefile, destFile);
Incrementing just the counter does not automatically update the file name, which you check to exist for the break condition of the loop.
while(File.Exists(destFile))
{
counter++;
destFile = Path.Combine(destinationPath, $"2018 Picture({ counter }).jpeg");
}
We have to update the file name with the incremented counter every time.
The $ syntax for string concatenation is optional but makes the file name composition clearer.
Furthermore, Directory.Exists does not work for files. If you pass a file name that exists, it will still return false, because it checks for the directory flag on the file system entry.
Put the your code creating the filename inside the loop and the File.Move outside of the loop. You should also set an upper limit on "counter" so that you can't get stuck in an infinite loop. Then only do the File.Move if you don't hit the limit. Since you're going to be changing the name with every iteration, you should only display the messagebox after the new filename has been successfully found.
foreach (string sourcefile in sourcefiles)
{
if (sourcefile.EndsWith(".jpeg"))
{
bool bSuccess = true;
string destFile = Path.Combine(destinationPath, "2018 Picture" + "(" + counter + ")" + ".jpeg");
counter = 0;
while (File.Exists(destFile))
{
destFile = Path.Combine(destinationPath, "2018 Picture" + "(" + counter + ")" + ".jpeg");
counter++;
if(counter>1000)
{
MessageBox.Show("'Too many tries.' or what ever message you want to use.");
bSuccess = false;;
}
}
if(bSuccess)
{
MessageBox.Show(destFile);
File.Move(sourcefile, destFile);
}
}
I found several things to correct or improve.
private void btnMove_Click(object sender, EventArgs e)
{
string sourcePath = #"C:\Users\David\Desktop\Personal Pictures & Videos\fromme";
string destinationPath = #"C:\Users\David\Desktop\Personal Pictures & Videos\practicefolder";
//no need to check if the path exists. CreateDirectory() already does the right thing
Directory.CreateDirectory(destinationPath);
int counter = 0;
var sourcefiles = Directory.EnumerateFiles(sourcePath, "*.jpeg");
foreach (string sourcefile in sourcefiles)
{
bool tryAgain = true;
while (tryAgain)
{
try
{
counter++;
destFile = Path.Combine(destinationPath, $"2018 Picture ({ counter }).jpeg");
File.Move(sourcefile, destFile);
tryAgain = false;
MessageBox.Show(destfile);
}
catch(IOException ex)
{ //file I/O is one of the few places where exceptions might be okay for flow control
tryAgain = (counter < 10000);
}
}
}
}
I'm making a program that writes a list of student objects to a text file and needs to be saved, I could either simply overwrite the contents of the file or delete the contents and rewrite the new list. This is the code I've tried using after some searching,
private void saveTSMI_Click(object sender, EventArgs e)
{
if (lstStudNames.Items.Count != 0)
{
SaveFileDia.Filter = "Text Files | *.txt";
if (SaveFileDia.ShowDialog() == DialogResult.OK)
{
//Clear the file
File.WriteAllText(SaveFileDia.FileName, string.Empty);
//Put all the student info into a string
foreach (Stud student in StudentList)
{
StudentInfoHolder += "Name: " + student.Name + Environment.NewLine +
"Subject: " + student.Subject + Environment.NewLine +
"Age: " + student.age + Environment.NewLine +
"Grade: " + student.Grade + Environment.NewLine
+ Environment.NewLine;
}
Clipboard.SetText(StudentInfoHolder);
File.WriteAllText(SaveFileDia.FileName, StudentInfoHolder);
}
}
else
{
MessageBox.Show("Nothing to save");
}
I've seen that File.WriteAllText() is meant to overwrite the file but nothing is overwritten when the program is saved.
You have to either reset the StudentInfoHolder class member before the foreach loop, or even better, use a local string variable in combination with String.Format method like this:
string studentInfoHolder;
foreach (Stud student in StudentList)
{
studentInfoHolder +=
string.Format("Name: {0}\r\nSubject: {1}\r\nAge: {2}\r\nGrade: {3}",
student.Name, student.Subject, student.age, student.Grade);
}
File.WriteAllText(SaveFileDia.FileName, studentInfoHolder);
Also, you're right that File.WriteAllText overwrites the file content, so this line is useless:
File.WriteAllText(SaveFileDia.FileName, string.Empty);
Update
As #kevin correctly pointed out, it is more efficient to use StringBuilder in the loop instead of the string concatenation:
StringBuilder studentInfoHolder;
foreach (Stud student in StudentList)
{
studentInfoHolder.AppendFormat("Name: {0}\r\nSubject: {1}\r\nAge: {2}\r\nGrade: {3}",
student.Name, student.Subject, student.age, student.Grade);
}
File.WriteAllText(SaveFileDia.FileName, studentInfoHolder.ToString());
Try something more like the following. It avoids opening the file twice, and string concatenation, which is not a great idea with immutable strings.
// This line over-writes the file if it exists, or otherwise creates it.
using (TextWriter fileWriter = new StreamWriter(SaveFileDia.FileName, append: false))
{
foreach (Stud student in StudentList)
{
fileWriter.WriteLine($"Name: {student.Name}");
fileWriter.WriteLine($"Subject: {student.Subject}");
fileWriter.WriteLine($"Age: {student.age}");
fileWriter.WriteLine($"Grade: {student.Grade}");
fileWriter.WriteLine();
}
}
There's no good reason to buffer all that in memory before writing it to the file. It's easier to open the file by calling File.CreateText, and then write each line to it, like this:
private void saveTSMI_Click(object sender, EventArgs e)
{
if (lstStudNames.Items.Count == 0)
{
MessageBox.Show("Nothing to save");
return;
}
SaveFileDia.Filter = "Text Files | *.txt";
if (SaveFileDia.ShowDialog() != DialogResult.OK)
{
return;
}
// Create the file (overwrite if it already exists),
// and write each student record.
using (var outFile = File.CreateText(SaveFileDia.FileName))
{
foreach (Stud student in StudentList)
{
outFile.WriteLine("Name: " + student.Name);
outFile.WriteLine("Subject: " + student.Subject);
outFile.WriteLine("Age: " + student.age);
outFile.WriteLine("Grade: " + student.Grade);
}
}
}
I also refactored your code a bit, reversing the logic on those two tests up front so as to reduce the nesting in your code.
Update after comment
If you really want a string to contain all that stuff, then you can modify the above to do it pretty easily. Replace the loop that writes to file with this one that uses a StringWriter:
// Create a StringWriter to hold the data, and write each line.
using (var sWriter = new StringWriter())
{
foreach (Stud student in StudentList)
{
sWriter.WriteLine("Name: " + student.Name);
sWriter.WriteLine("Subject: " + student.Subject);
sWriter.WriteLine("Age: " + student.age);
sWriter.WriteLine("Grade: " + student.Grade);
}
// write the data to the file
StudentInfoHolder = sWriter.ToString();
File.WriteAllText(SaveFileDia.FileName, StudentInfoHolder);
}
private void btn_Backup_Click(object sender, EventArgs e)
{
List<DirectoryInfo> SourceDir = this.lbox_Sources.Items.Cast<DirectoryInfo>().ToList();
List<DirectoryInfo> TargetDir = this.lbox_Targets.Items.Cast<DirectoryInfo>().ToList();
foreach (DirectoryInfo sourcedir in SourceDir)
{
foreach (DirectoryInfo targetdir in TargetDir)
{
string dateString = DateTime.Now.ToString("MM-dd-yyyy_H.mm.ss");
string LogFileName = #"BackupLog_" + sourcedir.Name + #"_" + dateString + #".log";
string[] lines = { dateString + "\t" + sourcedir.FullName + "\t" + targetdir.FullName + "\t" + "COMPLETED" };
if (this.checkbox_zipfiles.Checked == true)
{
System.IO.Compression.ZipFile.CreateFromDirectory(sourcedir.FullName, targetdir.FullName + #"\BACKUP_" + sourcedir.Name + #"_" + dateString + #".zip");
System.IO.File.WriteAllLines(tbox_LogFiles.Text + #"\" + LogFileName, lines);
}
else
{
foreach (var file in sourcedir.GetFiles())
{
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(sourcedir.FullName, targetdir.FullName, true);
System.IO.File.WriteAllLines(tbox_LogFiles.Text + #"\" + LogFileName, lines);
}
}
}
}
}
I need to exclude certain files from the backup (like .txt .docx)
I am using a list on my Form to add those exceptions.
I will also need to exclude certain Files and Folders but I think I can do that if I know how to do this.
private void btn_AddFileTypeException_Click(object sender, EventArgs e)
{
Form_FileTypeExceptions frm = new Form_FileTypeExceptions(new FileException());
if (frm.ShowDialog() == DialogResult.OK)
{
this.lbox_FileTypeExceptions.Items.Add(frm.Exception);
}
}
Any ideas please?
From form where you're setting extensions to be excluded fill list of strings that will contain extensions to skip, something like this:
List<string> extensionsToSkip = new List<string>();
extensionsToSkip.Add(".txt");
extensionsToSkip.Add(".docx");
//etc...
in your inner loop, change foreach loop from
foreach (var file in sourcedir.GetFiles())
into this
foreach (var file in sourcedir.GetFiles()
.Where(f => !extensionsToSkip.Contains(f.Extension)).ToList())
as you can see, when you get file collection with GetFiles it will be filtered to exclude extensions specified in extensionsToSkip list.
before that mentioned loop, test if you're getting right number of files by observing there two lists (just for test):
var originalList = sourcedir.GetFiles();
var filteredList = sourcedir.GetFiles().Where(f => !extensionsToSkip.Contains(f.Extension)).ToList();
I've been trying to figure out a way for the program to read all of the files from the path or zip file as input. Than read all of the file names inside of the input folder and split it so I can get information such as what is product id and chip name. Than store the pdf file in the correct db that matches with the product id and chip name.
The product id would be KHSA1234C and chip name LK454154.
Example File name: N3405-H-KAD_K-KHSA1234C-542164143_LK454154_GFK.pdf
public void btnUploadAttach_Click(object sender, EventArgs e)
{
string fName = this.FileUploadCFC.FileName;
string path = #"C:\mydir\";
string result;
result = Path.GetFileNameWithoutExtension(fName);
Console.WriteLine("GetFileNameWithoutExtension('{0}') return '{1}'",
fName, result);
result = Path.GetFileName(path);
Console.WriteLine("GetFileName('{0}') return '{1}'", path, result);
string[] sSplitFileName = fName.ToUpper().Split("-".ToCharArray());
foreach (char file in fName)
{
try
{
result = sSplitFileName[0] + "_" + sSplitFileName[1] + "-" +
sSplitFileName[2] + "_" + sSplitFileName[3] + "_" +
sSplitFileName[4] + "_" + sSplitFileName[5] + "_" +
sSplitFileName[6];
}
catch
{
return;
}
}
}
I don't know if I'm on the right track or not.
Can someone help me? Thank you.
first of all, in order to read all files in a folder you should use Directory.GetFiles, then you will iterate through this folder's files. Then you split file name's.. here you go..
using System.IO;
..
..
..
static void Main(string[] args)
{
string[] filePaths = Directory.GetFiles(#"c:\", "*.pdf");
string result;
foreach (var file in filePaths)
{
result = Path.GetFileNameWithoutExtension(file);
Console.WriteLine("GetFileNameWithoutExtension('{0}') return '{1}'",
file, result);
var sSplitFileName = file.ToUpper().Split('-');
var i = 0;
foreach (var item in sSplitFileName)
{
if (i == 3)
//it is product id
if (i == 7)
//it is chip name
i++;
}
}
}
To make a affirmative statement: You are on the right track - but not there yet :-)
First you need to read the files from your path, you don't do this currently.
Directory.GetFiles() may be the thing to search for. This will return a list of filenames as string[] array.
The you need to iterate over the files and apply the splitting, which looks ok to me in your code.
When you have the parts of your file, you want to decide on the database to use. It may be wise to split the filename your own filename class, that exposes properties for each part of the filename, but this is not required.
Next you need to get the db programming right, there are numerous examples on how to do this. Good luck :-)
Assuming the files all follow the same pattern you can probably just split on all of the deliminator characters '-' and '_'.
class Program
{
static void Main(string[] args)
{
string[] files = Directory.GetFiles(#"C:\mydir\", "*.pdf");
foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file);
var tokens = fileName.Split('-', '_');
for(int i=0;i<tokens.Length;i++)
{
string token = tokens[i];
Console.WriteLine("{0}-{1}", i, token);
}
Console.WriteLine();
}
Console.ReadLine();
}
}
Following the code found here:
How to check if file is under source control in SharpSvn?
I'm trying to make a small utility application that will iterate over a designated folder and print out the status of all the files.
private void btnCheckSVN_Click(object sender, EventArgs e)
{
ParseSVNResults(CheckSVN());
}
private Collection<SvnStatusEventArgs> CheckSVN()
{
string path = #"C:\AMG\trunk\AMC";
if (!Directory.Exists(path))
return null;
DevExpress.Utils.WaitDialogForm wait = new DevExpress.Utils.WaitDialogForm();
wait.Caption = "Please wait, loading SVN file statuses. This may take a moment.";
wait.Caption += Environment.NewLine + path;
wait.Show();
SvnClient client = new SvnClient();
SvnStatusArgs sa = new SvnStatusArgs();
sa.Depth = SvnDepth.Infinity;
Collection<SvnStatusEventArgs> statuses;
client.GetStatus(path, sa, out statuses);
wait.Close();
return statuses;
}
private void ParseSVNResults(Collection<SvnStatusEventArgs> results)
{
if (results == null)
return;
int modified = 0;
int unversioned = 0;
foreach (SvnStatusEventArgs item in results)
{
memoEditSVNFiles.Text += item.LocalContentStatus.ToString() + " -- " + item.Path + Environment.NewLine;
if (item.LocalContentStatus.ToString() == "Modified")
modified++;
else if (item.LocalContentStatus.ToString() == "NotVersioned")
unversioned++;
}
memoEditSVNFiles.Text += Environment.NewLine + "Modified: " + modified + Environment.NewLine;
memoEditSVNFiles.Text += "Not Versioned: " + unversioned + Environment.NewLine;
memoEditSVNFiles.Text += "Total: " + results.Count;
}
When the code executes, I get a total of 147 Files & Folders. The actual folder has a few thousand files. Is it possible I'm looking at too many files and SharpSVN just quits after a while?
edit; I just tried creating about 100 text files and putting 30 into 3 folders, then 'nesting' them. So I've got;
C:\AMG\trunk\test which has ~30 files
C:\AMG\trunk\test\Folder1 which has ~30 files
C:\AMG\trunk\test\Folder1\Sub which has another 30
Without comitting this to the repository, when I run the above code on C:\AMG\trunk\test instead of the given path in my code snippet, the output says 1 total file.
So it turns out the SvnStatusArgs class has a "RetrieveAllEntries" boolean flag that defaults to false.
As the name implies, setting this true returns every file, whether it was modified / unversioned or up to date.
1 extra line in the CheckSVN() method in my original post:
SvnClient client = new SvnClient();
SvnStatusArgs sa = new SvnStatusArgs();
sa.Depth = SvnDepth.Infinity;
sa.RetrieveAllEntries = true; //the new line
Collection<SvnStatusEventArgs> statuses;
client.GetStatus(path, sa, out statuses);