/
rezvich
/
Prik
Обзор
Документация
Войти
/
rezvich
/
Prik
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Infrastructure/Files/FileArchiver.cs
133 строки
5 KB
waweda299
Прик
08 июн 2026, 15:52
08 июн 2026, 15:52
9ddc25e
Код
Авторство
О чём код?
// prik/Infrastructure/Files/FileArchiver.cs using Microsoft.Extensions.Logging; using prik.Infrastructure.Settings; using System.Text; namespace prik.Infrastructure.Files { public sealed class FileArchiver : IFileArchiver { private readonly ILogger<FileArchiver> _log; public FileArchiver(ILogger<FileArchiver> log) => _log = log; public bool TryAcquireToWork(FileInfo input, AppSettings.PipelineSettings p, out WorkItem item) { item = default!; Directory.CreateDirectory(p.WorkDir); var logicalName = input.Name; var src = input.FullName; var dest = Path.Combine(p.WorkDir, input.Name); // если уже есть в work — считаем, что файл уже обрабатывается/остался хвост if (File.Exists(dest)) { _log.LogWarning("Work file already exists, skip acquire: {Dest}", dest); return false; } try { MoveWithRetry(src, dest); item = new WorkItem(SourceFullName: src, LogicalName: logicalName, WorkFile: new FileInfo(dest)); _log.LogInformation("Acquired to work: src={Src} work={Work}", src, dest); return true; } catch (Exception ex) { _log.LogWarning(ex, "Failed to acquire to work: {Src}", src); return false; } } public void ArchiveProcessed(WorkItem item, AppSettings.PipelineSettings p) { var archInput = Path.Combine(p.ArchiveDir, "input"); Directory.CreateDirectory(archInput); var dest = Path.Combine(archInput, item.LogicalName); MoveWithRetry(item.WorkFile.FullName, dest); _log.LogInformation("Archived OK: work={Work} -> {Dest}", item.WorkFile.FullName, dest); } public void QuarantineFailed(WorkItem item, AppSettings.PipelineSettings p, Exception ex) { var outLogs = Path.Combine(p.OutputDir, "logs"); Directory.CreateDirectory(outLogs); var failTxt = Path.Combine(outLogs, $"{Path.GetFileNameWithoutExtension(item.LogicalName)}.fail.txt"); File.WriteAllText( failTxt, $"SOURCE: {item.SourceFullName}\nWORK: {item.WorkFile.FullName}\nERROR: {ex}\n\nSTACK:\n{ex.StackTrace}\n", Encoding.UTF8); var archFailed = Path.Combine(p.ArchiveDir, "failed"); Directory.CreateDirectory(archFailed); // без суффиксов var dest = Path.Combine(archFailed, item.LogicalName); try { // перезаписываем, чтобы не появлялись .2025... if (File.Exists(dest)) { File.Delete(dest); } MoveWithRetry(item.WorkFile.FullName, dest); _log.LogWarning("Quarantined FAILED: work={Work} -> {Dest}. fail.txt={FailTxt}", item.WorkFile.FullName, dest, failTxt); } catch (Exception moveEx) { _log.LogError(moveEx, "Failed to quarantine. fail.txt={FailTxt}", failTxt); } } public IReadOnlyList<WorkItem> ListWork(AppSettings.PipelineSettings p) { var dir = new DirectoryInfo(p.WorkDir); if (!dir.Exists) { return Array.Empty<WorkItem>(); } // Поднимаем всё, что осталось в work (например, после падения процесса). // LogicalName тут не восстановить идеально — используем имя файла. return dir.EnumerateFiles("*", SearchOption.TopDirectoryOnly) .Where(f => string.Equals(f.Extension, ".csv", StringComparison.OrdinalIgnoreCase)) .OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase) .Select(f => new WorkItem(SourceFullName: "(unknown)", LogicalName: f.Name, WorkFile: f)) .ToList(); } private static void MoveWithRetry(string src, string dest, int attempts = 5, int delayMs = 300) { Directory.CreateDirectory(Path.GetDirectoryName(dest)!); for (int i = 1; i <= attempts; i++) { try { File.Move(src, dest, overwrite: true); // на одном томе — атомарно return; } catch (IOException) when (i < attempts) { Thread.Sleep(delayMs); } catch (UnauthorizedAccessException) when (i < attempts) { Thread.Sleep(delayMs); } } File.Move(src, dest, overwrite: true); } } }