|
1 | 1 | using System; |
2 | | -using System.Linq; |
3 | 2 | using System.Collections.Generic; |
| 3 | +using System.Linq; |
4 | 4 | using System.Threading; |
| 5 | +using Microsoft.Extensions.Logging; |
| 6 | +using Microsoft.Extensions.Logging.Abstractions; |
5 | 7 | using Microsoft.Win32; |
| 8 | +using WebSearchShortcut.Helpers; |
6 | 9 |
|
7 | 10 | namespace WebSearchShortcut.Browser; |
8 | 11 |
|
9 | 12 | public static class BrowsersDiscovery |
10 | 13 | { |
11 | | - private static Lazy<BrowserInfo[]> _installedBrowsersCache = CreateInstalledBrowsersCache(); |
12 | | - private static Lazy<BrowserInfo[]> CreateInstalledBrowsersCache() => |
13 | | - new(LoadInstalledBrowsers, LazyThreadSafetyMode.ExecutionAndPublication); |
14 | | - public static IReadOnlyCollection<BrowserInfo> GetAllInstalledBrowsers() => _installedBrowsersCache.Value; |
15 | | - |
16 | | - public static void Reload(bool warm = false) |
| 14 | + private static ILogger _logger = NullLogger.Instance; |
| 15 | + public static ILogger Logger |
17 | 16 | { |
18 | | - var newCache = CreateInstalledBrowsersCache(); |
| 17 | + get => _logger; |
| 18 | + set |
| 19 | + { |
| 20 | + _logger = value; |
| 21 | + _registryService = new RegistryLogService(value); |
| 22 | + _progIdBrowserResolver = new ProgIdBrowserService(value); |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + private static RegistryLogService _registryService = new(NullLogger.Instance); |
| 27 | + private static ProgIdBrowserService _progIdBrowserResolver = new(NullLogger.Instance); |
19 | 28 |
|
20 | | - Interlocked.Exchange(ref _installedBrowsersCache, newCache); |
| 29 | + private static Lazy<BrowserInfo[]> _cache = new(ScanInstalledBrowsers); |
| 30 | + public static IReadOnlyCollection<BrowserInfo> GetInstalledBrowsers() => _cache.Value; |
| 31 | + |
| 32 | + public static void Update(bool warm = false) |
| 33 | + { |
| 34 | + Interlocked.Exchange(ref _cache, new Lazy<BrowserInfo[]>(ScanInstalledBrowsers)); |
21 | 35 |
|
22 | 36 | if (warm) |
23 | | - _ = newCache.Value; |
| 37 | + _ = _cache.Value; |
24 | 38 | } |
25 | 39 |
|
26 | | - private static BrowserInfo[] LoadInstalledBrowsers() |
| 40 | + private static BrowserInfo[] ScanInstalledBrowsers() |
27 | 41 | { |
| 42 | + using var scope = Logger.BeginStaticScope(typeof(BrowsersDiscovery)); |
| 43 | + |
28 | 44 | string[] progIds = GetAssociatedProgIds(); |
29 | | - List<BrowserInfo> result = []; |
| 45 | + List<BrowserInfo> browsers = []; |
30 | 46 |
|
31 | 47 | foreach (var progId in progIds) |
32 | 48 | { |
33 | 49 | try |
34 | 50 | { |
35 | | - BrowserInfo info = ProgIdBrowserResolver.GetBrowserInfoFromProgId(progId); |
36 | | - result.Add(info); |
| 51 | + BrowserInfo browser = _progIdBrowserResolver.GetBrowserInfoFromProgId(progId); |
| 52 | + browsers.Add(browser); |
37 | 53 | } |
38 | | - catch |
| 54 | + catch (Exception ex) |
39 | 55 | { |
| 56 | + Logger.LogError(ex, ex.Message); |
40 | 57 | } |
41 | 58 | } |
42 | 59 |
|
43 | | - return [.. result.OrderBy(b => b.Name, StringComparer.OrdinalIgnoreCase)]; |
| 60 | + Logger.LogInformation("Browsers loaded. Found {Count} installed browsers.", browsers.Count); |
| 61 | + foreach (var browser in browsers) |
| 62 | + { |
| 63 | + Logger.LogInformation("\t'{Name}' ('{Path} {ArgumentsPattern}')", |
| 64 | + browser.Name, |
| 65 | + browser.Path, |
| 66 | + browser.ArgumentsPattern); |
| 67 | + } |
| 68 | + |
| 69 | + return [.. browsers.OrderBy(browser => browser.Name, StringComparer.OrdinalIgnoreCase)]; |
44 | 70 | } |
45 | 71 |
|
46 | 72 | private static string[] GetAssociatedProgIds() |
@@ -68,29 +94,55 @@ private static string[] GetAssociatedProgIds() |
68 | 94 | return [.. progIdSet]; |
69 | 95 | } |
70 | 96 |
|
71 | | - private static HashSet<string> ScanProgIdsFromRegistry(RegistryKey baseKey, string rootSubKey, string subKeySuffix) |
| 97 | + private static HashSet<string> ScanProgIdsFromRegistry(RegistryKey hiveKey, string containerPath, string targetSubPath) |
72 | 98 | { |
| 99 | + using var scope = Logger.BeginStepScope(typeof(BrowsersDiscovery)); |
| 100 | + |
| 101 | + Logger.LogTrace("Starting registry scan. hiveKey: '{hiveKey}', containerPath: '{containerPath}', targetSubPath: '{targetSubPath}'", hiveKey.Name, containerPath, targetSubPath); |
| 102 | + |
73 | 103 | HashSet<string> progIds = []; |
74 | 104 |
|
75 | | - using RegistryKey? root = baseKey.OpenSubKey(rootSubKey); |
76 | | - if (root == null) return progIds; |
| 105 | + using RegistryKey? containerKey = _registryService.OpenSubKey(hiveKey, containerPath, isRequired: false); |
| 106 | + if (containerKey is null) |
| 107 | + { |
| 108 | + Logger.LogDebug("Unique ProgIds count: {Count}\n", progIds.Count); |
| 109 | + |
| 110 | + return progIds; |
| 111 | + } |
| 112 | + |
| 113 | + string[] subKeyNames = containerKey.GetSubKeyNames(); |
| 114 | + Logger.LogDebug("\tFound {Count} subkeys under '{Path}'", subKeyNames.Length, containerKey.Name); |
77 | 115 |
|
78 | | - foreach (string subName in root.GetSubKeyNames()) |
| 116 | + foreach (string subKeyName in subKeyNames) |
79 | 117 | { |
80 | | - using RegistryKey? browserKey = root.OpenSubKey(subName); |
81 | | - if (browserKey == null) continue; |
| 118 | + using var subScope = Logger.BeginScope($"'{subKeyName}'"); |
82 | 119 |
|
83 | | - using RegistryKey? urlAssocKey = browserKey.OpenSubKey(subKeySuffix); |
84 | | - if (urlAssocKey == null) continue; |
| 120 | + using RegistryKey? itemKey = _registryService.OpenSubKey(containerKey, subKeyName, isRequired: false); |
| 121 | + if (itemKey is null) |
| 122 | + continue; |
85 | 123 |
|
86 | | - string? progId = urlAssocKey.GetValue("https") as string |
87 | | - ?? urlAssocKey.GetValue("http") as string; |
| 124 | + using RegistryKey? urlAssocKey = _registryService.OpenSubKey(itemKey, targetSubPath, isRequired: false); |
| 125 | + if (urlAssocKey is null) |
| 126 | + continue; |
88 | 127 |
|
89 | | - if (!string.IsNullOrWhiteSpace(progId)) |
| 128 | + string? httpsValue = _registryService.GetValue<string>(urlAssocKey, "https", isRequired: false); |
| 129 | + string? httpValue = _registryService.GetValue<string>(urlAssocKey, "http", isRequired: false); |
| 130 | + string? progId = httpsValue ?? httpValue; |
| 131 | + |
| 132 | + if (string.IsNullOrWhiteSpace(progId)) |
| 133 | + { |
| 134 | + Logger.LogDebug("No valid http/https ProgId found at: '{AssocPath}'", urlAssocKey.Name); |
| 135 | + } |
| 136 | + else |
90 | 137 | { |
91 | 138 | progIds.Add(progId.Trim()); |
| 139 | + |
| 140 | + Logger.LogDebug("Successfully retrieved ProgId: '{ProgId}' from '{AssocPath}'", progId.Trim(), urlAssocKey.Name); |
92 | 141 | } |
93 | 142 | } |
| 143 | + |
| 144 | + Logger.LogDebug("Registry scan completed for '{FullPath}'. Unique ProgIds count: {Count}\n", containerKey.Name, progIds.Count); |
| 145 | + |
94 | 146 | return progIds; |
95 | 147 | } |
96 | 148 | } |
0 commit comments