using Microsoft.Extensions.Logging;
namespace TeleWave.Infrastructure.Media;
///
/// Уборка перевалочного каталога uploads/: удаляет файлы, которых не касались дольше порога.
/// После регистрации ассета файл уходит в originals/, а токен загрузки нигде не хранится —
/// значит, всё залежавшееся в uploads/ это след оборванной заливки или аварийного рестарта,
/// и подобрать его некому.
///
public static class UploadsCleaner
{
///
/// Один проход. Порог считается от времени последней записи, а не создания: идущая многочасовая
/// заливка правит файл и под него не попадает.
///
public static void Sweep(MediaPathResolver paths, TimeSpan retention, ILogger logger)
{
try
{
if (!Directory.Exists(paths.UploadsDir))
return;
var deadline = DateTime.UtcNow - retention;
foreach (var path in Directory.EnumerateFiles(paths.UploadsDir))
DeleteIfStale(path, deadline, logger);
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка уборки uploads/");
}
}
private static void DeleteIfStale(string path, DateTime deadline, ILogger logger)
{
try
{
var file = new FileInfo(path);
if (!file.Exists || file.LastWriteTimeUtc > deadline)
return;
// Метрики снимаем до удаления: после него FileInfo их уже не отдаст.
var size = file.Length;
var lastWrite = file.LastWriteTimeUtc;
file.Delete();
logger.LogInformation(
"Удалён брошенный файл загрузки {Name} ({Size} байт, последняя запись {LastWrite:u})",
file.Name,
size,
lastWrite
);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
// Файл занят или права не те — попробуем на следующем тике.
logger.LogWarning(ex, "Не удалось удалить файл загрузки {Path}", path);
}
}
}