Remove older file(s) from list based on create date - c#

I have a set of folders containing log files. Each folder is named as the date the log files were created. I am getting the content of these folders within X days of today and storing the resulting FileInfo in a list. So it is possible to have file info with same file name X times, or less.
I need to keep only the latest files based on create date. So, if the list contains multiple entries where fi.FileName is the same, I need to keep the latest, based on fi.CreateDate and ditch the other instance(s).
I tried something like this but am messing up somewhere:
files = files.GroupBy(i => new {i.FileName, i.CreateDate}).Select(i => i.Last()).ToList();

You must change your sort code as follows:
files = files.OrderBy(f=>f.CreateDate).GroupBy(i => i.FileName).Select(i => i.Last()).ToList();
This one also will give the same result:
files =files.GroupBy(i => i.FileName).Select(i => i.OrderByDescending(f=>f.CreateDate).First()).ToList();

You can use such a method to get files to purge:
using System.IO;
using System.Linq;
using System.Collections.Generic;
static public IEnumerable<FileInfo> GetTraceFiles(bool sortByDateOnly = true)
{
string folder = "MyFullPath"; // Can be from some instance
string prefix = "MyTraceFile-"; // global vars
string extension = ".log"; // and config
var list = Directory.GetFiles(folder, prefix + "*" + extension)
.Where(f => !IsFileLocked(f))
.Select(f => new FileInfo(f))
.OrderBy(fi => fi.CreationTime);
return sortByDateOnly ? list : list.ThenBy(fi => fi.FullName);
}
And this clear method:
static public void ClearTraces(int retain = 0)
{
var list = GetTraceFiles();
if ( retain > 0 ) list = list.Take(list.Count() - retain + 1);
foreach ( var fileInfo in list )
try
{
File.Delete(fileInfo.FullName);
}
catch
{
}
}
Here it retains retain last files but you can adapt to add a Where clause to use a date before which to erase:
.Where(fi => fi.CreationTime < ...);
Also instead of using the file system creation date and time, it is possible to use the file pattern in case for example MyTrace-YYYY-MM-DD#HH-MM-SS...
IsFileLocked comes from:
Is there a way to check if a file is in use?

Related

How to EnumerateFiles to only 3 files directories in C#?

