// edtFTPnet
//
// Copyright (C) 2004 Enterprise Distributed Technologies Ltd
//
// www.enterprisedt.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Bug fixes, suggestions and comments should posted on
// http://www.enterprisedt.com/forums/index.php
//
// Change Log:
//
// $Log: VMSFileParser.cs,v $
// Revision 1.11 2009-06-22 09:08:34 bruceb
// better vms detection
//
// Revision 1.10 2008-12-02 07:47:49 bruceb
// better detection
//
// Revision 1.9 2008-07-15 05:42:57 bruceb
// refactor parsing code
//
// Revision 1.8 2007-05-23 00:29:36 hans
// Added TimeIncludesSeconds property.
//
// Revision 1.7 2007-03-16 04:57:24 bruceb
// various fixes re directories and more customizable
//
// Revision 1.6 2007/02/20 00:21:07 bruceb
// fix to cope with VMS listings that don't have a group, i.e. [USER]
//
// Revision 1.5 2006/06/28 22:18:46 hans
// Tidied up imports
//
// Revision 1.3 2006/06/22 12:37:38 bruceb
// use string.split
//
// Revision 1.2 2005/06/03 21:23:22 bruceb
// comment tweak
//
// Revision 1.1 2005/06/03 11:32:47 bruceb
// vms changes
//
//
using System;
using System.Globalization;
using System.Text;
using System.ComponentModel;
using EnterpriseDT.Util;
using EnterpriseDT.Util.Debug;
namespace EnterpriseDT.Net.Ftp{
/// <summary>
/// Represents a remote OpenVMS file parser
/// </summary>
/// <author>Bruce Blackshaw
/// </author>
/// <version>$Revision: 1.11 $</version>
/// <remarks>Hacked and modified from some helpful source provided by Jason Schultz</remarks>
public class VMSFileParser:FTPFileParser
{
/// <summary>Directory field</summary>
private static readonly string DIR = ".DIR";
/// <summary>Directory field</summary>
private static readonly string HDR = "Directory";
/// <summary>Total field</summary>
private static readonly string TOTAL = "Total";
/// <summary> Logging object</summary>
private Logger log = Logger.GetLogger("VMSFileParser");
/// <summary> Number of expected fields</summary>
private const int DEFAULT_BLOCKSIZE = 512*1024;
/// <summary> Number of expected fields</summary>
private static readonly int MIN_EXPECTED_FIELD_COUNT = 4;
private bool versionInName = false;
private int blocksize = DEFAULT_BLOCKSIZE;
/// <summary>
/// Get flag indicating whether or not the most recent parse returned seconds.
/// </summary>
public override bool TimeIncludesSeconds
{
get { return false; }
}
/// <summary>
/// Is the version number returned as part of the filename?
/// </summary>
/// <remarks>
/// Some VMS FTP servers do not permit a file to be deleted unless
/// the filename includes the version number. Note that directories are
/// never returned with the version number.
/// </remarks>
[DefaultValue(false)]
public bool VersionInName
{
get
{
return versionInName;
}
set
{
this.versionInName = value;
}
}
/// <summary>
/// Get and set the blocksize used to calculate the file
/// size.
/// </summary>
/// <remarks>
/// The blocksize is multiplied by the reported size to obtain
/// the actual size.
/// </remarks>
[DefaultValue(DEFAULT_BLOCKSIZE)]
public int BlockSize
{
get
{
return blocksize;
}
set
{
this.blocksize = value;
}
}
/// <summary>
/// Does this parser parse multiple lines to get one listing?
/// </summary>
/// <returns>true or false</returns>
public override bool IsMultiLine()
{
return true;
}
public override string ToString()
{
return "VMS";
}
/// <summary>
/// Test for valid format for this parser
/// </summary>
/// <param name="listing">listing to test</param>
/// <returns>true if valid</returns>
public override bool IsValidFormat(String[] listing)
{
int count = Math.Min(listing.Length, 10);
bool semiColonName = false;
bool comma = false;
bool squareBracketStart = false, squareBracketEnd = false;
for (int i = 0; i < count; i++)
{
if (listing[i].Trim().Length == 0)
continue;
int pos = 0;
if ((pos = listing[i].IndexOf(';')) > 0 && (++pos < listing[i].Length) &&
Char.IsDigit(listing[i][pos]))
semiColonName = true;
if (listing[i].IndexOf(',') > 0)
comma = true;
if (listing[i].IndexOf('[') > 0)
squareBracketStart = true;
if (listing[i].IndexOf(']') > 0)
squareBracketEnd = true;
}
if (semiColonName && comma && squareBracketStart && squareBracketEnd)
return true;
log.Debug("Not in VMS format");
return false;
}
/// <summary> Parse server supplied string</summary>
/// <param name="raw">raw string to parse</param>
/// <returns>FTPFile object representing the raw string</returns>
/// <remarks>Listing look like the below:
/// OUTPUT:
///
/// Directory dirname
///
/// filename
/// used/allocated dd-MMM-yyyy HH:mm:ss [group,owner] (PERMS)
/// filename
/// used/allocated dd-MMM-yyyy HH:mm:ss [group,owner] (PERMS)
/// ...
///
/// Total of n files, n/m blocks
/// </remarks>
public override FTPFile Parse(string raw)
{
string[] fields = StringSplitter.Split(raw);
// skip blank lines
if(fields.Length <= 0)
return null;
// skip line which lists Directory
if (fields.Length >= 2 && fields[0].Equals(HDR))
return null;
// skip line which lists Total
if (fields.Length > 0 && fields[0].Equals(TOTAL))
return null;
if (fields.Length < MIN_EXPECTED_FIELD_COUNT)
return null;
// first field is name
string name = fields[0];
// make sure it is the name (ends with ';<INT>')
int semiPos = name.LastIndexOf(';');
// check for ;
if(semiPos <= 0)
{
log.Warn("File version number not found in name '" + name + "'");
return null;
}
string nameNoVersion = name.Substring(0, semiPos);
// check for version after ;
string afterSemi = fields[0].Substring(semiPos+1);
try
{
Int64.Parse(afterSemi);
// didn't throw exception yet, must be number
// we don't use it currently but we might in future
}
catch(FormatException)
{
// don't worry about version number
}
// test is dir
bool isDir = false;
if (nameNoVersion.EndsWith(DIR))
{
isDir = true;
name = nameNoVersion.Substring(0, nameNoVersion.Length-DIR.Length);
}
if (!versionInName && !isDir)
{
name = nameNoVersion;
}
// 2nd field is size USED/ALLOCATED format, or perhaps just USED
int slashPos = fields[1].IndexOf('/');
string sizeUsed = fields[1];
if (slashPos > 0)
sizeUsed = fields[1].Substring(0, slashPos);
long size = Int64.Parse(sizeUsed) * blocksize;
// 3 & 4 fields are date time
string lastModifiedStr = TweakDateString(fields);
DateTime lastModified = DateTime.MinValue;
try
{
lastModified = DateTime.Parse(lastModifiedStr.ToString(), ParsingCulture.DateTimeFormat);
}
catch (FormatException)
{
log.Warn("Failed to parse date string '" + lastModifiedStr + "'");
}
// 5th field is [group,owner]
string group = null;
string owner = null;
if (fields.Length >= 5)
{
if (fields[4][0] == '[' && fields[4][fields[4].Length-1] == ']')
{
int commaPos = fields[4].IndexOf(',');
if (commaPos < 0)
{
owner = fields[4].Substring(1, fields[4].Length-2);
group = "";
}
else
{
group = fields[4].Substring(1, commaPos-1);
owner = fields[4].Substring(commaPos+1, fields[4].Length-commaPos-2);
}
}
}
// 6th field is permissions e.g. (RWED,RWED,RE,)
string permissions = null;
if (fields.Length >= 6)
{
if (fields[5][0] == '(' && fields[5][fields[5].Length-1] == ')')
{
permissions = fields[5].Substring(1, fields[5].Length-2);
}
}
FTPFile file = new FTPFile(FTPFile.VMS, raw, name, size, isDir, ref lastModified);
file.Group = group;
file.Owner = owner;
file.Permissions = permissions;
return file;
}
/// <summary> Tweak the date string to make the month camel case</summary>
/// <param name="fields">array of fields</param>
private string TweakDateString(string[] fields)
{
// convert the last 2 chars of month to lower case
StringBuilder lastModifiedStr = new StringBuilder();
bool monthFound = false;
for (int i = 0; i < fields[2].Length; i++)
{
if (!Char.IsLetter(fields[2][i]))
{
lastModifiedStr.Append(fields[2][i]);
}
else
{
if (!monthFound)
{
lastModifiedStr.Append(fields[2][i]);
monthFound = true;
}
else
{
lastModifiedStr.Append(Char.ToLower(fields[2][i]));
}
}
}
lastModifiedStr.Append(" ").Append(fields[3]);
return lastModifiedStr.ToString();
}
}
}
|