Extract common path from a collection of grouped file paths? - c#

I have a question related to https://en.wikipedia.org/wiki/Longest_common_substring_problem, my source collection contains a list of file paths that doesn't always share a common path (outside of the C:\ drive sometimes) ex:
Source collection :
C:\Test\Root\Common\Data\a.txt
C:\Test\Root\Common\Data\Home\b.txt
C:\Test\Root\Common\Data\Home\Dev\c.txt
C:\Test2\Random\Data\a.txt
C:\Test2\Random\b.txt
C:\Test2\c.txt
D:\Data\a.txt
Output should be a collection :
C:\Test\Root\Common\Data\
C:\Test2\
D:\Data\
How to find the common path of each "group" of file paths ? I've found many solutions here but it is always with a collection of file paths sharing at least one common directory which is not the case here.

I am still not sure that I understand problem correctly...
I hope this will work.
public List<string> ExtractCommonPaths(List<string> paths)
{
var separatedImput = paths
.Select(path => path.Split(new [] {":\\", "\\" }, StringSplitOptions.RemoveEmptyEntries))
.Select(path => path.Take(path.Length - 1).ToList());
return separatedImput.GroupBy(path => path[0] + ":\\" + path[1])
.Select(g =>
{
var commonPath = g.Key;
var commpoPathLength = 2;
for (;;)
{
var exit = false;
var pathItem = string.Empty;
foreach (var path in g)
{
if (path.Count <= commpoPathLength)
{
exit = true;
break;
}
if (pathItem == string.Empty)
pathItem = path[commpoPathLength];
else
{
if (pathItem != path[commpoPathLength])
{
exit = true;
break;
}
}
}
if (exit)
break;
commonPath += "\\" + pathItem;
commpoPathLength++;
}
return commonPath;
})
.ToList();
}

I have something if you want to look for the directories inside some location.
To this method i have every of directories from some (C:\Files1) example location.
If you want to get only main directories from this list:
public List<DirectoryInfo> ExtractDirectoriesCommonPaths(List<DirectoryInfo> directories, string location)
{
var result = new List<DirectoryInfo>() { };
directories.ForEach(directory =>
{
var otherDirectories = directories.Where(d => d.FullName != directory.FullName);
var anyDirectoryWithThisSamePath = otherDirectories.Where(x => directory.FullName.Contains(x.FullName) && x.FullName.Length < directory.FullName.Length);
if (anyDirectoryWithThisSamePath.Any())
{
result.Add(anyDirectoryWithThisSamePath.FirstOrDefault());
}
});
return result.Where(x => x.FullName != location && x.FullName.Length > location.Length).Distinct().ToList();
}
Input:
C:\Files1\test_announcements
C:\Files1\Parent
C:\Files1\test_announcements\test_announcements_archive
C:\Files1\Parent\child
C:\Files1\Parent\child2
C:\Files1\Parent\child\subchild
C:\Files1\test_announcements
C:\Files1\Parent
Output:
C:\Files1\test_announcements
C:\Files1\Parent

Related

Getting counter to return correct number after being recursively called c#

