add ProxyJump function
This commit is contained in:
1 parent
56e20e5464
commit
7f0e0fdb23
48 files changed
+3875
-44
No files matched your search
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BulkArchiveMailer.Core
|
||||
{
|
||||
/// <summary>Everything ArchiveScanner.Scan found in one folder, split by shape so the caller can render them however it likes.</summary>
|
||||
public sealed class ScanResult
|
||||
{
|
||||
/// <summary>Ready / AlreadySent / Error rows - one per login that had at least one recognized *.7z file.</summary>
|
||||
public List<SendJob> Jobs { get; } = new List<SendJob>();
|
||||
|
||||
/// <summary>One Skipped row per file that did not match the expected naming pattern.</summary>
|
||||
public List<SendJob> UnrecognizedFiles { get; } = new List<SendJob>();
|
||||
|
||||
/// <summary>Convenience: both lists concatenated, sorted the same way PreviewForm wants to bind a single grid.</summary>
|
||||
public IEnumerable<SendJob> AllRows => Jobs.Concat(UnrecognizedFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks a folder of ssh-access-tui output archives (plain System.IO -
|
||||
/// no Outlook/COM involved) and builds the list of preview rows.
|
||||
/// </summary>
|
||||
public static class ArchiveScanner
|
||||
{
|
||||
/// <param name="folderPath">Folder to scan (non-recursive - ssh-access-tui writes archives flat into one output directory).</param>
|
||||
/// <param name="emailDomain">Domain appended to the derived local part, e.g. "redlab.lo".</param>
|
||||
/// <param name="templates">Used to render each Ready job's Subject/BodyHtml. May be null (rows are then created with empty Subject/BodyHtml, useful for pure filename-parsing tests).</param>
|
||||
/// <param name="sendLog">When provided, each single-archive login is checked against the log (by SHA-256 of the archive's current bytes) and marked AlreadySent if a prior Sent row matches. When null, dedup is skipped entirely and every recognized single-archive login comes back Ready.</param>
|
||||
public static ScanResult Scan(string folderPath, string emailDomain, MailTemplateSet templates, SendLogStore sendLog = null)
|
||||
{
|
||||
var result = new ScanResult();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
|
||||
{
|
||||
// Caller (SettingsForm/PreviewForm) is expected to validate
|
||||
// the configured folder up front and show its own error;
|
||||
// scanning a missing folder just yields an empty result
|
||||
// rather than throwing.
|
||||
return result;
|
||||
}
|
||||
|
||||
// login -> every (path, kind) found for it, case-insensitive
|
||||
// because the file system this runs on (Windows) is too.
|
||||
var byLogin = new Dictionary<string, List<(string Path, ArchiveKind Kind)>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var filePath in Directory.EnumerateFiles(folderPath, "*.7z", SearchOption.TopDirectoryOnly).OrderBy(p => p, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
if (!FilenameParser.TryParse(fileName, out var parsed))
|
||||
{
|
||||
result.UnrecognizedFiles.Add(new SendJob
|
||||
{
|
||||
ArchivePath = filePath,
|
||||
Status = SendStatus.Skipped,
|
||||
ErrorMessage = $"Имя файла не соответствует шаблону \"{{login}}_ssh_access.7z\" / \"{{login}}_config_update.7z\": {fileName}",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!byLogin.TryGetValue(parsed.Login, out var list))
|
||||
{
|
||||
list = new List<(string, ArchiveKind)>();
|
||||
byLogin[parsed.Login] = list;
|
||||
}
|
||||
|
||||
list.Add((filePath, parsed.Kind));
|
||||
}
|
||||
|
||||
foreach (var kv in byLogin.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var login = kv.Key;
|
||||
var files = kv.Value;
|
||||
var email = EmailAddressBuilder.BuildEmail(login, emailDomain);
|
||||
|
||||
if (files.Count > 1)
|
||||
{
|
||||
// Both archive kinds present for one login (or, less
|
||||
// likely, a same-kind duplicate left behind from a
|
||||
// prior export) - this is a data error upstream. Never
|
||||
// auto-pick one; surface it as a blocking row instead.
|
||||
var names = string.Join(", ", files.Select(f => Path.GetFileName(f.Path)));
|
||||
result.Jobs.Add(new SendJob
|
||||
{
|
||||
Login = login,
|
||||
Email = email,
|
||||
Kind = null,
|
||||
ArchivePath = null,
|
||||
ConflictingArchivePaths = files.Select(f => f.Path).ToList(),
|
||||
Status = SendStatus.Error,
|
||||
ErrorMessage = $"Для логина \"{login}\" одновременно найдено {files.Count} архива(ов): {names}. Это ошибка данных (должен существовать только один архив на логин за раз) — требуется ручная проверка на jump-хосте, автоматический выбор не выполняется.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var (path, kind) = files[0];
|
||||
var rendered = templates != null
|
||||
? TemplateEngine.Render(templates.Get(kind), login, email)
|
||||
: new MailTemplate();
|
||||
|
||||
var job = new SendJob
|
||||
{
|
||||
Login = login,
|
||||
Email = email,
|
||||
Kind = kind,
|
||||
ArchivePath = path,
|
||||
Subject = rendered.Subject,
|
||||
BodyHtml = rendered.BodyHtml,
|
||||
Status = SendStatus.Ready,
|
||||
};
|
||||
|
||||
if (sendLog != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
job.FileSha256 = SendLogStore.ComputeSha256(path);
|
||||
if (sendLog.TryFindSent(email, kind, job.FileSha256, out var priorEntry))
|
||||
{
|
||||
job.Status = SendStatus.AlreadySent;
|
||||
job.ErrorMessage = $"Уже отправлено {priorEntry.TimestampUtc:yyyy-MM-dd HH:mm} UTC (тот же файл, без изменений с тех пор).";
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// File locked/unreadable right now - don't block
|
||||
// the whole scan over one bad file, surface it as
|
||||
// an error row instead.
|
||||
job.Status = SendStatus.Error;
|
||||
job.ErrorMessage = $"Не удалось прочитать файл архива для проверки дубликатов: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
result.Jobs.Add(job);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user