using System.Collections.Concurrent; using System.Diagnostics; namespace CORP_MAUI.Services; /// /// Сервис для кэширования изображений на диске (файловый кэш) /// public class ImageCacheService { private static ImageCacheService? _instance; public static ImageCacheService Instance => _instance ??= new ImageCacheService(); // Кэш путей к файлам (URL -> FilePath) private readonly ConcurrentDictionary _pathCache = new(); // Путь к папке кэша на диске private readonly string _diskCachePath; // Максимальное время жизни кэша private readonly TimeSpan _cacheExpiry = TimeSpan.FromDays(7); private ImageCacheService() { _diskCachePath = Path.Combine(FileSystem.CacheDirectory, "avatars"); // Создаём папку кэша, если не существует if (!Directory.Exists(_diskCachePath)) { Directory.CreateDirectory(_diskCachePath); } Trace.WriteLine($"[ImageCacheService] Initialized. Cache path: {_diskCachePath}"); } /// /// Получить путь к файлу из кэша или загрузить изображение /// public async Task GetOrLoadImageFileAsync(string? url, Func>? loader = null) { if (string.IsNullOrEmpty(url)) { Trace.WriteLine($"[ImageCacheService] URL пустой, пропускаем"); return null; } Trace.WriteLine($"[ImageCacheService] >>> GetOrLoadImageFileAsync: {url}"); // 1. Проверяем кэш путей if (_pathCache.TryGetValue(url, out string? cachedPath) && File.Exists(cachedPath)) { Trace.WriteLine($"[ImageCacheService] ✅ Найден в кэше путей: {cachedPath}"); return cachedPath; } // 2. Проверяем файл на диске string filePath = GetCacheFilePath(url); Trace.WriteLine($"[ImageCacheService] Ожидаемый путь: {filePath}"); if (File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); Trace.WriteLine($"[ImageCacheService] Файл существует, размер: {fileInfo.Length}, время: {fileInfo.LastWriteTime}"); if (fileInfo.Length > 0 && fileInfo.LastWriteTime > DateTime.Now - _cacheExpiry) { _pathCache[url] = filePath; Trace.WriteLine($"[ImageCacheService] ✅ Загружен с диска: {filePath}"); return filePath; } try { File.Delete(filePath); } catch { } } // 3. Загружаем изображение if (loader != null) { try { Trace.WriteLine($"[ImageCacheService] >>> Загрузка изображения через loader..."); byte[]? imageBytes = await loader(url); if (imageBytes != null && imageBytes.Length > 0) { Trace.WriteLine($"[ImageCacheService] Получено {imageBytes.Length} байт, сохраняем в {filePath}"); await File.WriteAllBytesAsync(filePath, imageBytes); _pathCache[url] = filePath; Trace.WriteLine($"[ImageCacheService] ✅ Сохранено и добавлено в кэш"); return filePath; } else { Trace.WriteLine($"[ImageCacheService] ⚠️ Loader вернул пустые данные"); } } catch (Exception ex) { Trace.WriteLine($"[ImageCacheService] ❌ Ошибка загрузки {url}: {ex.Message}"); } } else { Trace.WriteLine($"[ImageCacheService] ⚠️ Loader не задан"); } return null; } /// /// Получить путь к кэшированному файлу (если существует) /// public string? GetCachedFilePath(string? url) { if (string.IsNullOrEmpty(url)) return null; if (_pathCache.TryGetValue(url, out string? cachedPath) && File.Exists(cachedPath)) { return cachedPath; } string filePath = GetCacheFilePath(url); if (File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); if (fileInfo.Length > 0) { _pathCache[url] = filePath; return filePath; } } return null; } /// /// Проверить, есть ли изображение в кэше /// public bool IsCached(string? url) { if (string.IsNullOrEmpty(url)) return false; if (_pathCache.TryGetValue(url, out string? path) && File.Exists(path)) return true; string filePath = GetCacheFilePath(url); return File.Exists(filePath); } /// /// Получить путь к файлу кэша для URL /// private string GetCacheFilePath(string url) { using var md5 = System.Security.Cryptography.MD5.Create(); byte[] hashBytes = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(url)); string hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); return Path.Combine(_diskCachePath, $"{hash}.jpg"); } // Для обратной совместимости public string? GetFromCache(string? url) => GetCachedFilePath(url); /// /// Очистить устаревший кэш /// public void CleanExpiredCache() { try { if (!Directory.Exists(_diskCachePath)) return; var expiredFiles = Directory.GetFiles(_diskCachePath) .Where(f => File.GetLastWriteTime(f) < DateTime.Now - _cacheExpiry) .ToList(); foreach (var file in expiredFiles) { try { File.Delete(file); } catch { } } if (expiredFiles.Count > 0) { Trace.WriteLine($"[ImageCacheService] Удалено {expiredFiles.Count} устаревших файлов"); } } catch (Exception ex) { Trace.WriteLine($"[ImageCacheService] Ошибка очистки: {ex.Message}"); } } /// /// Очистить весь кэш /// public void ClearCache() { _pathCache.Clear(); try { if (Directory.Exists(_diskCachePath)) { foreach (var file in Directory.GetFiles(_diskCachePath)) { try { File.Delete(file); } catch { } } } } catch { } } }