Parsing FtpWebRequest ListDirectoryDetails line - c#

I need some help with parsing the response from ListDirectoryDetails in C#.
I only need the following fields.
File Name/Directory Name
Date Created
and the File Size.
Here's what some of the lines look like when I run ListDirectoryDetails:
d--x--x--x 2 ftp ftp 4096 Mar 07 2002 bin
-rw-r--r-- 1 ftp ftp 659450 Jun 15 05:07 TEST.TXT
-rw-r--r-- 1 ftp ftp 101786380 Sep 08 2008 TEST03-05.TXT
drwxrwxr-x 2 ftp ftp 4096 May 06 12:24 dropoff
Thanks in advance.

Not sure if you still need this, but this is the solution i came up with:
Regex regex = new Regex ( #"^([d-])([rwxt-]{3}){3}\s+\d{1,}\s+.*?(\d{1,})\s+(\w+\s+\d{1,2}\s+(?:\d{4})?)(\d{1,2}:\d{2})?\s+(.+?)\s?$",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace );
Match Groups:
object type:
d : directory
- : file
Array[3] of permissions (rwx-)
File Size
Last Modified Date
Last Modified Time
File/Directory Name

For this specific listing, the following code will do:
var request = (FtpWebRequest)WebRequest.Create("ftp://ftp.example.com/");
request.Credentials = new NetworkCredential("user", "password");
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
var reader = new StreamReader(request.GetResponse().GetResponseStream());
string pattern =
#"^([\w-]+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+" +
#"(\w+\s+\d+\s+\d+|\w+\s+\d+\s+\d+:\d+)\s+(.+)$";
Regex regex = new Regex(pattern);
IFormatProvider culture = CultureInfo.GetCultureInfo("en-us");
string[] hourMinFormats =
new[] { "MMM dd HH:mm", "MMM dd H:mm", "MMM d HH:mm", "MMM d H:mm" };
string[] yearFormats =
new[] { "MMM dd yyyy", "MMM d yyyy" };
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
Match match = regex.Match(line);
string permissions = match.Groups[1].Value;
int inode = int.Parse(match.Groups[2].Value, culture);
string owner = match.Groups[3].Value;
string group = match.Groups[4].Value;
long size = long.Parse(match.Groups[5].Value, culture);
string s = Regex.Replace(match.Groups[6].Value, #"\s+", " ");
string[] formats = (s.IndexOf(':') >= 0) ? hourMinFormats : yearFormats;
var modified = DateTime.ParseExact(s, formats, culture, DateTimeStyles.None);
string name = match.Groups[7].Value;
Console.WriteLine(
"{0,-16} permissions = {1} size = {2, 9} modified = {3}",
name, permissions, size, modified.ToString("yyyy-MM-dd HH:mm"));
}
You will get (as of year 2016):
bin permissions = d--x--x--x size = 4096 modified = 2002-03-07 00:00
TEST.TXT permissions = -rw-r--r-- size = 659450 modified = 2016-06-15 05:07
TEST03-05.TXT permissions = -rw-r--r-- size = 101786380 modified = 2008-09-08 00:00
dropoff permissions = drwxrwxr-x size = 4096 modified = 2016-05-06 12:24
But, actually trying to parse the listing returned by the ListDirectoryDetails is not the right way to go.
You want to use an FTP client that supports the modern MLSD command that returns a directory listing in a machine-readable format specified in the RFC 3659. Parsing the human-readable format returned by the ancient LIST command (used internally by the FtpWebRequest for its ListDirectoryDetails method) should be used as the last resort option, when talking to obsolete FTP servers, that do not support the MLSD command (like the Microsoft IIS FTP server).
Many servers use a different format for the LIST command response. Particularly IIS can use DOS format. See C# class to parse WebRequestMethods.Ftp.ListDirectoryDetails FTP response.
For example with WinSCP .NET assembly, you can use its Session.ListDirectory or Session.EnumerateRemoteFiles methods.
They internally use the MLSD command, but can fall back to the LIST command and support dozens of different human-readable listing formats.
The returned listing is presented as collection of RemoteFileInfo instances with properties like:
Name
LastWriteTime (with correct timezone)
Length
FilePermissions (parsed into individual rights)
Group
Owner
IsDirectory
IsParentDirectory
IsThisDirectory
(I'm the author of WinSCP)
Most other 3rd party libraries will do the same. Using the FtpWebRequest class is not reliable for this purpose. Unfortunately, there's no other built-in FTP client in the .NET framework.

Building on the regex idea of Ryan Conrad, this is my final reading code:
protected static Regex m_FtpListingRegex = new Regex(#"^([d-])((?:[rwxt-]{3}){3})\s+(\d{1,})\s+(\w+)?\s+(\w+)?\s+(\d{1,})\s+(\w+)\s+(\d{1,2})\s+(\d{4})?(\d{1,2}:\d{2})?\s+(.+?)\s?$",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
protected static readonly String Timeformat = "MMM dd yyyy HH:mm";
/// <summary>
/// Handles file info given in the form of a string in standard unix ls output format.
/// </summary>
/// <param name="filesListing">The file listing string.</param>
/// <returns>A list of FtpFileInfo objects</returns>
public static List<FtpFileInfo> GetFilesListFromFtpListingUnix(String filesListing)
{
List<FtpFileInfo> files = new List<FtpFileInfo>();
MatchCollection matches = m_FtpListingRegex.Matches(filesListing);
if (matches.Count == 0 && filesListing.Trim('\r','\n','\t',' ').Length != 0)
return null; // parse error. Could throw some kind of exception here too.
foreach (Match match in matches)
{
FtpFileInfo fileInfo = new FtpFileInfo();
Char dirchar = match.Groups[1].Value.ToLowerInvariant()[0];
fileInfo.IsDirectory = dirchar == 'd';
fileInfo.Permissions = match.Groups[2].Value.ToCharArray();
// No clue what "inodes" actually means...
Int32 inodes;
fileInfo.NrOfInodes = Int32.TryParse(match.Groups[3].Value, out inodes) ? inodes : 1;
fileInfo.User = match.Groups[4].Success ? match.Groups[4].Value : null;
fileInfo.Group = match.Groups[5].Success ? match.Groups[5].Value : null;
Int64 fileSize;
Int64.TryParse(match.Groups[6].Value, out fileSize);
fileInfo.FileSize = fileSize;
String month = match.Groups[7].Value;
String day = match.Groups[8].Value.PadLeft(2, '0');
String year = match.Groups[9].Success ? match.Groups[9].Value : DateTime.Now.Year.ToString(CultureInfo.InvariantCulture);
String time = match.Groups[10].Success ? match.Groups[10].Value.PadLeft(5, '0') : "00:00";
String timeString = month + " " + day + " " + year + " " + time;
DateTime lastModifiedDate;
if (!DateTime.TryParseExact(timeString, Timeformat, CultureInfo.InvariantCulture, DateTimeStyles.None, out lastModifiedDate))
lastModifiedDate = DateTime.MinValue;
fileInfo.LastModifiedDate = lastModifiedDate;
fileInfo.FileName = match.Groups[11].Value;
files.Add(fileInfo);
}
return files;
}
And the FtpFileInfo class that's filled:
public class FtpFileInfo
{
public Boolean IsDirectory { get; set; }
public Char[] Permissions { get; set; }
public Int32 NrOfInodes { get; set; }
public String User { get; set; }
public String Group { get; set; }
public Int64 FileSize { get; set; }
public DateTime LastModifiedDate { get; set; }
public String FileName { get; set; }
}

This is my algorithm to get the File/Dir name, Date Created, Attribute(File/Dir), Size.
Hope this helps...
FtpWebRequest _fwr = FtpWebRequest.Create(uri) as FtpWebRequest
_fwr.Credentials = cred;
_fwr.UseBinary = true;
_fwr.UsePassive = true;
_fwr.KeepAlive = true;
_fwr.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
StreamReader _sr = new StreamReader(_fwr.GetResponse().GetResponseStream());
List<object> _dirlist = new List<object>();
List<object> _attlist = new List<object>();
List<object> _datelist = new List<object>();
List<long> _szlist = new List<long>();
while (!_sr.EndOfStream)
{
string[] buf = _sr.ReadLine().Split(' ');
//string Att, Dir;
int numcnt = 0, offset = 4; ;
long sz = 0;
for (int i = 0; i < buf.Length; i++)
{
//Count the number value markers, first before the ftp markers and second
//the file size.
if (long.TryParse(buf[i], out sz)) numcnt++;
if (numcnt == 2)
{
//Get the attribute
string cbuf = "", dbuf = "", abuf = "";
if (buf[0][0] == 'd') abuf = "Dir"; else abuf = "File";
//Get the Date
if (!buf[i+3].Contains(':')) offset++;
for (int j = i + 1; j < i + offset; j++)
{
dbuf += buf[j];
if (j < buf.Length - 1) dbuf += " ";
}
//Get the File/Dir name
for (int j = i + offset; j < buf.Length; j++)
{
cbuf += buf[j];
if (j < buf.Length - 1) cbuf += " ";
}
//Store to a list.
_dirlist.Add(cbuf);
_attlist.Add(abuf);
_datelist.Add(dbuf);
_szlist.Add(sz);
offset = 0;
break;
}
}
}

Related

C# get offset for ISO8601 date from Windows settings

I have to maximise the use of ISO8601 dates in a C# project I'm using, especially when it comes to saving dates in the database.
In the same project I am creating Outlook appointments.
List<string> Recipients = new List<string>();
Recipients.Add("person#place.com");
string AppointmentSubject = "test subject";
string AppointmentBody = "test body";
string AppointmentLocation = "test location"; // Where do I get a list of meeting rooms?
string Offset = GetISO8601OffsetForThisMachine();
DateTime AppointmentStart = DateTime.Parse("2016-10-07T08:00:00" + Offset);
DateTime AppointmentEnd = DateTime.Parse("2016-10-07T10:00:00" + Offset);
Boolean IsAtDesk = true;
CreateOutlookAppointment(Recipients, AppointmentLocation, AppointmentSubject, AppointmentBody, AppointmentStart, AppointmentEnd, IsAtDesk);
Being in UK, our offset is either +1 or +0 due to daylight saving.
Is there a way to get the offset programatically based on the Windows locale settings?
You can probably do something like :
DateTime AppointmentStart = DateTime.Parse("2016-10-07T08:00:00").ToLocalTime();
Calculate the offset with the following:
public static string GetISO8601OffsetForThisMachine()
{
string MethodResult = null;
try
{
TimeSpan OffsetTimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now);
string Offset = (OffsetTimeSpan < TimeSpan.Zero ? "-" : "+") + OffsetTimeSpan.ToString(#"hh\:mm");
MethodResult = Offset;
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}

get files from directory based on substring of filename - C#

I have a C# console app which gets all files in a directory. Taking the file name below as an example, and given that I will have 3 strings, ref, year1, year2, how could I say:
File name: 31596_2015-06-30.pdf
Give me all files where the file name contain ref AND file name contains a year between year1 and year 2?
Code so far:
var files = Directory.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly)
.Select(Path.GetFileName);
string start_year = null;
string end_year = null;
string ref = null;
// dr is a sql data reader
if (dr.HasRows == true)
{
while (dr.Read())
{
start_year = dr.GetString(1).Substring(7, 4);
end_year = dr.GetString(2).Substring(7, 4);
ref = dr.GetString(3);
foreach(string filename in files)
{
if (filename.Contains(ref))
{
File.Copy(sourceDir + filename, targetDir + filename);
}
}
File.Copy(sourceDir + dr.GetString(0), targetDir + dr.GetString(0));
}
}
I suggest extracting a class, say, FileData:
public class FileData {
public bool IsParsed {get; private set; }
public String FileName {get; private set; }
public DateTime Date {get; private set; }
public String Ref {get; private set; }
public FileData(String path) {
FileName = path;
if (String.IsNullOrEmpty(path))
return;
int p = path.IndexOf('_');
if (p < 0)
return;
Ref = path.Substring(0, p);
DateTime dt;
IsParsed = DateTime.TryParseExact(path.Substring(p + 1),
"yyyy-MM-dd",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out dt);
Date = dt;
}
}
By doing this you can put a simple Linq to obtain the files
var files = Directory
.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly)
.Select(file => new FileData(file))
.Where(info => info.IsParsed)
.Where(info => String.Equals(info.Ref, myRef))
.Where(info => info.Date.Year >= year1 && info.Date.Year <= year2)
.Select(info => info.FileName);
Assuming that your filenames have always the format as in your post,
you first need to split the year out of the name.
One possible solution could be:
string filenameYear = filename.Split('_')[1].Split('-')[0];
then you can use this string in your if condition to ask for all three cases:
if (filename.Contains(_ref) &&
Convert.ToInt32(filenameYear) > Convert.ToInt32(start_year) &&
Convert.ToInt32(filenameYear) < Convert.ToInt32(end_year))
{
// Do what ever you desire
}
in the line:
File.Copy(sourceDir + filename, targetDir + filename);
If sourceDir and targetDir has not a \ in the end or filename in the beginning. This will crash.
You can use
File.Copy(Path.Combine(sourceDir,filename), Path.combine(targetDir, filename);
to avoid problems
ps. you should avoid using ref as a name for variables in C#.
it is reserved as a keyword
I like Linq for this sort of thing. Unless you know for a fact that the values in the reader will always by what you expect, you should perform some validations, but this will achieve your stated goal with the least amount of changes to your sample code.
var start_year = (DateTime)dr.GetString(1).Substring(7, 4);
var end_year = (DateTime)dr.GetString(2).Substring(7, 4);
ref = dr.GetString(3);
foreach(string filename in files.Where(x => x.StartsWith(ref)
&& (DateTime)x.Substring(7, 4) >= start_year
&& (DateTime)x.Substring(7, 4) <= end_year))
{
File.Copy(sourceDir + filename, targetDir + filename);
}

store date modified properties in array and copy them to another text file line by line C#

I'm building a utility that needs to take the date modified properties from one text file and copy it to another text file. I would prefer to use an array to store the date modified properties of the files. Here is what I have so far:
class NewTime
{
public DateTime Current { get; set; }
}
static void Main(string[] args)
{
int counter = 0;
string line;
// Read the file and display it line by line.
System.IO.StreamReader file = new System.IO.StreamReader(args[0]);
System.IO.StreamWriter filewriter = new System.IO.StreamWriter(args[1], false);
while ((line = file.ReadLine()) != null)
{
Thread.Sleep(10);
string [] pieces = line.Split(new char[] { '|' });
if(pieces.Length == 2)
{
DateTime outDate;
if(DateTime.TryParse(pieces[1], out outDate))
{
string[] listOfFiles = { #"D:\blahB.txt", #"C:\blahA.txt"};
string[] outputLines = listOfFiles.Select(file => file + " | " + file.GetLastWriteTime(file)).ToArray();
file.WriteAllLines("verifications.txt", outputLines);
//string outputstring = string.Format(" {0:yyyy-MM-dd-hh-mm-ss-ff-tt}", DateTime.Now);
//filewriter.WriteLine(pieces[0] + "|" + outputstring);
}
else
filewriter.WriteLine(line);
}
else
filewriter.WriteLine(line);
System.Console.WriteLine(line);
counter++;
}
System.Console.WriteLine(LastWriteLine+"blahblah");
file.Close();
filewriter.Close();
System.Console.ReadLine();
}
public static string LastWriteLine { get; set; }
}
}
Also, I don't need to open the first file I'm getting the date modified properties from.
You can use the File.GetLastWriteTime to get the last modification date of a file without opening it:
string[] listOfFiles = {#"C:\bin\1.txt", #"C:\bin\desktop.png", #"C:\main.c"};
string[] outputLines = listOfFiles.Select(file => file + " | " + File.GetLastWriteTime(file)).ToArray();
File.WriteAllLines("output.txt", outputLines);
Then you get something like this for output in the output.txt:
C:\bin\1.txt | 9/12/2014 12:49:54 PM
C:\bin\desktop.png | 9/6/2014 3:11:27 AM
C:\main.c | 11/24/2013 7:14:38 PM

Finding the user who modified the shared drive folder files

I have a shared drive which is elsewhere on a server. I want to get a notification which gives me the user name of the person who has modified any file present in the shared drive.
Currently I am using the FileSystemWatcher to get the notification and the code provided by Stack overflow question "Find out username(who) modified file in C#" to find the user name.
But Instead I get the name of the computer on which the shared drive is at the moment. I want the username who had modified the file on the shared drive.
My piece of code is :
private string GetSpecificFileProperties(string file, params int[] indexes)
{
string fileName = Path.GetFileName(file);
string folderName = Path.GetDirectoryName(file);
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(folderName);
StringBuilder sb = new StringBuilder();
foreach (Shell32.FolderItem2 item in objFolder.Items())
{
if (fileName == item.Name)
{
for (int i = 0; i < indexes.Length; i++)
{
sb.Append(objFolder.GetDetailsOf(item, indexes[i]) + ",");
}
break;
}
}
string result = sb.ToString().Trim();
if (result.Length == 0)
{
return string.Empty;
}
return result.Substring(0, result.Length - 1);
}
string Type = GetSpecificFileProperties(filePath, 2);
string ObjectKind = GetSpecificFileProperties(filePath, 11);
DateTime CreatedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 4));
DateTime LastModifiedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 3));
DateTime LastAccessDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 5));
string LastUser = GetSpecificFileProperties(filePath, 10);
string ComputerName = GetSpecificFileProperties(filePath, 53);
string FileSize = GetSpecificFileProperties(filePath, 1);
I have got the fix to that,
It can be achieved using the ObjectSecurity Class of .NET. In that we can use the GetOwner.
It fetches the owner of the file who has modified / created a file.
This is the piece of code which would help:
string owner = System.IO.File.GetAccessControl(e.FullPath).GetOwner(typeof(System.Security.Principal.NTAccount)).ToString();
Console.WriteLine(owner);

