using System; using System.Collections.Concurrent; using System.IO; using System.Net.Http; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; using System.Windows.Threading; namespace AxCopilot.Services; public static class FaviconService { private static readonly string CacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favicons"); private static readonly ConcurrentDictionary _memCache = new ConcurrentDictionary(); private static readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(5.0) }; public static BitmapImage? GetFavicon(string url, Action? onLoaded = null) { string text = ExtractDomain(url); if (string.IsNullOrEmpty(text)) { return null; } if (_memCache.TryGetValue(text, out BitmapImage value)) { return value; } string text2 = Path.Combine(CacheDir, text + ".png"); if (File.Exists(text2)) { try { BitmapImage bitmapImage = LoadFromDisk(text2); _memCache[text] = bitmapImage; return bitmapImage; } catch { } } _memCache[text] = null; DownloadAsync(text, text2, onLoaded); return null; } private static async Task DownloadAsync(string domain, string diskPath, Action? onLoaded) { try { string faviconUrl = "https://www.google.com/s2/favicons?domain=" + Uri.EscapeDataString(domain) + "&sz=32"; byte[] bytes = await _http.GetByteArrayAsync(faviconUrl).ConfigureAwait(continueOnCapturedContext: false); if (bytes.Length < 100) { return; } Directory.CreateDirectory(CacheDir); await File.WriteAllBytesAsync(diskPath, bytes).ConfigureAwait(continueOnCapturedContext: false); Application current = Application.Current; if (current == null) { return; } ((DispatcherObject)current).Dispatcher.Invoke((Action)delegate { try { BitmapImage value = LoadFromDisk(diskPath); _memCache[domain] = value; onLoaded?.Invoke(); } catch { } }); } catch (Exception ex) { Exception ex2 = ex; LogService.Warn("Favicon 다운로드 실패: " + domain + " — " + ex2.Message); } } private static BitmapImage LoadFromDisk(string path) { BitmapImage bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.UriSource = new Uri(path, UriKind.Absolute); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.DecodePixelWidth = 32; bitmapImage.EndInit(); ((Freezable)bitmapImage).Freeze(); return bitmapImage; } private static string? ExtractDomain(string url) { try { if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { url = "https://" + url; } return new Uri(url).Host.ToLowerInvariant(); } catch { return null; } } }