diff --git a/HellionChat/Themes/ThemeRegistry.cs b/HellionChat/Themes/ThemeRegistry.cs index efb1768..3e14c84 100644 --- a/HellionChat/Themes/ThemeRegistry.cs +++ b/HellionChat/Themes/ThemeRegistry.cs @@ -7,9 +7,11 @@ public sealed class ThemeRegistry public const string DefaultSlug = HellionArctic.Slug; private readonly Dictionary _builtIns; + private readonly Dictionary _customCache = new(StringComparer.OrdinalIgnoreCase); + private readonly string? _customThemesDir; private Theme _active; - public ThemeRegistry() + public ThemeRegistry(string? customThemesDir = null) { _builtIns = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -20,23 +22,75 @@ public sealed class ThemeRegistry { MintGrove.Slug, MintGrove.Build() }, }; _active = _builtIns[DefaultSlug]; + _customThemesDir = customThemesDir; } public Theme Active => _active; - // Active-Lookup: liefert das angeforderte Theme oder fällt auf Hellion Arctic - // zurück, wenn der Slug unbekannt ist. public Theme Get(string slug) { - return _builtIns.TryGetValue(slug, out var theme) - ? theme - : _builtIns[DefaultSlug]; + if (_builtIns.TryGetValue(slug, out var b)) return b; + + var custom = LoadCustomBySlug(slug); + if (custom != null) return custom; + + return _builtIns[DefaultSlug]; } public IEnumerable AllBuiltIns() => _builtIns.Values; - public void Switch(string slug) + public IEnumerable AllCustom() => RefreshCustomCache(); + + public void Switch(string slug) => _active = Get(slug); + + // Custom-Themes werden lazy aus dem Verzeichnis geladen, Cache mit + // LastWriteTime-Token. Eine geänderte JSON wird beim nächsten Lookup + // neu eingelesen. + private Theme? LoadCustomBySlug(string slug) { - _active = Get(slug); + if (_customThemesDir is null) return null; + if (!Directory.Exists(_customThemesDir)) return null; + + foreach (var theme in RefreshCustomCache()) + if (string.Equals(theme.Slug, slug, StringComparison.OrdinalIgnoreCase)) + return theme; + return null; + } + + private IEnumerable RefreshCustomCache() + { + if (_customThemesDir is null || !Directory.Exists(_customThemesDir)) + yield break; + + var seenSlugs = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var path in Directory.EnumerateFiles(_customThemesDir, "*.json")) + { + Theme? theme = null; + var stamp = File.GetLastWriteTimeUtc(path); + var key = path; + if (_customCache.TryGetValue(key, out var cached) && cached.Stamp == stamp) + { + theme = cached.Theme; + } + else + { + try + { + theme = ThemeJsonLoader.LoadFromFile(path); + _customCache[key] = (theme, stamp); + } + catch (Exception ex) + { + // Logging passiert in Plugin.cs durch den Aufrufer; hier still + // ignorieren, damit ein einzelnes kaputtes JSON nicht alle + // Custom-Themes blockt. + _ = ex; + continue; + } + } + + if (theme is not null && seenSlugs.Add(theme.Slug)) + yield return theme; + } } }