using System.Diagnostics; using Cronos; using OneDriveBackupService.Models; using OneDriveBackupService.OneDrive; namespace OneDriveBackupService; public class Worker(ILogger logger, ConfigData config, OneDriveClient client) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (Environment.GetCommandLineArgs().Contains("--run-once")) { logger.LogInformation("Manual backup triggered"); await RunBackup(DateTime.Now, stoppingToken); Environment.Exit(0); return; } var expression = CronExpression.Parse(config.Schedule); while (!stoppingToken.IsCancellationRequested) { var utcNow = DateTime.UtcNow; var cronTime = expression.GetNextOccurrence(utcNow); var delay = cronTime.GetValueOrDefault(utcNow) - utcNow; if (delay < TimeSpan.Zero) delay = TimeSpan.Zero; if (!cronTime.HasValue) { logger.LogError("Cron expression failed, falling back to default delay"); delay = TimeSpan.FromHours(12); } var nextRun = DateTime.Now + delay; logger.LogInformation("Next backup: {time}", nextRun.ToString("f")); await Task.Delay(delay, stoppingToken); await RunBackup(nextRun, stoppingToken); } } private async Task RunBackup(DateTime optimalTime, CancellationToken stoppingToken) { logger.LogInformation("Starting backup at {now}", DateTime.Now.ToString("f")); var file = await CreateBackupArchive(optimalTime, stoppingToken); if (string.IsNullOrEmpty(file)) { logger.LogError("Backup archive creation failed"); return; } client.UploadFile(file); var count = client.DeleteOldFiles(); logger.LogInformation("Deleted {count} old backups", count); logger.LogInformation("Backup completed"); } private async Task CreateBackupArchive(DateTime optimalTime, CancellationToken stoppingToken) { var timestamp = optimalTime.ToString("yyyyMMdd_HHmmss"); var output = $"/tmp/backup_{timestamp}.tar.gz"; var process = new Process { StartInfo = new ProcessStartInfo { FileName = "tar", ArgumentList = { "-czf", output, "-C", config.LocalRoot, "-T", Path.Combine(config.LocalRoot, config.IncludeFile) }, RedirectStandardOutput = true, RedirectStandardError = true } }; process.Start(); await process.WaitForExitAsync(stoppingToken); if (process.ExitCode != 0) { var error = await process.StandardError.ReadToEndAsync(stoppingToken); logger.LogError(error); return string.Empty; } return output; } }