| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- 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;
- namespace qdr.app.tools.qfu.Commands
- {
- [CommandDefinition]
- internal class ClearDirCmd : BaseCmd
- {
- #region *** Constants ***
- private const string CMD_NAME = "cleardir";
- private const string CMD_DESCR = "Remove all files or directories (with optional search pattern) from directory specified and optionally its subdirectories.";
- #endregion
- #region *** Properties ***
- public override string Name => CMD_NAME;
- public override string Description => CMD_DESCR;
- protected string Scope { get; private set; } = string.Empty;
- protected string Directory { get; private set; } = string.Empty;
- protected bool IncludeSubdirectories { get; private set; } = false;
- protected string[] SearchPatterns { get; private set; } = Array.Empty<string>();
- protected bool IsDryRun { get; private set; } = false;
- protected bool OnlyEmpty { get; private set; } = false;
- #endregion
- #region *** Constructors ***
- public ClearDirCmd(Engine engine) : base(engine)
- {
- }
- #endregion
- #region *** Overrides ***
- #region **** EXECUTE ****
- protected override Result OnExecute()
- {
- var scopeType = ScopeAspect.IsScopeFiles(Scope) ? "files" : "directories";
- var subdirText = IncludeSubdirectories ? " and subdirectories" : "";
- var dryText = IsDryRun ? " (DRY RUN)" : "";
- var emptyText = OnlyEmpty ? " (empty only)" : "";
- var patternsText = SearchPatterns.Length > 1 ? $" with patterns: {string.Join(", ", SearchPatterns)}" :
- SearchPatterns.Length == 1 && SearchPatterns[0] != "*" ? $" with pattern: {SearchPatterns[0]}" : "";
- WriteCaption($"Will be clearing {scopeType} from '{Directory}'{subdirText}{patternsText}{emptyText}{dryText}...");
- var totalProcessed = 0;
- var totalRemoved = 0;
- var totalSkipped = 0;
- var errors = new List<string>();
- try
- {
- if (ScopeAspect.IsScopeFiles(Scope))
- {
- ProcessFiles(Directory, ref totalProcessed, ref totalRemoved, ref totalSkipped, errors);
- }
- else
- {
- ProcessDirectories(Directory, ref totalProcessed, ref totalRemoved, ref totalSkipped, errors);
- }
- WriteCaption($"Operation completed successfully.");
- WriteCaption($"SUMMARY:");
- WriteCaption($" Total {scopeType}: {totalProcessed}");
- WriteCaption($" Deleted {scopeType}: {totalRemoved}");
- WriteCaption($" Skipped {scopeType}: {totalSkipped}");
- if (errors.Count > 0)
- WriteCaption($" Failed {scopeType}: {errors.Count}");
-
- if (errors.Count > 0)
- {
- WriteWarning($"Encountered {errors.Count} errors during operation:");
- foreach (var error in errors.Take(10)) // Show first 10 errors
- {
- WriteError(error);
- }
- if (errors.Count > 10)
- WriteWarning($"... and {errors.Count - 10} more errors.");
- }
- return errors.Count == 0 ? new Result() : new Result(new Exception($"Failed to process {errors.Count} items."));
- }
- catch (Exception ex)
- {
- WriteError($"Critical error during operation: {ex.Message}");
- return new Result(ex);
- }
- }
- #endregion
- #region **** Arguments ****
- protected override void OnValidateArguments()
- {
- // Get and validate scope first
- Scope = ArgumentAsFileName(ScopeAspect.ARG_SCOPE_NAME);
- if (!ScopeAspect.IsValidScope(Scope))
- throw new ArgumentException($"Invalid scope '{Scope}'. Valid values are 'files' or 'directories'.", ScopeAspect.ARG_SCOPE_NAME);
- // Get and validate directory (from our custom directory argument)
- Directory = ArgumentAsFileName("directory");
- if (!FileSystem.Directory.Exists(Directory))
- throw new ArgumentException($"Directory '{Directory}' does not exist.", "directory");
- // Get flags
- IncludeSubdirectories = GetArgumentValueOrDefault<bool>(SubdirsAspect.ARG_SUBDIRS_NAME, false);
- IsDryRun = GetArgumentValueOrDefault<bool>(DryAspect.ARG_DRY_NAME, false);
- OnlyEmpty = GetArgumentValueOrDefault<bool>(EmptyAspect.ARG_EMPTY_NAME, false);
- // Get search patterns
- SearchPatterns = FilterAspect.GetSearchPatterns(this);
- }
- protected override IEnumerable<AbstractArgument> OnSetupArguments()
- {
- var list = new List<AbstractArgument>();
-
- // Add arguments in the correct order: scope first, then directory
- list.AddRange(ScopeAspect.SetupArguments());
-
- // Add directory argument (ordinal 1) - custom since we need different ordinal than SourceAspect
- list.Add(new OrdinalArgument(1, false, "directory", "Directory to clean.", "directory",
- Quadarax.Foundation.Core.QConsole.Value.TypeValuesEnum.String, string.Empty, true));
-
- // Add optional arguments
- list.AddRange(SubdirsAspect.SetupArguments());
- list.AddRange(FilterAspect.SetupArguments());
- list.AddRange(DryAspect.SetupArguments());
- list.AddRange(EmptyAspect.SetupArguments());
-
- return list;
- }
- #endregion
- #region **** Private Operations ****
- private void ProcessFiles(string directory, ref int totalProcessed, ref int totalRemoved, ref int totalSkipped, List<string> errors)
- {
- try
- {
- var searchOption = IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
-
- foreach (var pattern in SearchPatterns)
- {
- var files = FileSystem.Directory.GetFiles(directory, pattern, searchOption);
-
- foreach (var file in files)
- {
- totalProcessed++;
-
- try
- {
- if (OnlyEmpty)
- {
- var fileInfo = FileSystem.FileInfo.New(file);
- if (fileInfo.Length > 0)
- {
- WriteInfo($"[{totalProcessed}] Skipping non-empty file '{file}' (size: {fileInfo.Length} bytes).");
- totalSkipped++;
- continue;
- }
- }
- if (IsDryRun)
- {
- WriteInfo($"[{totalProcessed}] Would delete file '{file}'.");
- totalRemoved++;
- }
- else
- {
- FileSystem.File.Delete(file);
- WriteInfo($"[{totalProcessed}] Deleted file '{file}'.");
- totalRemoved++;
- }
- }
- catch (Exception ex)
- {
- var errorMsg = $"[{totalProcessed}] Failed to delete file '{file}': {ex.Message}";
- errors.Add(errorMsg);
- WriteError(errorMsg);
- }
- }
- }
- }
- catch (Exception ex)
- {
- var errorMsg = $"Failed to enumerate files in directory '{directory}': {ex.Message}";
- errors.Add(errorMsg);
- WriteError(errorMsg);
- }
- }
- private void ProcessDirectories(string directory, ref int totalProcessed, ref int totalRemoved, ref int totalSkipped, List<string> errors)
- {
- try
- {
- var searchOption = IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
- var allDirectories = new List<string>();
- foreach (var pattern in SearchPatterns)
- {
- var directories = FileSystem.Directory.GetDirectories(directory, pattern, searchOption);
- allDirectories.AddRange(directories);
- }
- // Remove duplicates and sort by depth (deepest first) to ensure proper deletion order
- var uniqueDirectories = allDirectories.Distinct()
- .OrderByDescending(dir => dir.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length)
- .ToArray();
- foreach (var dir in uniqueDirectories)
- {
- // Skip the root directory itself
- if (string.Equals(dir, directory, StringComparison.OrdinalIgnoreCase))
- continue;
- totalProcessed++;
- try
- {
- if (OnlyEmpty)
- {
- var isEmpty = IsDirectoryEmpty(dir);
- if (!isEmpty)
- {
- WriteInfo($"[{totalProcessed}] Skipping non-empty directory '{dir}'.");
- totalSkipped++;
- continue;
- }
- }
- if (IsDryRun)
- {
- WriteInfo($"[{totalProcessed}] Would delete directory '{dir}'.");
- totalRemoved++;
- }
- else
- {
- FileSystem.Directory.Delete(dir, !OnlyEmpty); // recursive delete if not OnlyEmpty
- WriteInfo($"[{totalProcessed}] Deleted directory '{dir}'.");
- totalRemoved++;
- }
- }
- catch (Exception ex)
- {
- var errorMsg = $"[{totalProcessed}] Failed to delete directory '{dir}': {ex.Message}";
- errors.Add(errorMsg);
- WriteError(errorMsg);
- }
- }
- }
- catch (Exception ex)
- {
- var errorMsg = $"Failed to enumerate directories in '{directory}': {ex.Message}";
- errors.Add(errorMsg);
- WriteError(errorMsg);
- }
- }
- private bool IsDirectoryEmpty(string directory)
- {
- try
- {
- return !FileSystem.Directory.EnumerateFileSystemEntries(directory).Any();
- }
- catch
- {
- return false; // If we can't enumerate, assume it's not empty
- }
- }
- #endregion
- #endregion
- }
- }
|