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

186 lines
5.9 KiB
C#

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)) return null;
// 1. Проверяем кэш путей
if (_pathCache.TryGetValue(url, out string? cachedPath) && File.Exists(cachedPath))
{
return cachedPath;
}
// 2. Проверяем файл на диске
string filePath = GetCacheFilePath(url);
if (File.Exists(filePath))
{
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 0 && fileInfo.LastWriteTime > DateTime.Now - _cacheExpiry)
{
_pathCache[url] = filePath;
return filePath;
}
try { File.Delete(filePath); } catch { }
}
// 3. Загружаем изображение
if (loader != null)
{
try
{
byte[]? imageBytes = await loader(url);
if (imageBytes != null && imageBytes.Length > 0)
{
await File.WriteAllBytesAsync(filePath, imageBytes);
_pathCache[url] = filePath;
return filePath;
}
}
catch (Exception ex)
{
Trace.WriteLine($"[ImageCacheService] Ошибка загрузки {url}: {ex.Message}");
}
}
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 { }
}
}