-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathBlackMatcher.cs
More file actions
131 lines (121 loc) · 6.5 KB
/
Copy pathBlackMatcher.cs
File metadata and controls
131 lines (121 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.IO;
using System.Linq;
using GeneralUpdate.Core.Configuration;
namespace GeneralUpdate.Core.FileSystem;
/// <summary>
/// Default implementation of a Glob-pattern-based blacklist matcher, driven by <see cref="BlackPolicy"/>.
/// </summary>
/// <remarks>
/// <para>
/// BlackMatcher implements the <see cref="IBlackMatcher"/> interface and provides three types of blacklist filtering:
/// </para>
/// <list type="bullet">
/// <item><description><b>Blacklist files</b>: Matches file names using Glob patterns (e.g., <c>*.log</c> matches all log files).</description></item>
/// <item><description><b>Blacklist formats</b>: Performs case-insensitive exact matching by file extension.</description></item>
/// <item><description><b>Skip directories</b>: Uses case-insensitive substring containment matching to determine whether to skip subdirectories.</description></item>
/// </list>
/// <para>
/// Instances of this class are typically created from <see cref="UpdateContext"/> via the <see cref="FromConfigInfo"/> factory method,
/// or constructed directly by passing a <see cref="BlackPolicy"/> configuration object.
/// </para>
/// </remarks>
public class BlackMatcher : IBlackMatcher
{
private readonly BlackPolicy _config;
/// <summary>
/// Initializes a new instance of the <see cref="BlackMatcher"/> class with the specified blacklist configuration.
/// </summary>
/// <param name="config">The blacklist configuration object containing file name patterns, extensions, and directory names to exclude.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="config"/> is <c>null</c>.</exception>
public BlackMatcher(BlackPolicy config)
=> _config = config ?? throw new ArgumentNullException(nameof(config));
/// <summary>
/// Creates a matcher instance from the blacklist properties in <see cref="UpdateContext"/>.
/// </summary>
/// <param name="config">The global configuration information object.</param>
/// <returns>A configured <see cref="BlackMatcher"/> instance.</returns>
/// <remarks>
/// This factory method only sets the corresponding blacklist rules when the respective list has elements (<c>Count > 0</c>);
/// empty lists are treated as <c>null</c> (meaning that type of filtering is disabled).
/// </remarks>
public static BlackMatcher FromConfigInfo(UpdateContext config)
{
var cfg = new BlackPolicy(
config.Files?.Count > 0 ? config.Files : null,
config.Formats?.Count > 0 ? config.Formats : null,
config.Directories?.Count > 0 ? config.Directories : null);
return new BlackMatcher(cfg);
}
/// <summary>
/// Determines whether the specified file is excluded by the blacklist matching rules.
/// </summary>
/// <param name="relativeFilePath">The relative path or file name of the file.</param>
/// <returns><c>true</c> if the file matches the blacklist rules; otherwise, <c>false</c>.</returns>
/// <remarks>
/// The matching logic checks in the following order:
/// <list type="number">
/// <item><description>Whether the file name matches any Glob pattern in <c>Files</c>.</description></item>
/// <item><description>Whether the file extension matches any format in <c>Formats</c>.</description></item>
/// </list>
/// The file is considered blacklisted if any condition is met.
/// </remarks>
public bool IsBlacklisted(string relativeFilePath)
{
var fileName = Path.GetFileName(relativeFilePath);
var ext = Path.GetExtension(relativeFilePath);
if (_config.Files?.Any(f => MatchGlob(fileName, f)) == true) return true;
if (_config.Formats?.Any(f => string.Equals(f, ext, StringComparison.OrdinalIgnoreCase)) == true) return true;
return false;
}
/// <summary>
/// Determines whether the specified file extension is in the blacklist format list.
/// </summary>
/// <param name="extension">The file extension (e.g., <c>.log</c>, <c>.tmp</c>).</param>
/// <returns><c>true</c> if the extension is in the blacklist; otherwise, <c>false</c>.</returns>
/// <remarks>
/// Uses case-insensitive string comparison for evaluation.
/// </remarks>
public bool IsBlacklistedFormat(string extension)
=> _config.Formats?.Any(f => string.Equals(f, extension, StringComparison.OrdinalIgnoreCase)) == true;
/// <summary>
/// Determines whether the specified directory name should be skipped (i.e., not entered during traversal).
/// </summary>
/// <param name="directoryName">The directory name.</param>
/// <returns><c>true</c> if the directory name matches any skip rule; otherwise, <c>false</c>.</returns>
/// <remarks>
/// Uses case-insensitive substring containment matching (<c>string.IndexOf</c>) for evaluation.
/// The directory is considered skippable if its name contains any string from the <c>Directories</c> list.
/// </remarks>
public bool ShouldSkipDirectory(string directoryOrPath)
{
// Extract the directory name from a possible full path so both
// bare names ("backup-2026") and full paths (".backups/backup-2026") work.
var dirName = Path.GetFileName(directoryOrPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
if (string.IsNullOrEmpty(dirName)) return false;
return _config.Directories?.Any(d =>
dirName.StartsWith(d, StringComparison.OrdinalIgnoreCase)) == true;
}
/// <summary>
/// Matches a file name against a simple Glob pattern.
/// </summary>
/// <param name="input">The file name to match.</param>
/// <param name="pattern">The Glob pattern (supports <c>*.xxx</c> wildcard prefix matching and exact matching).</param>
/// <returns><c>true</c> if the file name matches the pattern; otherwise, <c>false</c>.</returns>
/// <remarks>
/// Currently supports two Glob patterns:
/// <list type="bullet">
/// <item><description><c>*.log</c>: Wildcard matching for names ending with <c>.log</c>.</description></item>
/// <item><description><c>filename</c>: Case-insensitive exact file name matching.</description></item>
/// </list>
/// </remarks>
private static bool MatchGlob(string input, string pattern)
{
if (pattern.StartsWith("*."))
{
var ext = pattern.Substring(1);
return input.EndsWith(ext, StringComparison.OrdinalIgnoreCase);
}
return string.Equals(input, pattern, StringComparison.OrdinalIgnoreCase);
}
}