I have a method called clearFiles which can be called recursively if there is sub directories. I have created a counter that will count the number of files that have been deleted. The counter holds the correct number when the function is either called once or called recursively but the toast at the end of the function only returns the correct number when it is not called recursively. Is there anyway I can display the toast and then reset the number of filesCleared? Because it's just returning a 0 when it's called recursively.
From playing around with it for a bit it seems like the toast is getting called after the filesCleared variable is set to 0 which is not what I want.
filesCleared variable:
int filesCleared = 0;
clearFiles:
public async Task ClearFiles()
{
var pathName = FileFilter.PathName;
FileInfo[] files = SortFiles(pathName);
try
{
if(FileFilter.Filter == "all")
{
foreach(var file in files)
{
if(file.Extension == FileFilter.Extension || FileFilter.Extension == "all")
{
File.Delete(file.ToString());
filesCleared++;
}
}
}
if(FileFilter.Filter == "date")
{
foreach (var file in files) //regular files
{
if(file.CreationTime < FileFilter.DeleteDate) //based on time
{
if(file.Extension == FileFilter.Extension || FileFilter.Extension == "all") //based on extension
{
File.Delete(file.ToString());
filesCleared++;
}
}
}
}
if(FileFilter.Filter == "number")
{
var i = 0;
for(var j = files.Length-1; j >= 0 ; j--)
{
if(files[j].Extension == FileFilter.Extension || FileFilter.Extension == "all")
{
if(i++ >= FileFilter.FilesToKeep)
{
File.Delete(files[j].ToString());
filesCleared++;
}
}
}
}
if (FileFilter.SubFolders == true) //subfiles (will be called recursively w/ each filter)
{
foreach(var subDir in new DirectoryInfo(pathName).GetDirectories())
{
//subDir.Delete(true);
FileFilter.PathName = subDir.ToString();
ClearFiles();
//await ClearFiles(subDir.ToString());
}
FileFilter.PathName = pathName; //resets the pathName so it will go back to what it was before the recursion
}
}
catch (IOException ioExp)
{
Console.WriteLine(ioExp.Message);
Toast = Toast.Bad();
logger.LogError(ioExp, "Error Deleting");
}
Toast = Toast.Good(filesCleared + " Files Deleted");
filesCleared = 0;
}
If you want to do something once but also want to call a method recursively, you have to split it in two. After trying to simplify your code I get a ClearFiles method like this:
public void ClearFiles()
{
var filesCleared = 0;
try
{
filesCleared = DeleteFilesRecursively(FileFilter.PathName, FileFilter);
}
catch (IOException ioExp)
{
Console.WriteLine(ioExp.Message);
Toast = Toast.Bad();
logger.LogError(ioExp, "Error Deleting");
}
Toast = Toast.Good(filesCleared + " Files Deleted");
}
Now Toast.Good is only called once after all subfolders have been traversed.
Note that filesCleared is a local variable, since I don't see any point in making it global. That way you also don't need to reset it.
The implementation of DeleteFilesRecursively could be something like this and could be simplified more if you wanted:
private const string All = "all";
private const string FilterByDate = "date";
private const string FilterByNumber = "number";
int DeleteFilesRecursively(string dirPath, SomeFileFilterType fileFilter)
{
FileInfo[] files = SortFiles(dirPath);
var deleted = 0;
var toBeDeleted = files.Where(f => MatchesByExtension(f, fileFilter.Extension));
if (fileFilter.Filter == FilterByDate)
{
toBeDeleted = toBeDeleted.Where(f => MatchesByDate(f, fileFilter.DeleteDate));
}
else if (FileFilter.Filter == FilterByNumber)
{
// If your SortFiles method sorted in the other
// direction this call to Reverse would not be needed.
toBeDeleted = toBeDeleted.Reverse().Take(fileFilter.FilesToKeep);
}
foreach (var file in toBeDeleted)
{
File.Delete(file.ToString());
deleted++;
}
if (fileFilter.SubFolders)
{
foreach(var subDir in new DirectoryInfo(dirPath).GetDirectories())
{
deleted += DeleteFilesRecursively(subDir.FullName, fileFilter);
}
}
return deleted;
}
bool MatchesByExtension(FileInfo file, string extension)
=> file.Extension == extension || extension == All;
bool MatchesByDate(FileInfo file, DateTime deleteDate)
=> file.CreationTime < deleteDate;
Note that I also removed your magic strings, which could be even better by replacing them with an enum type.
I haven't tested this but I believe it should give you the same behavior as your current code (at least the parts about filtering and deleting).

How can I check if folders are empty?