Currently I can only Enumeratefiles method all files or the source directory. I want it to go 3 subdirectory's deeper and then only check there.
For Example the second snippet will check only K:\SourceFolder
The first example will check K:\SourceFolder\JobName\Batches\Folder1\Folder11\Images
It will check all folders and therefore decreasing the performance and efficiency of the application.
I only need it to check too K:\SourceFolder\JobName\Batches
This code goes too far:
List<string> validFiles = new List<string>();
List<string> files = Directory.EnumerateFiles(folderPath, "*.*", SearchOption.AllDirectories).ToList();
foreach (var file in files)
This code doesn't go far enough:
List<string> files = Directory.Enumeratefiles(directory)
Theres a few different methods we could use to tackle this task, so I'll list a few examples to help get started.
Using a loop to iterate all the folders and return only those at target depth:
List<string> ListFilesAtSpecificDepth(string rootPath, int targetDepth)
{
// Set the root folder before we start iterating through subfolders.
var foldersAtCurrentDepth = Directory.EnumerateDirectories(rootPath).ToList(); // calling ToList will make the enumeration happen now.
for (int currentDepth = 0; currentDepth < targetDepth; currentDepth++)
{
// Using select many is a clean way to select the subfolders for multiple root folders into a flat list.
foldersAtCurrentDepth = foldersAtCurrentDepth.SelectMany(x => Directory.EnumerateDirectories(x)).ToList();
}
// After the loop we have a list of folders for the targetDepth only.
// Select many again to get all the files for all the folders.
return foldersAtCurrentDepth.SelectMany(x => Directory.EnumerateFiles(x, "*.*", SearchOption.TopDirectoryOnly)).ToList();
}
Another options is to use recursion to recall the same method until we reach the desired depth. This can be used to only return results at the target depth or all the results along the way, since above example does only target depth, I've decided to do all results for this one using a custom object to track the depth:
class FileSearchResult
{
public string FilePath {get;set;}
public int FolderDepthFromRoot {get;set;}
}
List<FileSearchResult> ListFilesUntilSpecificDepth(string rootPath, int maxDepth, int currentDepth = 0)
{
// Add all the files at the current level along with extra details like the depth.
var iterationResult = Directory.EnumerateFiles(rootPath, "*.*", SearchOption.TopDirectoryOnly)
.Select(x => new FileSearchResult
{
FilePath = x,
FolderDepthFromRoot = currentDepth
}).ToList();
if (currentDepth < maxDepth) // we need to go deeper.
{
var foldersUnderMe = Directory.EnumerateDirectories(rootPath);
// Add all the results for subfolders recursively by calling the same method again.
foreach (var subFolder in foldersUnderMe)
{
iterationResult.AddRange(ListFilesUntilSpecificDepth(subFolder, maxDepth, currentDepth + 1))
}
}
return iterationResult;
}
With and example for both recursion and looping both covered, the last one I want to touch on is using the structure of the file system itself to help accomplish this task. So instead of managing our own loops or recursion we can use the original method to get all files recursively and then using the list of all results we can determine the depth. e.g. We know that '/' is a character used by the file system to delimit folders and that it's an illegal character to use in a folder or file name, so it should be pretty safe to use this marker to effectively count folders. In this example I'll use another custom class to track the results so it should effectively return the same results as the recursive method but with infinite depth.
class FileSearchResult
{
public string FilePath { get; set; }
public int FolderDepthFromRoot { get; set; }
}
List<FileSearchResult> ListFiles(string rootPath)
{
var allFiles = Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories).ToList();
int numberOfFoldersInRootPath = rootPath.Count(c => c == '\\'); // count how many backslashes in root path as a base.
return allFiles.Select(filePath => new FileSearchResult
{
FilePath = filePath,
FolderDepthFromRoot = filePath.Count(c => c == '\\') - numberOfFoldersInRootPath
}).ToList();
}

Get Highest Version Number in a Folder

How do I get the highest number file inside a folder N.2.4.0.12? The file names are
N.2.1.0,
N.2.1.1,
N.2.1.2,
N.2.4.0.8,
N.2.4.0.9,
N.2.4.0.10,
N.2.4.0.11,
N.2.4.0.12,
files.txt
I tried so many ways but it is still showing N.2.4.0.09 instead of N.2.4.0.12
var FileInOrdered = allFiles.OrderBy(f => f.Name).Last();
Version numbers, when sorted in lexicographical order, will not necessarily be in the version order. You should sort it while taking into consideration that they are versions, not just strings.
There is a Version class in the framework to represent a version. It is also comparable. You can just convert all your strings to Version:
var FileInOrdered = allFiles.OrderBy(f => new Version(f.Name.Substring(2))).Last();
It appears that one or more of your files do not have a valid file name. You can use this instead:
var FileInOrdered = allFiles.OrderBy(f => Version.TryParse(f.Name.Substring(2), out var v) ? v : new Version(0, 0)).Last();
Create a method that extracts the version number from the filename and then do the comparison.
Version GetVersion(string filename)
{
var versionString = filename.Substring(filename.IndexOf("V") + 1);
return new Version(versionString);
}
Then use this method like this:
var orderedList = allFiles.OrderBy(f => GetVersion(f.Name));

How to Remove Directories From EnumerateFiles?

