Compare commits

..

5 Commits

Author SHA1 Message Date
523441dd2f extra logging 2025-12-17 00:15:43 -05:00
6e91d188fe ensure proper disposal of objects 2025-12-17 00:15:23 -05:00
ea7ff0b051 changed logging to allow debugging 2025-12-16 23:46:58 -05:00
63fa071180 Merge remote-tracking branch 'main/master'
# Conflicts:
#	Program.cs
2025-12-16 23:28:22 -05:00
3a1ddd7341 download in parallel 2025-12-16 23:27:50 -05:00

View File

@@ -4,40 +4,65 @@ using Cronos;
using Renci.SshNet;
using Renci.SshNet.Sftp;
using Serilog;
using Serilog.Core;
using Serilog.Events;
var resetEvent = new ManualResetEvent(false);
using var tokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (sender, eventArgs) =>
Console.CancelKeyPress += (_, eventArgs) =>
{
resetEvent?.Set();
tokenSource?.Cancel();
Log.Debug("Canceling with CancelKeyPress");
Dispose();
eventArgs.Cancel = true;
Log.CloseAndFlush();
};
AppDomain.CurrentDomain.ProcessExit += (_, _) => Dispose();
SetupLogging();
//TODO: check environment variables to see if config file is enabled, otherwise use env vars
var config = LoadConfig();
Log.Information("Configuration file loaded. Beginning initial grab.");
await RunJob(config, tokenSource.Token);
Log.Information("Initial grab complete. Initializing schedule.");
Task.Run(() => ScheduleJobs(config, tokenSource.Token));
Log.Verbose("Jobs scheduled. Main Thread waiting.");
resetEvent.WaitOne();
return;
void Dispose()
{
resetEvent?.Set();
tokenSource?.Cancel();
Log.CloseAndFlush();
}
static void SetupLogging()
{
var minLevel = (Environment.GetEnvironmentVariable("LOG_LEVEL") ?? "info").ToUpperInvariant() switch
{
"VERBOSE" => LogEventLevel.Verbose,
"DEBUG" => LogEventLevel.Debug,
"WARNING" => LogEventLevel.Warning,
"ERROR" => LogEventLevel.Error,
"FATAL" => LogEventLevel.Fatal,
_ => LogEventLevel.Information
};
var levelSwitch = new LoggingLevelSwitch(minLevel);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.Console()
.WriteTo.File("log.txt",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
retainedFileCountLimit: 10)
.CreateLogger();
Log.Debug($"Logging initialized at {minLevel}");
}
static Config LoadConfig()
@@ -48,13 +73,14 @@ static Config LoadConfig()
WriteIndented = true
};
//TODO: stop creating dummy config file
if (!File.Exists("/config.json"))
{
Log.Fatal("No config file found, creating dummy config.");
CreateConfig(serializationOptions);
Environment.Exit(1);
}
Log.Verbose("Loading config from file.");
var configFile = File.ReadAllText("/config.json");
return JsonSerializer.Deserialize<Config>(configFile, serializationOptions);
}
@@ -69,6 +95,7 @@ static void CreateConfig(JsonSerializerOptions options)
static async Task ScheduleJobs(Config config, CancellationToken token)
{
Log.Debug($"Scheduling job with cron expression '{config.Schedule}'");
var schedule = CronExpression.Parse(config.Schedule);
while (!token.IsCancellationRequested)
@@ -80,11 +107,12 @@ static async Task ScheduleJobs(Config config, CancellationToken token)
Log.Information($"Next scheduled scan at {nextJob.Value}");
var delay = nextJob.Value - now;
Log.Debug($"{delay} until next scan at {nextJob.Value}");
if (delay > TimeSpan.Zero)
{
await Task.Delay(delay, token);
}
Log.Debug($"Delay {delay} wait is complete, beginning scan now.");
await RunJob(config, token);
}
}
@@ -92,9 +120,12 @@ static async Task ScheduleJobs(Config config, CancellationToken token)
static async Task RunJob(Config config, CancellationToken token)
{
using var client = GetClient(config.FTP);
Log.Information($"Connecting to {config.FTP.Host}:{config.FTP.Port} as {config.FTP.UserName}");
var buffer = Math.Clamp(config.BufferSizeMB ?? 1, 0.5f, 4);
client.BufferSize = (uint)(1024 * 1024 * buffer);
Log.Information($"Connecting to {config.FTP.Host}:{config.FTP.Port} as {config.FTP.UserName}");
Log.Debug($"Connection buffer size: {client.BufferSize}");
await client.ConnectAsync(token);
foreach (var target in config.Targets)
{
@@ -107,17 +138,19 @@ static async Task RunJob(Config config, CancellationToken token)
static async Task RecurseDirectory(SftpClient client, string source, string destination, bool deleteDirectory, CancellationToken token)
{
Log.Information($"Scanning directory '{source}'");
Parallel.ForEachAsync(client.ListDirectoryAsync(source, token), token, async (item,token) =>
await Parallel.ForEachAsync(client.ListDirectoryAsync(source, token), token, async (item,token) =>
{
if (item.Name is ".." or ".") return;
if (item.IsDirectory)
{
Log.Verbose($"{item.Name} is a directory");
var newPath = Path.Combine(destination, item.Name);
await RecurseDirectory(client, item.FullName, newPath, true, token);
return;
}
Log.Verbose($"{item.Name} is a file");
await DownloadFile(client, item, destination, token);
Log.Information($"Deleting '{item.Name}'");
await client.DeleteAsync(item.FullName, token);
@@ -133,9 +166,11 @@ static async Task RecurseDirectory(SftpClient client, string source, string dest
static async Task DownloadFile(SftpClient client, ISftpFile item, string destination, CancellationToken token)
{
Directory.CreateDirectory(destination);
Log.Verbose($"Ensuring '{destination}' path exists.");
await using var stream = File.Open(Path.Combine(destination, item.Name), FileMode.Create, FileAccess.Write);
Log.Information($"Downloading '{item.FullName}' to '{stream.Name}'");
await client.DownloadFileAsync(item.FullName, stream, token);
Log.Verbose("Download completed.");
}
static SftpClient GetClient(SFtpTarget target)