diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs index b95a1d5b..7cca1e5a 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -37,12 +38,20 @@ public class GeneralUpdateBootstrap : AbstractBootstrap? _customSkipOption; private Func? _updatePrecheck; private readonly List> _customOptions = new(); + private CancellationTokenSource? _cts; public GeneralUpdateBootstrap() { InitializeFromEnvironment(); } + /// Cancel the current update operation. + public void Cancel() + { + _cts?.Cancel(); + GeneralTracer.Info("GeneralUpdateBootstrap: cancellation requested."); + } + // ════════════════════════════════════════════════════════════════ // Launch — AppType dispatch via role strategies // ════════════════════════════════════════════════════════════════ @@ -69,8 +78,11 @@ public override async Task LaunchAsync() private async Task LaunchWithStrategy(IStrategy roleStrategy) { + _cts = new CancellationTokenSource(); + var token = _cts.Token; try { + token.ThrowIfCancellationRequested(); ApplyRuntimeOptions(); // Resolve hooks and reporter from extensions @@ -107,6 +119,11 @@ private async Task LaunchWithStrategy(IStrategy roleStra GeneralTracer.Error("LaunchWithStrategy failed.", ex); EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, ex.Message)); } + finally + { + _cts?.Dispose(); + _cts = null; + } return this; } diff --git a/src/c#/GeneralUpdate.Core/FileSystem/FileTreeEnumerator.cs b/src/c#/GeneralUpdate.Core/FileSystem/FileTreeEnumerator.cs new file mode 100644 index 00000000..86cbda27 --- /dev/null +++ b/src/c#/GeneralUpdate.Core/FileSystem/FileTreeEnumerator.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using GeneralUpdate.Core.Configuration; + +namespace GeneralUpdate.Core.FileSystem; + +/// +/// Recursively enumerates files in a directory, +/// applying blacklist filtering via IBlackListMatcher. +/// +public class FileTreeEnumerator +{ + private readonly IBlackListMatcher _matcher; + + public FileTreeEnumerator(IBlackListMatcher matcher) + { + _matcher = matcher ?? throw new ArgumentNullException(nameof(matcher)); + } + + /// + /// Enumerate all files under , + /// skipping blacklisted files and directories. + /// + public IEnumerable EnumerateFiles(string rootPath) + { + if (!Directory.Exists(rootPath)) + yield break; + + foreach (var filePath in Directory.EnumerateFiles(rootPath)) + { + var relativePath = Path.GetFileName(filePath); + if (!_matcher.IsBlacklisted(relativePath)) + yield return filePath; + } + + foreach (var dirPath in Directory.EnumerateDirectories(rootPath)) + { + var dirName = Path.GetFileName(dirPath); + if (_matcher.ShouldSkipDirectory(dirName)) + continue; + + foreach (var file in EnumerateFiles(dirPath)) + yield return file; + } + } + + /// + /// Create an enumerator from a BlackListConfig. + /// + public static FileTreeEnumerator FromConfig(BlackListConfig config) + => new(new DefaultBlackListMatcher(config)); +}