using qdr.app.tools.qfu.Commands.Aspects; using Quadarax.Foundation.Core.QConsole; using Quadarax.Foundation.Core.QConsole.Argument; using Quadarax.Foundation.Core.QConsole.Attributes; using Quadarax.Foundation.Core.Value; using System.Drawing; using System.Drawing.Imaging; namespace qdr.app.tools.qfu.Commands { [CommandDefinition] internal class ImgListCmd : BaseCmd { #region *** Constants *** private const string CMD_NAME = "img_list"; private const string CMD_DESCR = "List all image files from source directory with dimensions, resolution and size information."; // Common image file extensions private static readonly string[] ImageExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".webp", ".ico", ".svg", ".psd", ".raw", ".cr2", ".nef", ".arw" }; #endregion #region *** Properties *** public override string Name => CMD_NAME; public override string Description => CMD_DESCR; protected string Directory { get; private set; } = string.Empty; protected bool IncludeSubdirectories { get; private set; } = false; protected string[] ExcludePatterns { get; private set; } = Array.Empty(); #endregion #region *** Constructors *** public ImgListCmd(Engine engine) : base(engine) { } #endregion #region *** Overrides *** #region **** EXECUTE **** protected override Result OnExecute() { var subdirText = IncludeSubdirectories ? " and subdirectories" : ""; var excludeText = ExcludePatterns.Length > 0 ? $" (excluding: {string.Join(", ", ExcludePatterns)})" : ""; WriteCaption($"Listing image files from '{Directory}'{subdirText}{excludeText}..."); WriteInfo($"{"Dimensions",-12}\t{"Resolution",-15}\t{"Size",-10}\t{"File Path"}"); WriteInfo($"{new string('-', 12)}\t{new string('-', 15)}\t{new string('-', 10)}\t{new string('-', 50)}"); var totalFiles = 0; var processedFiles = 0; var errors = new List(); try { var searchOption = IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var allImageFiles = new List(); // Get all files with image extensions foreach (var extension in ImageExtensions) { try { var files = FileSystem.Directory.GetFiles(Directory, $"*{extension}", searchOption); allImageFiles.AddRange(files); } catch (Exception ex) { WriteWarning($"Failed to search for {extension} files: {ex.Message}"); } } // Remove duplicates and apply exclusion filters var uniqueFiles = allImageFiles.Distinct().ToList(); var filteredFiles = ApplyExcludeFilters(uniqueFiles); totalFiles = filteredFiles.Count; foreach (var filePath in filteredFiles.OrderBy(f => f)) { processedFiles++; try { var relativePath = Path.GetRelativePath(Directory, filePath); var imageInfo = GetImageInfo(filePath); if (imageInfo != null) { WriteInfo($"{imageInfo.Dimensions,-12}\t{imageInfo.Resolution,-15}\t{imageInfo.Size,-10}\t{relativePath}"); } else { WriteWarning($"{"N/A",-12}\t{"N/A",-15}\t{GetFileSize(filePath),-10}\t{relativePath} (unsupported format)"); } } catch (Exception ex) { var relativePath = Path.GetRelativePath(Directory, filePath); var errorMsg = $"Failed to process '{relativePath}': {ex.Message}"; errors.Add(errorMsg); WriteError($"{"ERROR",-12}\t{"ERROR",-15}\t{GetFileSize(filePath),-10}\t{relativePath}"); } } WriteCaption($"Successfully processed: {processedFiles} of {totalFiles} image files."); if (errors.Count > 0) { WriteWarning($"Encountered {errors.Count} errors during operation."); } return errors.Count == 0 ? new Result() : new Result(new Exception($"Failed to process {errors.Count} files.")); } catch (Exception ex) { WriteError($"Critical error during operation: {ex.Message}"); return new Result(ex); } } #endregion #region **** Arguments **** protected override void OnValidateArguments() { base.OnValidateArguments(); // Get and validate directory (using Source from BaseCmd) Directory = Source; if (!FileSystem.Directory.Exists(Directory)) throw new ArgumentException($"Directory '{Directory}' does not exist.", SourceAspect.ARG_SRC_NAME); // Get flags IncludeSubdirectories = GetArgumentValueOrDefault(SubdirsAspect.ARG_SUBDIRS_NAME, false); // Get exclude patterns ExcludePatterns = ExcludeAspect.GetExcludePatterns(this); } protected override IEnumerable OnSetupArguments() { var list = base.OnSetupArguments().ToList(); // Add our specific arguments list.AddRange(SubdirsAspect.SetupArguments()); list.AddRange(ExcludeAspect.SetupArguments()); return list; } #endregion #region **** Private Operations **** private ImageInfo? GetImageInfo(string filePath) { try { using var fileStream = FileSystem.File.OpenRead(filePath); using var image = Image.FromStream(fileStream, false, false); var width = image.Width; var height = image.Height; var dimensions = $"{width}x{height}"; // Get resolution (DPI) var horizontalRes = image.HorizontalResolution; var verticalRes = image.VerticalResolution; var resolution = $"{horizontalRes:F0}x{verticalRes:F0} DPI"; // Get file size var size = GetFileSize(filePath); return new ImageInfo(dimensions, resolution, size); } catch { // If System.Drawing can't handle the image, try to at least get basic info return null; } } private string GetFileSize(string filePath) { try { var fileInfo = FileSystem.FileInfo.New(filePath); var bytes = fileInfo.Length; return bytes switch { < 1024 => $"{bytes} B", < 1024 * 1024 => $"{bytes / 1024.0:F1} KB", < 1024 * 1024 * 1024 => $"{bytes / (1024.0 * 1024.0):F1} MB", _ => $"{bytes / (1024.0 * 1024.0 * 1024.0):F1} GB" }; } catch { return "N/A"; } } private List ApplyExcludeFilters(List files) { if (ExcludePatterns.Length == 0) return files; var filteredFiles = new List(); foreach (var file in files) { var fileName = Path.GetFileName(file); var shouldExclude = false; foreach (var pattern in ExcludePatterns) { if (IsMatch(fileName, pattern) || IsMatch(file, pattern)) { shouldExclude = true; break; } } if (!shouldExclude) { filteredFiles.Add(file); } } return filteredFiles; } private bool IsMatch(string input, string pattern) { // Simple pattern matching - supports * wildcards if (string.IsNullOrEmpty(pattern)) return false; if (pattern == "*") return true; // Convert pattern to regex-like matching var regexPattern = "^" + pattern.Replace("*", ".*").Replace("?", ".") + "$"; return System.Text.RegularExpressions.Regex.IsMatch(input, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase); } #endregion #endregion #region *** Helper Classes *** private record ImageInfo(string Dimensions, string Resolution, string Size); #endregion } }