Explorar o código

qfu: add command img_list, update docs

Dalibor Votruba hai 1 ano
pai
achega
04c7f5c66a

+ 52 - 0
qfu/Commands/Aspects/ExcludeAspect.cs

@@ -0,0 +1,52 @@
+using Quadarax.Foundation.Core.QConsole.Argument;
+using Quadarax.Foundation.Core.QConsole.Command.Base;
+
+namespace qdr.app.tools.qfu.Commands.Aspects
+{
+    internal class ExcludeAspect
+    {
+        #region *** Constants ***
+        private const string ARG_EXCLUDE_SEP = "|";
+        public const string ARG_EXCLUDE_NAME = "exclude";
+        private const string ARG_EXCLUDE_HINT = "exclude";
+        private const string ARG_EXCLUDE_DESCR = "One or more files or search patterns to exclude separated by character pipe (" + ARG_EXCLUDE_SEP + "). Example: *.tmp|temp*|backup.jpg";
+        #endregion
+
+        #region *** Public Operations ***
+        public static IEnumerable<AbstractArgument> SetupArguments()
+        {
+            return
+            [
+                new NamedArgument(ARG_EXCLUDE_NAME, ARG_EXCLUDE_DESCR, ARG_EXCLUDE_HINT, Quadarax.Foundation.Core.QConsole.Value.TypeValuesEnum.String, string.Empty, false)
+            ];
+        }
+
+        public static string[] GetExcludePatterns(AbstractCommand context)
+        {
+            if (context == null)
+                throw new ArgumentNullException(nameof(context), "Command context cannot be null.");
+
+            var excludeArgument = context.Arguments.FirstOrDefault(x => string.Equals(x.Code, ARG_EXCLUDE_NAME));
+            if (excludeArgument?.Value.Get() == null)
+                return Array.Empty<string>(); // No exclude patterns
+
+            var excludeValue = excludeArgument.Value.Get()?.ToString();
+            if (string.IsNullOrWhiteSpace(excludeValue))
+                return Array.Empty<string>(); // No exclude patterns
+
+            return ParseExcludePatterns(excludeValue);
+        }
+
+        public static string[] ParseExcludePatterns(string excludeArgument)
+        {
+            if (string.IsNullOrWhiteSpace(excludeArgument))
+                return Array.Empty<string>();
+
+            return excludeArgument.Split(new[] { ARG_EXCLUDE_SEP }, StringSplitOptions.RemoveEmptyEntries)
+                                 .Select(f => f.Trim())
+                                 .Where(f => !string.IsNullOrEmpty(f))
+                                 .ToArray();
+        }
+        #endregion
+    }
+}

+ 253 - 0
qfu/Commands/ImgListCmd.cs

@@ -0,0 +1,253 @@
+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<string>();
+        #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<string>();
+
+            try
+            {
+                var searchOption = IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+                var allImageFiles = new List<string>();
+
+                // 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<bool>(SubdirsAspect.ARG_SUBDIRS_NAME, false);
+
+            // Get exclude patterns
+            ExcludePatterns = ExcludeAspect.GetExcludePatterns(this);
+        }
+
+        protected override IEnumerable<AbstractArgument> 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<string> ApplyExcludeFilters(List<string> files)
+        {
+            if (ExcludePatterns.Length == 0)
+                return files;
+
+            var filteredFiles = new List<string>();
+
+            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
+    }
+}

+ 3 - 1
qfu/directory_structure.txt

@@ -1,5 +1,5 @@
 Project Directory Structure 
-Generated on: 13.06.2025 11:58:46,19 
+Generated on: 17.06.2025  8:37:35,84 
 Root: d:\Projects\quadarax\qdr.app\qdr.app.tools\qfu 
  
 directory_structure.txt 
@@ -18,9 +18,11 @@ Commands/
   BaseCmd.cs 
   ClearDirCmd.cs 
   CloneCmd.cs 
+  ImgListCmd.cs 
   Aspects/ 
     DryAspect.cs 
     EmptyAspect.cs 
+    ExcludeAspect.cs 
     FilesAspect.cs 
     FilterAspect.cs 
     ForceAspect.cs 

+ 1 - 0
qfu/qfu.csproj

@@ -21,6 +21,7 @@
 
   <ItemGroup>
     <PackageReference Include="qdr.fnd.core.qconsole" Version="0.0.7-alpha" />
+    <PackageReference Include="System.Drawing.Common" Version="9.0.6" />
   </ItemGroup>
 
   <ItemGroup>

+ 114 - 46
qfu/readme.md

@@ -9,8 +9,9 @@ QFU is a powerful filesystem utility that provides efficient file operations thr
 ### Key Features
 
 - **File Cloning**: Clone source files to multiple destinations with various options
