Complete rework of output of fs ls command output.

This commit is contained in:
2025-08-20 07:10:06 +01:00
parent d143cbee42
commit 9fa5a1b62e
5 changed files with 160 additions and 47 deletions

View File

@@ -0,0 +1,71 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Ls.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Commands.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using Aaru.CommonTypes.Structs;
namespace Aaru.Core;
public static class FileAttributesExtensions
{
/// <summary>
/// Returns a 19-character string representation of the attributes
/// </summary>
public static string ToAttributeChars(this FileAttributes flags)
{
char[] attr = new char[19];
for(int i = 0; i < attr.Length; i++) attr[i] = '.';
(FileAttributes Flag, int Slot, char Symbol)[] attrs =
[
(FileAttributes.AppendOnly, 1, 'a'), (FileAttributes.Alias, 0, 'l'), (FileAttributes.Archive, 1, 'A'),
(FileAttributes.BlockDevice, 0, 'b'), (FileAttributes.Bundle, 0, 'B'),
(FileAttributes.CharDevice, 0, 'c'), (FileAttributes.Compressed, 5, 'z'),
(FileAttributes.Device, 0, 'v'), (FileAttributes.Directory, 0, 'd'), (FileAttributes.Encrypted, 6, 'e'),
(FileAttributes.Extents, 7, 'e'), (FileAttributes.FIFO, 0, 'F'), (FileAttributes.File, 0, 'f'),
(FileAttributes.HasBeenInited, 8, 'i'), (FileAttributes.HasCustomIcon, 9, 'c'),
(FileAttributes.HasNoINITs, 8, 'n'), (FileAttributes.Hidden, 4, 'H'),
(FileAttributes.Immutable, 2, 'i'), (FileAttributes.IndexedDirectory, 1, 'i'),
(FileAttributes.Inline, 11, 'i'), (FileAttributes.IntegrityStream, 12, 'i'),
(FileAttributes.IsOnDesk, 13, 'd'), (FileAttributes.Journaled, 14, 'j'),
(FileAttributes.NoAccessTime, 15, 'a'), (FileAttributes.NoCopyOnWrite, 16, 'w'),
(FileAttributes.NoDump, 1, 'd'), (FileAttributes.Password, 17, 'p'), (FileAttributes.ReadOnly, 2, 'R'),
(FileAttributes.ReparsePoint, 0, 'r'), (FileAttributes.Sparse, 18, 's'),
(FileAttributes.Shadow, 0, 'l'), (FileAttributes.Stationery, 0, 't'), (FileAttributes.Symlink, 0, 'l'),
(FileAttributes.System, 3, 'S'), (FileAttributes.TopDirectory, 0, 'T'),
(FileAttributes.Undeletable, 2, 'u'), (FileAttributes.Pipe, 0, 'p'), (FileAttributes.Socket, 0, 's'),
(FileAttributes.Deleted, 2, 'd'), (FileAttributes.Executable, 10, 'x')
];
// 4) Post-process extras: only overwrite slots still .
foreach((FileAttributes flag, int slot, char symbol) in attrs)
if(slot >= 0 && slot < attr.Length && attr[slot] == '.' && flags.HasFlag(flag))
attr[slot] = symbol;
return new string(attr);
}
}

View File

@@ -6111,5 +6111,11 @@ namespace Aaru.Localization {
return ResourceManager.GetString("Running_in_0_architecture", resourceCulture);
}
}
public static string Title_Date_modified {
get {
return ResourceManager.GetString("Title_Date_modified", resourceCulture);
}
}
}
}

View File

@@ -2154,8 +2154,8 @@ Probadores:
<value>[bold][blue]ATIP:[/][/]</value>
</data>
<data name="Title_Attributes" xml:space="preserve">
<value>Atributos</value>
</data>
<value>[bold][gold3]Atributos[/][/]</value>
</data>
<data name="Title_Author" xml:space="preserve">
<value>Autor</value>
</data>
@@ -3053,4 +3053,7 @@ Probadores:
<data name="Unable_to_get_separate_tracks_not_checksumming_them" xml:space="preserve">
<value>[yellow]No se pudieron separar las pistas, no se harán checksum de ellas[/]</value>
</data>
<data name="Title_Date_modified" xml:space="preserve">
<value>[bold][dodgerblue1]Fecha modificación[/][/]</value>
</data>
</root>

View File

@@ -2239,8 +2239,8 @@ Testers:
<value>Last write</value>
</data>
<data name="Title_Attributes" xml:space="preserve">
<value>Attributes</value>
</data>
<value>[bold][gold3]Attributes[/][/]</value>
</data>
<data name="Title_GID" xml:space="preserve">
<value>GID</value>
</data>
@@ -3132,4 +3132,7 @@ Do you want to continue?</value>
<data name="Running_in_0_architecture" xml:space="preserve">
<value>Running in {0} architecture</value>
</data>
<data name="Title_Date_modified" xml:space="preserve">
<value>[bold][dodgerblue1]Date modified[/][/]</value>
</data>
</root>

View File

