I am having a list of files from a directory and I want to sort it out by filename.
This is the main code:
var localPath = this.Server.MapPath("~/Content/Img/" + type + "/");
var directory = new DirectoryInfo(localPath);
isDirectory = directory.Exists;
if (isDirectory)
{
foreach (FileInfo f in directory.GetFiles())
{
Picture picture = new Picture();
picture.ImagePath = path;
picture.CreationDate = f.CreationTime;
picture.FileName = f.Name;
listPictures.Add(picture);
}
}
here is the class Picture where all the files are stored:
public class Picture
{
public string ImagePath { get; set; }
public string FileName { get; set; }
public DateTime CreationDate { get; set; }
}
How do you do to sort a files list by order of FileName?
Simply change your for loop :
foreach (FileInfo f in directory.GetFiles().OrderBy(fi=>fi.FileName))
{
}
Alternatively, you can rewrite the whole loop using this code :
var sortedFiles = from fi in directory.GetFiles()
order by fi.FileName
select new Picture { ImagePath = path, CreationDate = f.CreationTime, FileName = f.FileName };
listPictures.AddRange(sortedFiles);
listPictures = listPictures.OrderBy(x => x.FileName).ToList();
You can use lambda expression and/or extension methods. For example:
listPictures.OrderBy(p => p.FileName).ToList();
Note that EnumerateFiles performs lazy loading and can be more efficient for larger directories, so:
dir.EnumerateFiles().OrderBy(f => f.FileName))
You can use LINQ from the beginning:
var files = from f in directory.EnumerateFiles()
let pic = new Picture(){
ImagePath = path;
CreationDate = f.CreationTime;
FileName = f.Name;
}
orderby pic.FileName
select pic;
Note that Directory.EnumerateFiles(path) will be more efficient if only the FileName is used.
Related
i need some help with my sort script. I wanna sort some files.
This is how the Name is constructed: Name#Page#Version
I can pick the Name/category and the page but i dont know how to pick the last version :/
Here you can see an example.
foreach(string files in Directory.GetFiles(path).OrderBy(fi => fi.Length))
{
try
{
filename = Path.GetFileNameWithoutExtension(files);
index = filename.LastIndexOf("#");
index2 = filename.LastIndexOf("#",index-1);
strversion = filename.Substring(index+1);
strpage = filename.Substring(index2+1);
strpage = strpage.Substring(0, strpage.LastIndexOf("#"));
page = Int32.Parse(strpage);
version = Int32.Parse(strversion);
Console.WriteLine("Page: "+page);
Console.WriteLine("Version: "+version);
if (filename.Contains("SMA"))
{
if (page == 1)
{
Console.WriteLine(filename);
}
}
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
}
You're over complicating things, you can split the string by # and get what you want from the array given:
var fileName = "SMA#1#2";
var parts = fileName.Split('#');
var name = parts[0];
var page = parts[1];
var version = parts[2];
EDIT
As for getting the last version for each page, you're probably better off creating some sort of class for your file and then grouping by page, and then sorting by version, and then selecting the first one:
public class Program
{
public static void Main()
{
var fileNames = new[] { "SMA#1#1", "SMA#1#2", "SMA#1#3", "SMA#2#1", "SMA#2#3" };
var files = (from fileName in fileNames select fileName.Split('#') into parts let name = parts[0] let page = Int32.Parse(parts[1]) let version = Int32.Parse(parts[2]) select new MyFile(name, page, version)).ToList();
var grouped = files.GroupBy(x => x.Page).ToList();
foreach (var group in grouped)
{
var ordered = group.OrderByDescending(x => x.Version);
Console.WriteLine($"Page {group.Key} highest version: {ordered.First().Version}");
}
}
}
public class MyFile
{
public string Name { get; set; }
public int Page { get; set; }
public int Version { get; set; }
public MyFile(string name, int page, int version)
{
Name = name;
Page = page;
Version = version;
}
}
If I correctly understand your requirement, you want to
filter out every file not containing "SMA"
then order by page
then by version
You can achieve this quite declaratively using LINQ:
var orderedFileNames =
fileNames
.Where(fn=>fn.Contains("SMA")
// parse name
.Select(fn => fn.Split('#'))
// pull parts into anonymous type
.Select(fn => new {
Name = fn[0], Page = int.Parse(fn[1]), Version = int.Parse(fn[2])
})
.OrderBy(fn=>fn.Name)
.ThenBy(fn=>fn.Page)
.ThenBy(fn=>fn.Version);
int lastIndex = filename.LastIndexOf("#");
string version = fileName.SubString(lastIndex, fileName.Length - lastIndex);
Is that what you are looking for?
I have a C# application which reads text files with lines such as:
c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm
c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4
What I need are two values from each lines in such a way:
From line 1:
filename variable valued as 'myfile2.cfm' and filepath variable as "" [empty]
From line 2:
filename variable valued as 'my file1.blahh4' and filepath as 'includes' [also be \includes\subfolder]
I have tried code like indexof and substring but no success so far. I think some RegEx should help? Basically, the slashes will be constantly 3 before file or folder names begin.
Thanks!
You can use Path which parses paths.
Please see the following code for more details:
var path = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm";
var pathFileName = Path.GetFileName(path); // "myfile2.cfm"
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16";
var pathDirectory = Path.GetDirectoryName(path).Replace(baseDirectory, ""); // ""
Edit See the code below which sets the paths to LowerInvariant in order to ensure the replace works as expected.
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16".ToLowerInvariant();
var paths = new string[] {
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4"
};
var sanitizedPaths = new List<Tuple<string,string>>();
foreach(var path in paths.Select(p => (p ?? String.Empty).ToLowerInvariant()))
{
var fileName = Path.GetFileName(path);
var directory = Path.GetDirectoryName(path).Replace(baseDirectory, String.Empty);
sanitizedPaths.Add(new Tuple<string, string>(fileName, directory));
}
// sanitizedPaths[0] -> "myfile2.cfm" | ""
// sanitizedPaths[1] -> "my file1.blahh4" | "\includes"
Edit 2 Using Uri and based on the fact your base directory is always 3 segments, the following should do:
var paths = new string[] {
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\subFolder\other file.extension"
};
var sanitizedPaths = new List<Tuple<string, string>>();
foreach (var path in paths.Select(p => (p ?? String.Empty).ToLowerInvariant()))
{
var uri = new Uri(path);
var pathWithoutBaseDirectory = String.Join("/", uri.Segments.Skip(4));
var fileName = Path.GetFileName(pathWithoutBaseDirectory);
var directory = Path.GetDirectoryName(pathWithoutBaseDirectory);
sanitizedPaths.Add(new Tuple<string, string>(fileName, directory));
}
Use Path class to get file names and file directories:
var baseDirectory = #"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\";
var files = new[]
{
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm",
#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4"
};
And here goes LINQ query
var query = from file in files
let directoryName = Path.GetDirectoryName(file)
select new
{
filename = Path.GetFileName(file),
filepath = directoryName.StartsWith(baseDirectory)
? directoryName.Substring(baseDirectory.Length) : ""
};
Output:
[
{
filename: "myfile2.cfm",
filepath: ""
},
{
filename: "my file1.blahh4",
filepath: "includes"
}
]
What I would do is use the FileInfo class in a recursive loop to get a list of all files. You can separate the path and filename using the FileInfo. Then using the string length of the base folder you are acting on and substring that from the path of each of the files.
Something like this?
using System;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
RelativePaths p = new RelativePaths(#"u:\test");
foreach (var str in p.MyFiles)
{
Console.WriteLine(str);
}
Console.ReadKey();
}
}
class MyFileInfo
{
public MyFileInfo(string path, string filename)
{
Path = path;
Filename = filename;
}
public string Path { get; private set; }
public string Filename { get; private set; }
public override string ToString() => $"{Path}, {Filename}";
}
class RelativePaths
{
List<MyFileInfo> myPaths = new List<MyFileInfo>();
public RelativePaths(string startingPath = #"U:\test")
{
DirectoryInfo dir = new DirectoryInfo(startingPath);
PathSeparator(dir.FullName, dir);
}
public MyFileInfo[] MyFiles => myPaths.ToArray();
public void PathSeparator(string originalPath, DirectoryInfo dir)
{
// Files in dir
foreach (var file in dir.GetFiles())
{
myPaths.Add(new MyFileInfo(file.DirectoryName.Substring(originalPath.Length),
file.Name));
}
foreach (var folder in dir.GetDirectories())
{
PathSeparator(originalPath, folder);
}
}
}
}
Original paths:
u:\test\subfolder
u:\test\testfile1.txt
u:\test\subfolder\fileinsub1.txt
u:\test\subfolder\subfolder2
u:\test\subfolder\subfolder2\two deep.txt
Where result is:
, testfile1.txt
\subfolder, fileinsub1.txt
\subfolder\subfolder2, two deep.txt
Note that the results are in a list of type MyFileInfo so you can just use the data output however you see fit. This is only and example. You may want to create a static class with an extension method instead but using the recursive method to look in each folder is the concept that might be helpful. Note you can copy the source code directly into a new console project and run it in Visual Studio just change the starting path.
Try this out:
var s1 =#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\myfile2.cfm";
var s2 =#"c:\ecpg\BL_Publish_Staging_CFCS_PSC_Outage_Notification_16\includes\my file1.blahh4";
var filename = Path.GetFileName(s1);
var p1 = Path.GetDirectoryName(s1);
if (!p1.ToLowerInvariant().Contains(#"\includes"))
p1 = "";
var p2 = Path.GetDirectoryName(s2);
if (!p2.ToLowerInvariant().Contains(#"\includes"))
p2 = "";
I need to search a folder containing csv files. The records i'm interested in have 3 fields: Rec, Country and Year. My job is to search the files and see if any of the files has records for more then a single year. Below the code i have so far:
// Get each individual file from the folder.
string startFolder = #"C:\MyFileFolder\";
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);
var queryMatchingFiles =
from file in fileList
where (file.Extension == ".dat" || file.Extension == ".csv")
select file;
Then i'm came up with this code to read year field from each file and find those where year count is more than 1(The count part was not successfully implemented)
public void GetFileData(string filesname, char sep)
{
using (StreamReader reader = new StreamReader(filesname))
{
var recs = (from line in reader.Lines(sep.ToString())
let parts = line.Split(sep)
select parts[2]);
}
below a sample file:
REC,IE,2014
REC,DE,2014
REC,FR,2015
Now i'am struggling to combine these 2 ideas to solve my problem in a single query. The query should list those files that have record for more than a year.
Thanks in advance
Something along these lines:
string startFolder = #"C:\MyFileFolder\";
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*",
System.IO.SearchOption.AllDirectories);
var fileData =
from file in fileList
where (file.Extension == ".dat" || file.Extension == ".csv")
select GetFileData(file, ',')
;
public string GetFileData(string filesname, char sep)
{
using (StreamReader reader = new StreamReader(filesname))
{
var recs = (from line in reader.Lines(sep.ToString())
let parts = line.Split(sep)
select parts[2]);
var multipleyears = recs.Distinct().Count();
if(multipleyears > 1)
return filename;
}
}
Not on my develop machine, so this might not compile "as is", but here's a direction
var lines = // file.readalllines();
var years = from line in lines
let parts = line.Split(new [] {','})
select parts[2]);
var distinct_years = years.Distinct();
if (distinct_years >1 )
// this file has several years
"My job is to search the files and see if any of the files has records
for more then a single year."
This specifies that you want a Boolean result, one that says if any of the files has those records.
For fun I'll extend it a little bit more:
My job is to get the collection of files where any of the records is about more than a single year.
You were almost there. Let's first declare a class with the records in your file:
public class MyRecord
{
public string Rec { get; set; }
public string CountryCode { get; set; }
public int Year { get; set; }
}
I'll make an extension method of the class FileInfo that will read the file and returns the sequence of MyRecords that is in it.
For extension methods see MSDN Extension Methods (C# Programming Guide)
public static class FileInfoExtension
{
public static IEnumerable<MyRecord> ReadMyRecords(this FileInfo file, char separator)
{
var records = new List<MyRecord>();
using (var reader = new StreamReader(file.FullName))
{
var lineToProcess = reader.ReadLine();
while (lineToProcess != null)
{
var splitLines = lineToProcess.Split(new char[] { separator }, 3);
if (splitLines.Length < 3) throw new InvalidDataException();
var record = new MyRecord()
{
Rec = splitLines[0],
CountryCode = splitLines[1],
Year = Int32.Parse(splitLines[2]),
};
records.Add(record);
lineToProcess = reader.ReadLine();
}
}
return records;
}
}
I could have used string instead of FileInfo, but IMHO a string is something completely different than a filename.
After the above you can write the following:
string startFolder = #"C:\MyFileFolder\";
var directoryInfo = new DirectoryInfo(startFolder);
var allFiles = directoryInfo.EnumerateFiles("*.*", SearchOption.AllDirectories);
var sequenceOfFileRecordCollections = allFiles.ReadMyRecords(',');
So now you have per file a sequence of the MyRecords in the file. You want to know which files have more than one year, Let's add another extension method to class FileInfoExtension:
public static bool IsMultiYear(this FileInfo file, char separator)
{
// read the file, only return true if there are any records,
// and if any record has a different year than the first record
var myRecords = file.ReadMyRecords(separator);
if (myRecords.Any())
{
int firstYear = myRecords.First().Year;
return myRecords.Any(record => record.Year != firstYear);
}
else
{
return false;
}
}
The sequence of file that have more than one year in it is:
allFiles.Where(file => file.IsMultiYear(',');
Put everything in one line:
var allFilesWithMultiYear = new DirectoryInfo(#"C:\MyFileFolder\")
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.Where(file => file.IsMultiYear(',');
By creating two fairly simple extension methods your problem became one highly readable statement.
I got the nuget package ID3.NET from nuget. I would like to sort the mp3 files by their artist. But I don't really know, what is the right way, to use the files with the mp3 info.
DirectoryInfo rootDir = rootDir.GetFiles("*.mp3", SearchOption.AllDirectories);
foreach (FileInfo dir in fileList)
{
Console.WriteLine("{0}", dir);
using (var mp3 = new Mp3File(dir.FullName))
{
Id3Tag tag = mp3.GetTag(Id3TagFamily.FileStartTag);
Console.WriteLine("Title: {0}", tag.Title.Value);
Console.WriteLine("Artist: {0}", tag.Artists.Value);
Console.WriteLine("Album: {0}", tag.Album.Value);
}
}
To sort the file by e.g. their size, I use this working code.
fileList = rootDir.GetFiles(".", SearchOption.AllDirectories)
.Where(s => Worker.sExtensionFilerStrings
.Contains(Path.GetExtension(s.ToString()).ToLower()))
.OrderBy(jo => jo.Name);
Here is your example expanded minimally to allow sorting by a Tag:
string[] musicFiles = Directory.GetFiles(#"D:\mp3test", "*.mp3");
// a structure to hold the filename and the tags object:
public struct mp3FileTags
{
public string fileName { get; set; }
public Id3Tag tags { get; set; }
public mp3FileTags(string fn, Id3Tag t)
{ this = new mp3FileTags(); fileName = fn; tags = t; }
}
// we create a list of the mp3FileTags objects:
List<mp3FileTags> mp3s = new List<mp3FileTags>();
foreach (string musicFile in musicFiles)
{
using (var mp3 = new Mp3File(musicFile))
{
Id3Tag tag = mp3.GetTag(Id3TagFamily.FileStartTag);
mp3s.Add(new mp3FileTags(musicFile, tag)); // fill the list
}
}
// now we can order it:
mp3s = mp3s.OrderBy(o => o.tags.Artists.ToString()).ToList();
// now we can use the ordered list:
foreach (mp3FileTags FTag in mp3s)
{
Id3Tag tag = FTag.tags;
Console.WriteLine("File: {0}", FTag.fileName);
Console.WriteLine("Title: {0}", tag.Title.Value.);
Console.WriteLine("Artist: {0}", tag.Artists.Value);
Console.WriteLine("Album: {0}", tag.Album.Value);
}
You may note how I avoid using arrays for List
BTW: I'm not convinced whether I'd be going with ID3.Lib; it seems to be undocumented and abandoned..
Update I have replaced the direct Id3Tag in the List by a structure, that contains the filename and the tags. You could include more data there e.g. a FileInfo object..
I have very simple code which explains itself.
List<string> Files = new List<string>( Directory.EnumerateFiles(PathLocation));
However I now wish to make life complicated and I have a file object.
public class File
{
public int FileId { get; set; }
public string Filename { get; set; }
}
Is there an optimal way to populate the string property of the class, ie is there a better way than using a foreach loop or similar?
Sure:
List<File> Files = Directory.EnumerateFiles(PathLocation).Select(f=> new File { FileId = /*...*/, Filename = f }).ToList();
You can use LINQ Select to replace foreach loop:
List<File> files = Files.Select(s => new File() { FileId = id, Filename = s})
.ToList();
But needless to create new List to optimize your code:
List<File> files = Directory.EnumerateFiles(PathLocation)
.Select(s => new File() { FileId = id, Filename = s})
.ToList();
MSDN is here
You can map the contents of Files into a List<File>:
var files = Files.Select(f => new File { Filename = f })
.ToList();
The same using LINQ syntax:
var query = from f
in Files
select new File { Filename = f };
var files = query.ToList();
List<File> bigList;
var stringList = bigList.Select(f=>f.Filename);