corp-app/CORP-MAUI/Services/ImageCacheService.cs

210 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Concurrent;
using System.Diagnostics;
namespace CORP_MAUI.Services;
/// <summary>
/// Сервис для кэширования изображений на диске (файловый кэш)
/// </summary>
public class ImageCacheService
{
private static ImageCacheService? _instance;
public static ImageCacheService Instance => _instance ??= new ImageCacheService();
// Кэш путей к файлам (URL -> FilePath)
private readonly ConcurrentDictionary<string, string> _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}");
}
/// <summary>
/// Получить путь к файлу из кэша или загрузить изображение
/// </summary>
public async Task<string?> GetOrLoadImageFileAsync(string? url, Func<string, Task<byte[]?>>? 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;
}
/// <summary>
/// Получить путь к кэшированному файлу (если существует)
/// </summary>
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;
}
/// <summary>
/// Проверить, есть ли изображение в кэше
/// </summary>
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);
}
/// <summary>
/// Получить путь к файлу кэша для URL
/// </summary>
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);
/// <summary>
/// Очистить устаревший кэш
/// </summary>
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}");
}
}
/// <summary>
/// Очистить весь кэш
/// </summary>
public void ClearCache()
{
_pathCache.Clear();
try
{
if (Directory.Exists(_diskCachePath))
{
foreach (var file in Directory.GetFiles(_diskCachePath))
{
try { File.Delete(file); } catch { }
}
}
}
catch { }
}
}