- FtpListParser: tolerant DOS/IIS regex (optional AM/PM, 2-or-4-digit year, 1-or-2-digit hour) for 24h/locale-dependent Mazak servers; NLST fallback now only accepts single-token lines (multi-token garbage/header -> skip) to avoid whole-line-as-filename. - MazakMachine.BuildTreeAsync: root-level LIST failure now propagates -> Fail (no more silent exit 0 on a diagnostic probe). Children keep best-effort inline "errore lista" swallow. Depth semantics preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
3.6 KiB
C#
91 lines
3.6 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace NcProgramManager.Cnc.Mazak
|
|
{
|
|
/// <summary>
|
|
/// Parsing puro (senza I/O) delle righe LIST/ListDirectoryDetails FTP dei
|
|
/// controlli Mazak Smooth/Matrix. Supporta formato Unix (drwx...) e DOS/IIS
|
|
/// (MM-DD-YY ... <DIR>), piu' fallback NLST (solo nome).
|
|
/// </summary>
|
|
internal static class FtpListParser
|
|
{
|
|
/// <summary>Assicura path con slash iniziale e finale per uso come directory.</summary>
|
|
public static string NormalizeDir(string path)
|
|
{
|
|
string dir = string.IsNullOrEmpty(path) ? "/" : path;
|
|
if (!dir.StartsWith("/")) dir = "/" + dir;
|
|
if (!dir.EndsWith("/")) dir += "/";
|
|
return dir;
|
|
}
|
|
|
|
// DOS/IIS: "07-18-26 10:00AM <DIR> NAME"
|
|
// "07-18-26 10:00AM 1234 FILE.EIA"
|
|
private static readonly Regex _dosLine = new Regex(
|
|
@"^\d{2}-\d{2}-\d{2,4}\s+\d{1,2}:\d{2}\s*(AM|PM)?\s+(?<dir><DIR>|\d+)\s+(?<name>.+)$",
|
|
RegexOptions.IgnoreCase);
|
|
|
|
// Whitespace chars used to detect multi-token (non-NLST) lines in fallback.
|
|
private static readonly char[] _whitespace = { ' ', '\t', '\v', '\f' };
|
|
|
|
// Unix: "drwxr-xr-x 1 owner group 4096 Jul 18 10:00 NAME"
|
|
private static readonly Regex _unixLine = new Regex(
|
|
@"^(?<perm>[dl\-][rwxsStT\-]{9})\s+\d+\s+\S+\s+\S+\s+(?<size>\d+)\s+\S+\s+\S+\s+\S+\s+(?<name>.+)$");
|
|
|
|
/// <summary>Parsa una riga LIST. Ritorna null se non riconosciuta o '.'/'..'.</summary>
|
|
public static FtpEntry Parse(string line, string dir)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line)) return null;
|
|
|
|
string name = null;
|
|
bool isDir = false;
|
|
long size = -1;
|
|
|
|
var dos = _dosLine.Match(line);
|
|
if (dos.Success)
|
|
{
|
|
name = dos.Groups["name"].Value.Trim();
|
|
if (string.Equals(dos.Groups["dir"].Value, "<DIR>", StringComparison.OrdinalIgnoreCase))
|
|
isDir = true;
|
|
else
|
|
long.TryParse(dos.Groups["dir"].Value, out size);
|
|
}
|
|
else
|
|
{
|
|
var unix = _unixLine.Match(line);
|
|
if (unix.Success)
|
|
{
|
|
name = unix.Groups["name"].Value.Trim();
|
|
isDir = unix.Groups["perm"].Value[0] == 'd';
|
|
if (!isDir)
|
|
long.TryParse(unix.Groups["size"].Value, NumberStyles.Integer,
|
|
CultureInfo.InvariantCulture, out size);
|
|
}
|
|
else
|
|
{
|
|
// Fallback: NLST-style (bare name only). Only a single token
|
|
// (no internal whitespace) is a real name-only listing; a line
|
|
// with internal whitespace that matched neither regex is an
|
|
// unrecognized header/garbage -> skip (avoid whole-line-as-name).
|
|
string trimmed = line.Trim();
|
|
if (trimmed.IndexOfAny(_whitespace) >= 0)
|
|
return null;
|
|
name = trimmed;
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(name) || name == "." || name == "..")
|
|
return null;
|
|
|
|
return new FtpEntry
|
|
{
|
|
Name = name,
|
|
FullPath = dir + name,
|
|
IsDirectory = isDir,
|
|
Size = size,
|
|
Raw = line
|
|
};
|
|
}
|
|
}
|
|
}
|