public Form1()
{
InitializeComponent();
if (textBoxRadarPath.Text != "")
{
if (!Directory.Exists(textBoxRadarPath.Text))
{
Directory.CreateDirectory(textBoxRadarPath.Text);
btnStart.Enabled = true;
}
}
if (textBoxSatellitePath.Text != "")
{
if (!Directory.Exists(textBoxSatellitePath.Text))
{
Directory.CreateDirectory(textBoxSatellitePath.Text);
btnStart.Enabled = true;
}
}
CheckIfImagesExist();
}
I'm checking if the folders in textBoxRadarPath.Text and textBoxSatellitePath.Text exist and create them if not. Then I call the method CheckIfImagesExist();
The problem is in the folder CheckIfImagesExist(); I'm also trying to get files :
There is more code in the method but the important is the GetImagesFiles method
private void CheckIfImagesExist()
{
GetImagesFiles();
}
And in GetImagesFiles
private void GetImagesFiles()
{
if (textBoxRadarPath.Text != "" || textBoxSatellitePath.Text != "")
{
if (Directory.Exists(textBoxRadarPath.Text))
{
if (checkBoxGetImages)
{
filesRadar = System.IO.Directory.GetFiles(textBoxRadarPath.Text,
"*.gif", SearchOption.AllDirectories).OrderBy(x => x).ToArray();
}
else
{
var t = new DirectoryInfo(textBoxRadarPath.Text).GetDirectories()
.OrderByDescending(d => d.LastWriteTimeUtc).First();
filesRadar = System.IO.Directory.GetFiles(t.FullName,
"*.gif", SearchOption.AllDirectories).OrderBy(x => x).ToArray();
}
Array.Sort(filesRadar, new MyComparer(true));
}
if (Directory.Exists(textBoxSatellitePath.Text))
{
if (checkBoxGetImages)
{
filesSatellite = System.IO.Directory.GetFiles(textBoxSatellitePath.Text,
"*.gif", SearchOption.AllDirectories).OrderBy(x => x).ToArray();
}
else
{
var t = new DirectoryInfo(textBoxSatellitePath.Text).GetDirectories()
.OrderByDescending(d => d.LastWriteTimeUtc).First();
filesSatellite = System.IO.Directory.GetFiles(t.FullName,
"*.gif", SearchOption.AllDirectories).OrderBy(x => x).ToArray();
}
Array.Sort(filesSatellite, new MyComparer(false));
}
}
}
Here I'm trying to get images from the first child folder under textBoxRadarPath.Text and textBoxSatellitePath.Text
The problem is if the directories textBoxRadarPath.Text and textBoxSatellitePath.Text not exist so they have created but they are empty yet so I'm getting exception on the line
var t = new DirectoryInfo(textBoxRadarPath.Text).GetDirectories()
.OrderByDescending(d => d.LastWriteTimeUtc).First();
Because the two directories just created or they are empty.
How can I check in the constructor if the directories are empty don't call the method CheckIfImagesExist() ?
Only if there are child folders inside then call the method CheckIfImagesExist()
The exception message :
System.InvalidOperationException: 'Sequence contains no elements'
On the line :
var t = new DirectoryInfo(textBoxRadarPath.Text).GetDirectories()
.OrderByDescending(d => d.LastWriteTimeUtc).First();
This screenshot show example of the folders structure on the hard disk.
Use FirstOrDefault when you are not sure there will be a resulting item
var t = new DirectoryInfo(textBoxRadarPath.Text)
.GetDirectories()
.OrderByDescending(d => d.LastWriteTimeUtc)
.FirstOrDefault(); // <- this will return null if there are no entries
then before you start checking the files, make sure you found a directory.
if (t != null)
{
filesSatellite = System.IO.Directory
.GetFiles(t.FullName, "*.gif", SearchOption.AllDirectories)
.OrderBy(x => x)
.ToArray();
}

An item with the same key has already been added C#

