diff --git a/Aaru.Core/FileAttributesExtensions.cs b/Aaru.Core/FileAttributesExtensions.cs new file mode 100644 index 000000000..7709b8100 --- /dev/null +++ b/Aaru.Core/FileAttributesExtensions.cs @@ -0,0 +1,71 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Ls.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Natalia Portillo +// ****************************************************************************/ + +using Aaru.CommonTypes.Structs; + +namespace Aaru.Core; + +public static class FileAttributesExtensions +{ + /// + /// Returns a 19-character string representation of the attributes + /// + 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); + } +} \ No newline at end of file diff --git a/Aaru.Localization/UI.Designer.cs b/Aaru.Localization/UI.Designer.cs index 9b1b1960b..4d45e36c1 100644 --- a/Aaru.Localization/UI.Designer.cs +++ b/Aaru.Localization/UI.Designer.cs @@ -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); + } + } } } diff --git a/Aaru.Localization/UI.es.resx b/Aaru.Localization/UI.es.resx index 40378219e..048e658fe 100644 --- a/Aaru.Localization/UI.es.resx +++ b/Aaru.Localization/UI.es.resx @@ -2154,8 +2154,8 @@ Probadores: [bold][blue]ATIP:[/][/] - Atributos - + [bold][gold3]Atributos[/][/] + Autor @@ -3053,4 +3053,7 @@ Probadores: [yellow]No se pudieron separar las pistas, no se harán checksum de ellas[/] + + [bold][dodgerblue1]Fecha modificación[/][/] + \ No newline at end of file diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx index ed8a39788..954828d40 100644 --- a/Aaru.Localization/UI.resx +++ b/Aaru.Localization/UI.resx @@ -2239,8 +2239,8 @@ Testers: Last write - Attributes - + [bold][gold3]Attributes[/][/] + GID @@ -3132,4 +3132,7 @@ Do you want to continue? Running in {0} architecture + + [bold][dodgerblue1]Date modified[/][/] + \ No newline at end of file diff --git a/Aaru/Commands/Filesystem/Ls.cs b/Aaru/Commands/Filesystem/Ls.cs index 44f89ee51..321b4eabd 100644 --- a/Aaru/Commands/Filesystem/Ls.cs +++ b/Aaru/Commands/Filesystem/Ls.cs @@ -249,7 +249,7 @@ sealed class LsCommand : Command if(error == ErrorNumber.NoError) { - ListFilesInDir("/", fs, settings.LongFormat); + ListFilesInDir("/", fs); Statistics.AddFilesystem(fs.Metadata.Type); } @@ -278,7 +278,7 @@ sealed class LsCommand : Command if(error == ErrorNumber.NoError) { - ListFilesInDir("/", fs, settings.LongFormat); + ListFilesInDir("/", fs); Statistics.AddFilesystem(fs.Metadata.Type); } @@ -299,7 +299,7 @@ sealed class LsCommand : Command 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 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 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 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 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 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 [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)]