-- **Auto-Generation**: Automatically generate multiple copies with sequential naming using count parameter
-- **Flexible Input Methods**: Support for direct file specification, auto-generation, or file list input
+- **Directory Cleaning**: Remove files or directories with pattern matching and recursive options
+- **Image Analysis**: List image files with detailed metadata including dimensions, resolution, and file size
+- **Flexible Input Methods**: Support for direct file specification or file list input
 - **Path Management**: Automatic directory creation when needed
 - **Force Operations**: Override existing files when necessary
 - **Robust Error Handling**: Comprehensive validation and error reporting
@@ -20,8 +21,8 @@ QFU is a powerful filesystem utility that provides efficient file operations thr
 
 The application follows a clean modular architecture:
 
-- **Commands**: Core operations (currently `clone`)
-- **Aspects**: Reusable functionality components (Source, Files, List, Force)
+- **Commands**: Core operations (`clone`, `cleardir`, `img_list`)
+- **Aspects**: Reusable functionality components (Source, Files, List, Force, Filter, Exclude, etc.)
 - **Base Classes**: Shared functionality and abstractions
 - **Dependency Injection**: Uses `System.IO.Abstractions` for testable file operations
 
@@ -58,18 +59,60 @@ Clone a source file to one or more destination files.
 **Syntax:**
 ```
 qfu clone <source_file> <target_files> [options]
-qfu clone <source_file> -n <count> [options]
 ```
 
 **Arguments:**
 - `source_file`: Path to the source file to clone
-- `target_files`: Pipe-separated list of target file paths (`file1.txt|file2.txt|file3.txt`) - ignored when using `-n`
+- `target_files`: Pipe-separated list of target file paths (`file1.txt|file2.txt|file3.txt`)
 
 **Options:**
-- `-n <count>`: Generate the specified number of files automatically with sequential naming (e.g., `file_1.ext`, `file_2.ext`)
 - `-pe` or `-path_ensure`: Create destination directories if they don't exist
 - `-force`: Overwrite existing destination files
 - `-list`: Treat the first target file as a text file containing a list of destination paths (one per line)
+- `-n <count>`: Generate N numbered copies instead of using target files list
+
+#### Clear Directory Command
+
+Remove files or directories from a specified directory with optional pattern matching.
+
+**Syntax:**
+```
+qfu cleardir <scope> <directory> [options]
+```
+
+**Arguments:**
+- `scope`: Either `files` or `directories` - what to clean
+- `directory`: Path to the directory to clean
+
+**Options:**
+- `-s`: Include subdirectories in the operation
+- `-f <patterns>`: Filter patterns (pipe-separated, e.g., `*.txt|*.log`)
+- `-dry`: Dry run - show what would be deleted without actually deleting
+- `-empty`: Only delete empty files or directories
+
+#### Image List Command
+
+List all image files from a directory with their dimensions, resolution, and size information.
+
+**Syntax:**
+```
+qfu img_list <source> [options]
+```
+
+**Arguments:**
+- `source`: Path to directory to scan for image files
+
+**Options:**
+- `-s`: Include subdirectories in the scan
+- `-exclude <patterns>`: Exclude files matching patterns (pipe-separated, e.g., `*.tmp|backup*`)
+
+**Output Format:**
+```
+Dimensions   Resolution      Size      File Path
+------------ --------------- --------- --------------------------------------------------
+1920x1080    72x72 DPI       2.5 MB    photos/vacation.jpg
+800x600      96x96 DPI       145.2 KB  thumbnails/small.png
+```
 
 ### Examples
 
@@ -77,44 +120,46 @@ qfu clone <source_file> -n <count> [options]
 ```bash
 # Clone a file to multiple destinations
 qfu clone source.txt "backup1.txt|backup2.txt|backup3.txt"
-```
 
-#### Clone with Path Creation
-```bash
-# Clone and create destination directories if needed
-qfu clone template.config "configs/dev.config|configs/staging.config|configs/prod.config" -pe
+# Generate 5 numbered copies
+qfu clone template.txt dummy -n 5
+# Creates: template_1.txt, template_2.txt, template_3.txt, template_4.txt, template_5.txt
 ```
 
-#### Force Overwrite Existing Files
+#### Directory Cleaning
 ```bash
-# Overwrite existing destination files
-qfu clone master.template "project1/config.xml|project2/config.xml" -force -pe
-```
+# Remove all .log and .tmp files from current directory
+qfu cleardir files . -f "*.log|*.tmp"
 
-#### Using File Lists
-```bash
-# Create a file list first
-echo "backup/file1.txt" > targets.txt
-echo "backup/file2.txt" >> targets.txt
-echo "backup/file3.txt" >> targets.txt
+# Remove empty directories recursively (dry run first)
+qfu cleardir directories ./temp -s -empty -dry
 
-# Use the list file for cloning
-qfu clone source.txt targets.txt -list -pe
+# Remove all files from subdirectories
+qfu cleardir files ./cache -s -force
 ```
 