So I'm working on a program that will list all the files in a directory. Pretty simple. Basically, when I do this: List<string> dirs = new List<string>(Directory.EnumerateFiles(target));, I don't want it to include the directory and all. Just the file name. When I run my code;
List<string> dirs = new List<string>(Directory.EnumerateFiles(target));
Console.WriteLine($"Folders and files in this directory:\n");
foreach (string i in dirs) {
Console.WriteLine($"> {i}");
}
it gives me the following:
C:\Users\Camden\Desktop\Programming\Visual Studio\C#\DirectoryManager\DirectoryManager\bin\Debug\DirectoryManager.exe
I just want the DirectoryManager.exe part, so I looked it up and I found that you can replace strings inside of strings. Like so: i.Replace(target, "");. However, this isn't doing anything, and it's just running like normal. Why isn't it replacing, and how should I instead do this?
Use methods from the System.IO.Path class.
var fullfile = #"C:\Users\Camden\Desktop\Programming\Visual Studio\C#\DirectoryManager\DirectoryManager\bin\Debug\DirectoryManager.exe";
var fileName = Path.GetFileName(fullfile); // DirectoryManager.exe
var name = Path.GetFileNameWithoutExtension(fullfile); // DirectoryManager
The simplest way is to use the Select IEnumerable extension
(you need to have a using Linq; at the top of your source code file)
List<string> files = new List<string>(Directory.EnumerateFiles(target)
.Select(x => Path.GetFileName(x)));
In this way the sequence of files retrieved by Directory.EnumerateFiles is passed, one by one, to the Select method where each fullfile name (x) is passed to Path.GetFileName to produce a new sequence of just filenames.
This sequence is then returned as a parameter to the List constructor.
And about your question on the Replace method. Remember that the Replace method doesn't change the string that you use to call the method, but returns a new string with the replacement executed. In NET strings are immutable.
So if you want to look at the replacement you need
string justFileName = i.Replace(target, "");
An alternative to using Directory.EnumerateFiles, would be DirectoryInfo.EnumerateFiles. This method returns an IEnumerable<FileInfo>. You can then make use of the FileInfo.Name property of each of the returned objects. Your code would then become:
var files = new DirectoryInfo(target).EnumerateFiles();
Console.WriteLine("Files in this directory:\n");
foreach (FileInfo i in files) {
Console.WriteLine($"> {i.Name}");
}
For just the list of file names:
List<string> fileNames = new DirectoryInfo(target).EnumerateFiles().Select(f => f.Name).ToList();
Alternatively, if you want both files and directories, you can use EnumerateFileSystemInfos. If you need to know if you have a file vs a directory you can query the Attributes property and compare it to the FileAttributes flags enumeration.
var dirsAndFiles = new DirectoryInfo(target).EnumerateFileSystemInfos();
Console.WriteLine("Folders and files in this directory:\n");
foreach (var i in dirsAndFiles) {
var type = (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory ? "Directory" : "File";
Console.WriteLine($"{type} > {i.Name}");
}
The FileSystemInfo.Name property will return either the file's name (in case of a file) or the last directory in the hierarchy (for a directory)--so just the subdirectory name and not the full path ("sub" instead of "c:\sub").

How to add files to a list after order by its name?

I am selecting all files in a folder and add all files names into to 2 lists. xmlFilePath and listVersion
As a example my files names are like below
1.8.1
1.8.2
1.10.0
1.10.1
but following code add its name as below.
1.10.0, 1.10.1, 1.8.1, 1.8.2
I want to change its order as below (Order by its name)
1.8.1, 1.8.2, 1.10.0, 1.10.1,
How can I achieve this requirement ?
FolderBrowserDialog folderDlg = new FolderBrowserDialog();
DialogResult result = folderDlg.ShowDialog();
if (result == DialogResult.OK)
{
string[] files = Directory.GetFiles(folderDlg.SelectedPath);
lblFolder.Text = "Folder : " + folderDlg.SelectedPath;
lblFiles.Text = "No of Xml Found : " + files.Length.ToString();
try
{
foreach (string filePath in Directory.GetFiles(folderDlg.SelectedPath))
{
xmlFilePath.Add(filePath);
}
foreach (string file in files)
{
string fileName = Path.GetFileNameWithoutExtension(file);
listVersion.Add(fileName);
}
}
catch (System.Exception ex)
{
lbl_notifications.Text = ex.Message;
}
}
The problem is it's sorting the name as text, without respect to the numeric value of the items. In text, a "1" is less than an "8", and it doesn't matter that the "1" is followed by a "0".
Do this:
string[] files = Directory.GetFiles(folderDlg.SelectedPath);
files = files.OrderBy(f => int.Parse(f.Split(".")[0]))
.ThenBy(f => int.Parse(f.Split(".")[1]))
.ThenBy(f => int.Parse(f.Split(".")[2])).ToArray();
You can improve on this further by creating a .Select() projection at the beginning that includes the filename and split parts, so that you don't have to keep re-Split()-ing the same string, but this should at least work.
If file names are actually versions sort them as versions:
string[] files = Directory.GetFiles(folderDlg.SelectedPath);
Array.Sort(files, (left, right) =>
new Version(Path.GetFileNameWithoutExtension(left)).CompareTo(
new Version(Path.GetFileNameWithoutExtension(right))));
Edit: in general case, e.g. for names like "v_1.2.4 bla-bla-bla 5.6.7" you can use StrCmpLogicalW function (As File Explorer does) or its analogue
using System.Runtime.InteropServices;
...
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern int StrCmpLogicalW(string x, string y);
...
string[] files = Directory.GetFiles(folderDlg.SelectedPath);
Array.Sort(files, (left, right) => StrCmpLogicalW(
Path.GetFileNameWithoutExtension(left),
Path.GetFileNameWithoutExtension(right)));
It looks like you are struggling with strings representing versions.
You can convert them to version and sort them.
var sortedVersions = files.Select(x => new Version(x)).OrderBy(x => x);
Now you can use the sortedVersions or convert it back to an array of strings.
files = sortedVersions.Select(x => x.ToString()).ToArray();
You can take a look at Version class and use it if it fits better. Working with strings are risky and you can reduce this risk by using appropriate data type.
If file names start by "v_" then you can modify the code above and handle it.
var sortedVersions = files.Select(x => new Version(x.Substring(2))).OrderBy(x => x);
files = sortedVersions.Select(x => "v_" + x.ToString()).ToArray();

C#: Get the 5 newest (last modified) files from a directory

Is there a way I can store the file location of the 5 last modified files from a directory using Array?
I am currently using the following codes below to get the last file:
DateTime lastHigh = new DateTime(1900,1,1);
string highDir;
foreach (string subdir in Directory.GetDirectories(path)){
DirectoryInfo fi1 = new DirectoryInfo(subdir);
DateTime created = fi1.LastWriteTime;
if (created > lastHigh){
highDir = subdir;
lastHigh = created;
}
}
I'll be using Array to send multiple files to an email address as attachment.
UPDATE
I am currently using the codes below to get the last modified files after 1 minute:
string myDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
"Test Folder");
var directory = new DirectoryInfo(myDirectory);
DateTime from_date = DateTime.Now.AddMinutes(-1);
DateTime to_date = DateTime.Now;
var files = directory.GetFiles().Where(file => file.LastWriteTime >= from_date && file.LastWriteTime <= to_date);
I want to store to list of file names coming from files
Here's a general way to do this with LINQ:
Directory.GetFiles(path)
.Select(x => new FileInfo(x))
.OrderByDescending(x => x.LastWriteTime)
.Take(5)
.ToArray()
I suspect this isn't quite what you want, since your code examples seem to be working at different tasks, but in the general case, this would do what the title of your question requests.
It sounds like you want a string array of the full filepaths of all the files in a directory.
Given you already have your FileInfo enumerable, you can do this:
var filenames = files.Select(f => f.FullName).ToArray();
If you wanted just the filenames, replace FullName with Name.
While the answer Paul Phillips provided worked. It's worth to keep in mind that the
FileInfo.LastWriteTime & FileInfo.LastAccessTime do not always work. It depends on how the OS is configured or could be a caching issue.
.NET FileInfo.LastWriteTime & FileInfo.LastAccessTime are wrong
File.GetLastWriteTime seems to be returning 'out of date' value

Categories