fe9044115b
Server-007: GalaxyHierarchyProjector re-filtered the whole hierarchy per page (O(total) paging). It now memoizes the filtered list per cache-entry + filter signature so subsequent pages are an O(pageSize) slice. Server-008: WatchDeployEvents re-resolved browse subtrees and rebuilt globs per streamed event. ResolveBrowseSubtrees is hoisted out of the loop and GalaxyGlobMatcher caches compiled Regex instances per pattern. Server-009: auth-store connections used no busy timeout or WAL. A new OpenConnectionAsync applies journal_mode=WAL and a busy_timeout; all auth call sites use it. docs/Authentication.md updated. Server-010: the dashboard rendered Rotate/Revoke for revoked keys, where Rotate silently reactivates them. ApiKeysPage now shows actions only for Active keys. docs/Authentication.md updated. Server-011: WorkerAlarmRpcDispatcher converted to a primary constructor and brought in line with module conventions. Server-012: CLAUDE.md corrected to the canonical *:* scope strings. Server-013 (partly re-triaged): three named coverage gaps were already closed; the genuine gap (WorkerExecutableValidator) is now covered. Server-014: rewrote stale "alarm path not yet wired" comments in MxAccessGatewayService to describe the production WorkerAlarmRpcDispatcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
58 lines
1.8 KiB
C#
58 lines
1.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace MxGateway.Server.Galaxy;
|
|
|
|
public static class GalaxyGlobMatcher
|
|
{
|
|
/// <summary>
|
|
/// Compiled-regex cache keyed by glob pattern. <c>IsMatch</c> is called once per
|
|
/// object per <c>DiscoverHierarchy</c>/<c>WatchDeployEvents</c> evaluation, so the
|
|
/// same handful of glob patterns are translated repeatedly; caching avoids
|
|
/// rebuilding and recompiling the regex on every call.
|
|
/// </summary>
|
|
private static readonly ConcurrentDictionary<string, Regex> RegexCache = new(StringComparer.Ordinal);
|
|
|
|
public static bool IsMatch(string value, string glob)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(glob))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return GetOrCreateRegex(glob).IsMatch(value ?? string.Empty);
|
|
}
|
|
|
|
private static Regex GetOrCreateRegex(string glob)
|
|
{
|
|
return RegexCache.GetOrAdd(glob, static pattern => new Regex(
|
|
BuildRegex(pattern),
|
|
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
|
TimeSpan.FromMilliseconds(100)));
|
|
}
|
|
|
|
private static string BuildRegex(string glob)
|
|
{
|
|
StringBuilder builder = new("^", glob.Length + 2);
|
|
foreach (char character in glob)
|
|
{
|
|
switch (character)
|
|
{
|
|
case '*':
|
|
builder.Append(".*");
|
|
break;
|
|
case '?':
|
|
builder.Append('.');
|
|
break;
|
|
default:
|
|
builder.Append(Regex.Escape(character.ToString()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
builder.Append('$');
|
|
return builder.ToString();
|
|
}
|
|
}
|