I'm trying to browse all *.txt files from folder to get metadata inside.
void SearchAndPopulate(string path, string searchText)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.txt");
Dictionary<String, dynamic> dictionary = new Dictionary<String, Object>();
int i = 0;
foreach (FileInfo file in files)
{
dictionary.Add(String.Format("name{0}", i.ToString()), i);
using (StreamReader sr = new StreamReader(file.FullName))
{
string content = sr.ReadToEnd().ToLower();
if (content.Contains(searchText.ToLower()))
{
dictionary["name"+i] = File
.ReadAllLines(file.FullName)
.Select(y => y.Split('='))
.Where(y => y.Length > 1)
.ToDictionary(y => y[0].Trim(), y => y[1]);
var temp = dictionary["name" + i];
listBox1.Text = temp["NUM_CLIENT"];
}
}
i++;
}
}
I get "An item with the same key has already been added" for dictionary variable.
This exception is thrown when you try to add a duplicate entry in a Dictionary using the same key. The key value in a Dictionary must be unique.
Possible causes
Look at the content of your file, you will find at least 2 lines where the left side of the = sign have the same string value.
You have multiple empty values on the left side of the = sign in your file. You can correct this in your Linq statement by ignoring those lines:
dictionary["name"+i] = File.ReadAllLines(file.FullName)
.Select(y => y.Split('='))
.Where(y => y.Length > 1 && !string.IsNullOrWhiteSpace(y[0]))
.ToDictionary(y => y[0].Trim(), y => y[1]);
Honestly I don't think you need any dictionaries in your code. Also you can reduce the number of times you read in each file.
void SearchAndPopulate(string path, string searchText)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.txt");
foreach (FileInfo file in files)
{
var content = File.ReadAllLines(file.FullName);
if (content.Any(line => line.ToLower().Contains(searchText.ToLower())))
{
var numClient = content.Select(y => y.Split('='))
.Where(y => y.Length > 1 && y[0].Trim() == "NUM_CLIENT")
.Select(y => y[1])
.FirstOrDefault();
if(numClient != null)
listBox1.Text = numClient;
else
// Do something here if "NUM_CLIENT" wasn't in the file.
}
}
}

Grouping a list by the results of a split

so I have some strings in a list
Folder1\File.png
Folder1\File2.png
File3.png
File4.png
and I would like to group these on a split('\\')[0]; for example
foreach (var group in files.GroupBy(x => //mysplit))
{
if (group.Count() > 1)
{
// this is a folder and its files are: group
}
else
{
//group is an individual file
}
}
but I'm not sure how to group the files by this split?
I would just group items that Contains() a backslash:
var lst1 = new string[] {"Folder1\\File.png", "Folder1\\File2.png" , "File3.png", "File4.png" };
var grps = lst1.GroupBy(x => x.Contains(#"\"));
foreach (var g in grps)
{
if (g.Key) // we have a path with directory
Console.WriteLine(String.Join("\r\n", g.ToList()));
else // we have an individual file
Console.WriteLine(String.Join("\r\n", g.ToList()));
}
So my solution was:
foreach (var groupedFiles in files.GroupBy(s => s.Split('\\')[0]))
{
if (Path.GetExtension(groupedFiles.Key) == string.Empty)
{
//this is a folder
var folder = groupedFiles.Key;
var folderFiles = groupedFiles.ToList();
}
else
{
//this is a file
var file = groupedFiles.First();
}
}

DirectoryInfo().GetFileSystemInfos() - how to avoid hidden folders?

If I do this:
var entries = new DirectoryInfo(#"C:\Folder1")
.GetFileSystemInfos("test*", SearchOption.AllDirectories);
when I have a folder structure like this, where the "HiddenFolder" is hidden but the "test.txt" file is not hidden:
C:\Folder1\
C:\Folder1\HiddenFolder\
C:\Folder1\HiddenFolder\test.txt
Then how can I code to NOT get the "test.txt" file?
(You might think that GetFileSystemInfos would skip hidden folders, but it doesn't.)
Here is a partial solution
This code strips out hidden files and folders, but doesn't handle a non-hidden file inside a hidden folder!
var includeLocalFolderNames = true; // to include folder names in the list
var hiddenFolders = new List<FileSystemInfo>();
return entries
.Where(entry =>
{
// skip if this entry is hidden, or it is in a hidden folder
var isHidden = (entry.Attributes & FileAttributes.Hidden) != 0;
if (isHidden)
{
hiddenFolders.Add(entry);
return false;
}
return !hiddenFolders.Any(fsi => entry.FullName.StartsWith(fsi.FullName));
})
.Where(entry =>
{
// include folder names if requested...
if (includeLocalFolderNames) return true;
return (entry.Attributes & FileAttributes.Directory) == 0;
})
.Select(entry => {
// do something...
return entry.Name;
});
Certainly not the most optimal and/or elegant solution.
var root = new DirectoryInfo(path);
var nonHiddenDirectories =
root.GetDirectories("*", SearchOption.AllDirectories).Where(
d => (d.Attributes & FileAttributes.Hidden) == 0).ToArray();
var nonHiddenFiles =
nonHiddenDirectories.SelectMany(d => d.GetFiles()).Where(
f => (f.Attributes & FileAttributes.Hidden) == 0);

Categories