210 lines
7.3 KiB
C#
210 lines
7.3 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))
|
||
{
|
||
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 { }
|
||
}
|
||
}
|