Extracting text from a file where date -time is the index

I have got around 800 files of maximum 55KB-100KB each where the data is in this format
Date,Time,Float1,Float2,Float3,Float4,Integer
Date is in DD/MM/YYYY format and Time is in the format of HH:MM
Here the date ranges from say 1st May to 1June and each day, the Time varies from 09:00 to 15:30.
I want to run a program so that, for each file, it extracts the data pertaining to a particular given date and writes to a file.
I am trying to get around, to form a to do a search and extract operation. I dont know, how to do it, would like to have some idea.
I have written the code below:
static void Main(string[] args)
{
string destpath = Directory.GetCurrentDirectory();
destpath += "\\DIR";
DirectoryInfo Dest = Directory.CreateDirectory(destpath);
DirectoryInfo Source = new DirectoryInfo(Directory.GetCurrentDirectory() + "\\IEOD");
FileInfo[] fiArr = Source.GetFiles("*.csv");
Console.WriteLine("Search Date:");
string srchdate = Console.ReadLine();
String FileNewLine;
String FileNewdt;
FileInfo r;
foreach (FileInfo f in fiArr)
{
r = new FileInfo(destpath + "\\" + f.Name);
r.Create();
StreamWriter Sw = r.AppendText();
StreamReader Sr = new StreamReader(f.FullName);
while (Sr.Peek() >= 0)
{
FileNewLine = Sr.ReadLine();
FileNewdt = FileNewLine.Substring(0,10);
if (String.Compare(FileNewdt, srchdate, true) == 0)
{
//write it to a file;
Console.WriteLine(FileNewLine);
}
}
}
Console.ReadKey();
}
As of now, it should write into the Console. The writing with the help of StreamWriter will be done later, but I am facing a runtime error. It says, " 'C:\Documents and Settings\Soham Das\Desktop\Test\DIR\ABAN.csv' because it is being used by another process."
Here ABAN is a newly created file, by the code. The problem is faced at StreamWriter Sw = r.AppendText()
Help appreciated.
Thanks
Soham
Now that you have edited the question to show that the delimiter is actually a comma instead of a slash (which would have conflicted with the date format) this becomes a lot easier. I've re-posted the answer from last night below.
// This would come from Stream.ReadLine() or something
string line = "02/06/2010,10:05,1.0,2.0,3.0,4.0,5";
string[] parts = line.Split(',');
DateTime date = DateTime.ParseExact(parts[0], "dd/MM/yyyy", null);
TimeSpan time = TimeSpan.Parse(parts[1]);
date = date.Add(time); // adds the time to the date
float float1 = Single.Parse(parts[2]);
float float2 = Single.Parse(parts[3]);
float float3 = Single.Parse(parts[4]);
float float4 = Single.Parse(parts[5]);
int integer = Int32.Parse(parts[6]);
Console.WriteLine("Date: {0:d}", date);
Console.WriteLine("Time: {0:t}", date);
Console.WriteLine("Float1: {0}", float1);
Console.WriteLine("Float2: {0}", float2);
Console.WriteLine("Float3: {0}", float3);
Console.WriteLine("Float4: {0}", float4);
Console.WriteLine("Integer: {0}", integer);
Obviously you can make it more resilient by adding error handling, using TryParse, etc. But this should give you a basic idea of how to manipulate strings in .NET.
So 800 files with around 100KB sums up to 80 KBytes. So why don't built up a little class like
public class Entry
{
public DateTime Date {get; set;}
public float Float1 {get; set;}
public int Integer1 {get; set;}
public Entry(string values)
{
//ToDo: Parse single line into properties
// e.g. use String.Split, RegEx, etc.
}
}
Also you should take care about implementing GetHashCode() and Equals() (there is a good explanation in the book Essential C#). And you should add the interface IComparable to that class which just makes somethine like
public int CompareTo(Entry rhs)
{
return this.Date.CompareTo(rhs.Date);
}
If you got this you can easily do the following:
var allEntries = new SortedList<Entry>();
string currentLine = null;
using (var streamReader = new StreamReader("C:\\MyFile.txt"))
while ((currentLine = streamReader.ReadLine()) != null)
{
try
{
var entry = new Entry(currentLine);
allEntries.Add(entry);
}
catch (Exception ex)
{
//Do whatever you like
//maybe just
continue;
//or
throw;
}
}
So what's missing is to read in all the files (instead of a single one). But this can be done by another loop on Directory.GetFiles() which maybe itself is looped through a Directory.GetDirectories().
After reading all the files into your List you can do whatever LINQ query comes to your mind.

Categories