-#### Auto-Generate Multiple Files
+#### Image Analysis
 ```bash
-# Generate 5 copies with sequential naming (source_1.txt, source_2.txt, etc.)
-qfu clone source.txt -n 5
+# List all images in current directory
+qfu img_list .
 
-# Generate 3 copies with force and path creation
-qfu clone template.config -n 3 -force -pe
+# List images recursively, excluding thumbnails and temporary files
+qfu img_list ./photos -s -exclude "thumb*|*.tmp|backup*"
+
+# Scan project directory but exclude build artifacts
+qfu img_list ./src -s -exclude "bin/*|obj/*|*.cache"
 ```
 
-#### Complex Example
+#### Complex Examples
 ```bash
-# Clone with all options (manual file specification)
+# Clone with all options
 qfu clone template.cs "src/Class1.cs|src/Class2.cs|tests/TestClass.cs" -force -pe
+
+# Clean directories with complex patterns
+qfu cleardir files ./downloads -s -f "*.tmp|temp*|cache*" -dry
+
+# Comprehensive image analysis
+qfu img_list ./media -s -exclude "*.psd|raw/*|backup*"
 ```
 
 ### Return Codes
@@ -130,24 +175,32 @@ QFU provides detailed progress information:
 - Final success/failure count
 - Detailed error messages for failed operations
 
-Example output:
-```
-Will be cloning file 'source.txt' to 3 files (with force)...
-[1] Cloned 'source.txt' to 'dest1.txt' successfully.
-[2] File 'dest2.txt' already exists. Skipping clone operation.
-[3] Cloned 'source.txt' to 'dest3.txt' successfully.
-Successfully cloned: 2 of 3 files.
-```
+## Supported Image Formats
+
+The `img_list` command supports the following image formats:
+- **JPEG** (.jpg, .jpeg)
+- **PNG** (.png)
+- **GIF** (.gif)
+- **BMP** (.bmp)
+- **TIFF** (.tiff, .tif)
+- **WebP** (.webp)
+- **ICO** (.ico)
+- **SVG** (.svg)
+- **PSD** (.psd)
+- **RAW formats** (.raw, .cr2, .nef, .arw)
+
+Note: Some formats may show "N/A" for dimensions/resolution if they cannot be processed by the image library.
 
 ## Error Handling
 
 QFU provides comprehensive error handling:
 
-- **Source Validation**: Verifies source file exists before operation
-- **Destination Validation**: Checks for existing files (unless `-force` used)
+- **Source Validation**: Verifies source files/directories exist before operation
+- **Destination Validation**: Checks for existing files (unless `--force` used)
 - **Path Validation**: Ensures destination paths are valid
 - **Permission Checking**: Reports access denied errors
-- **List File Validation**: Validates list files when using `-list` option
+- **Image Processing**: Graceful handling of unsupported or corrupted image files
+- **Pattern Validation**: Validates search and exclude patterns
 
 ## Development
 
@@ -158,11 +211,19 @@ qfu/
 ├── Commands/
 │   ├── BaseCmd.cs          # Base command functionality
 │   ├── CloneCmd.cs         # Clone command implementation
+│   ├── ClearDirCmd.cs      # Directory cleaning command
+│   ├── ImgListCmd.cs       # Image listing command
 │   └── Aspects/
-│       ├── FilesAspect.cs  # File handling logic
-│       ├── ForceAspect.cs  # Force operation logic
-│       ├── ListAspect.cs   # List file processing
-│       └── SourceAspect.cs # Source file validation
+│       ├── FilesAspect.cs      # File handling logic
+│       ├── ForceAspect.cs      # Force operation logic
+│       ├── ListAspect.cs       # List file processing
+│       ├── SourceAspect.cs     # Source file validation
+│       ├── FilterAspect.cs     # Pattern filtering
+│       ├── ExcludeAspect.cs    # Exclusion patterns
+│       ├── ScopeAspect.cs      # Operation scope (files/dirs)
+│       ├── SubdirsAspect.cs    # Subdirectory inclusion
+│       ├── DryAspect.cs        # Dry run functionality
+│       └── EmptyAspect.cs      # Empty file/directory handling
 ├── QfuCommandContext.cs    # Command execution context
 ├── QfuEngineContext.cs     # Engine context
 └── Program.cs              # Application entry point
@@ -179,6 +240,13 @@ qfu/
 
 The project uses `System.IO.Abstractions` for testable file operations. Mock the `IFileSystem` interface for unit testing.
 
+## Dependencies
+
+- **.NET 9.0**: Core runtime
+- **qdr.fnd.core.qconsole**: Command-line framework
+- **System.Drawing.Common**: Image processing capabilities
+- **System.IO.Abstractions**: File system abstraction for testing
+
 ## License
 
 Copyright © 2025 Quadarax

+ 3 - 2
qfu/releasenotes.md

@@ -1,9 +1,10 @@
 # 1.0.0.
- 13.6.2025
+ 17.6.2025
 - Initial version
 - Added clone command with file cloning capabilities
 - Added cleardir command with advanced directory cleaning
 - Implemented modular aspect-based architecture
 - Added comprehensive error handling and progress reporting
 - Implemented dry run mode for safe testing
-- Added pattern matching and filtering capabilities
+- Added pattern matching and filtering capabilities
+- Added img_list_ command to list image files with metadata