@@ -249,7 +249,7 @@ sealed class LsCommand : Command<LsCommand.Settings>
if(error == ErrorNumber.NoError)
{
ListFilesInDir("/", fs, settings.LongFormat);
ListFilesInDir("/", fs);
Statistics.AddFilesystem(fs.Metadata.Type);
}
@@ -278,7 +278,7 @@ sealed class LsCommand : Command<LsCommand.Settings>
if(error == ErrorNumber.NoError)
{
ListFilesInDir("/", fs, settings.LongFormat);
ListFilesInDir("/", fs);
Statistics.AddFilesystem(fs.Metadata.Type);
}
@@ -299,7 +299,7 @@ sealed class LsCommand : Command<LsCommand.Settings>
return (int)ErrorNumber.NoError;
}
static void ListFilesInDir(string path, [NotNull] IReadOnlyFilesystem fs, bool longFormat)
static void ListFilesInDir(string path, [NotNull] IReadOnlyFilesystem fs)
{
ErrorNumber error = ErrorNumber.InvalidArgument;
IDirNode node = null;
@@ -339,61 +339,95 @@ sealed class LsCommand : Command<LsCommand.Settings>
fs.CloseDir(node);
});
var table = new Table();
table.Border(TableBorder.None);
table.AddColumn(new TableColumn($"[underline]{UI.Title_Attributes}[/]")
{
NoWrap = true,
Alignment = Justify.Right
});
table.AddColumn(new TableColumn($"[underline]{UI.Title_Size}[/]")
{
NoWrap = true,
Alignment = Justify.Right,
Width = 12
});
table.AddColumn(new TableColumn($"[underline]{UI.Title_Date_modified}[/]")
{
NoWrap = true,
Alignment = Justify.Center,
Width = 20
});
table.AddColumn(new TableColumn($"[underline]{UI.Title_Name}[/]")
{
NoWrap = true,
Alignment = Justify.Left
});
foreach(KeyValuePair<string, FileEntryInfo> entry in
stats.OrderBy(e => e.Value?.Attributes.HasFlag(FileAttributes.Directory) == false))
{
if(longFormat)
if(entry.Value != null)
{
if(entry.Value != null)
if(entry.Value.Attributes.HasFlag(FileAttributes.Directory))
{
if(entry.Value.Attributes.HasFlag(FileAttributes.Directory))
table.AddRow($"[gold3]{entry.Value.Attributes.ToAttributeChars()}[/]",
"",
"",
$"[green]{Markup.Escape(entry.Key)}[/]");
AaruLogging.Information($"{entry.Value.Attributes.ToAttributeChars()} {entry.Key}");
}
else
{
table.AddRow($"[gold3]{entry.Value.Attributes.ToAttributeChars()}[/]",
$"[lime]{entry.Value.Length}[/]",
$"[dodgerblue1]{entry.Value.LastWriteTimeUtc:s}[/]",
$"[green]{Markup.Escape(entry.Key)}[/]");
AaruLogging
.Information($"{entry.Value.Attributes.ToAttributeChars()} {entry.Value.Length} {entry.Value.LastWriteTimeUtc:s} {entry.Key}");
}
error = fs.ListXAttr(path + "/" + entry.Key, out List<string> xattrs);
if(error != ErrorNumber.NoError) continue;
foreach(string xattr in xattrs)
{
byte[] xattrBuf = [];
error = fs.GetXattr(path + "/" + entry.Key, xattr, ref xattrBuf);
if(error == ErrorNumber.NoError)
{
AaruLogging.WriteLine("{0, 10:d} {0, 12:T} {1, -20} {2}",
$"[dodgerblue1]{entry.Value.CreationTimeUtc}[/]",
UI.Directory_abbreviation,
$"[teal]{Markup.Escape(entry.Key)}[/]");
}
else
{
AaruLogging.WriteLine("{0, 10:d} {0, 12:T} {1, 6}{2, 14:N0} {3}",
$"[dodgerblue1]{entry.Value.CreationTimeUtc}[/]",
$"[fuchsia]{entry.Value.Inode}[/]",
$"[lime]{entry.Value.Length}[/]",
$"[teal]{Markup.Escape(entry.Key)}[/]");
}
table.AddRow("", $"[lime]{xattrBuf.Length}[/]", "", $"[fuchsia]{Markup.Escape(xattr)}[/]");
error = fs.ListXAttr(path + "/" + entry.Key, out List<string> xattrs);
if(error != ErrorNumber.NoError) continue;
foreach(string xattr in xattrs)
{
byte[] xattrBuf = [];
error = fs.GetXattr(path + "/" + entry.Key, xattr, ref xattrBuf);
if(error == ErrorNumber.NoError)
AaruLogging.WriteLine("\t\t[orange3]{0}[/]\t{1:##,#}",
Markup.Escape(xattr),
xattrBuf.Length);
AaruLogging.Information($"{xattrBuf.Length} {xattr}");
}
}
else
AaruLogging.WriteLine("{0, 47}{1}", string.Empty, Markup.Escape(entry.Key));
}
else
{
AaruLogging.WriteLine(entry.Value?.Attributes.HasFlag(FileAttributes.Directory) == true
? "[green]{0}/[/]"
: "[teal]{0}[/]",
Markup.Escape(entry.Key));
table.AddRow("", "", "", $"[green]{Markup.Escape(entry.Key)}[/]");
AaruLogging.Information(entry.Key);
}
}
AnsiConsole.Write(table);
AaruLogging.WriteLine();
foreach(KeyValuePair<string, FileEntryInfo> subdirectory in
stats.Where(e => e.Value?.Attributes.HasFlag(FileAttributes.Directory) == true))
ListFilesInDir(path + "/" + subdirectory.Key, fs, longFormat);
ListFilesInDir(path + "/" + subdirectory.Key, fs);
}
#region Nested type: Settings
@@ -404,10 +438,6 @@ sealed class LsCommand : Command<LsCommand.Settings>
[CommandOption("-e|--encoding")]
[DefaultValue(null)]
public string Encoding { get; init; }
[Description("Use long format.")]
[CommandOption("-l|--long-format")]
[DefaultValue(true)]
public bool LongFormat { get; init; }
[Description("Comma separated name=value pairs of options to pass to filesystem plugin.")]
[CommandOption("-O|--options")]
[DefaultValue(null)]