.NET WinForms-Anwendung (Core, Modules/CongressTrading, UI). Enthaelt .gitignore und settings.example.json als Konfigurationsvorlage. Echte settings.json mit Zugangsdaten ist bewusst ausgeschlossen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
147 lines
5.5 KiB
C#
147 lines
5.5 KiB
C#
using IBKRTrader.Core.Database;
|
||
using IBKRTrader.Core.Logging;
|
||
using IBKRTrader.Core.Settings;
|
||
|
||
namespace IBKRTrader.Core.Workers.BuiltIn;
|
||
|
||
/// <summary>
|
||
/// BackupWorker (Core) – läuft alle 30 Minuten (konfigurierbar).
|
||
/// 1. Ruft mysqldump.exe auf → SQL-Dump in Backups\DB\
|
||
/// 2. Kopiert Logs\ → Backups\Logs\
|
||
/// Falls mysqldump nicht gefunden: Warnung und Überspringen.
|
||
/// </summary>
|
||
public class BackupWorker : WorkerBase
|
||
{
|
||
private readonly SettingsService _settings;
|
||
|
||
public override string Name => "BackupWorker";
|
||
public override string Module => "Core";
|
||
public override WorkerType Type => WorkerType.Worker;
|
||
|
||
protected override TimeSpan? Interval =>
|
||
TimeSpan.FromMinutes(_settings.Settings.WorkerSettings.BackupWorker.IntervalMinutes);
|
||
|
||
public BackupWorker(LoggingService logger, DatabaseService db, SettingsService settings)
|
||
: base(logger, db)
|
||
{
|
||
_settings = settings;
|
||
Info.Active = settings.Settings.WorkerSettings.BackupWorker.Enabled;
|
||
}
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken ct)
|
||
{
|
||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm");
|
||
var backupRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Backups");
|
||
var dbDir = Path.Combine(backupRoot, "DB");
|
||
var logsDir = Path.Combine(backupRoot, "Logs");
|
||
|
||
Directory.CreateDirectory(dbDir);
|
||
Directory.CreateDirectory(logsDir);
|
||
|
||
// ── 1. Datenbank-Dump ────────────────────────────────────────────────
|
||
await RunMysqlDumpAsync(dbDir, timestamp, ct);
|
||
|
||
// ── 2. Logs-Backup ───────────────────────────────────────────────────
|
||
CopyLogs(logsDir, timestamp);
|
||
|
||
Logger.Info(Module, $"Backup abgeschlossen: {timestamp}");
|
||
}
|
||
|
||
// ─── mysqldump ────────────────────────────────────────────────────────────
|
||
|
||
private async Task RunMysqlDumpAsync(string dbDir, string timestamp, CancellationToken ct)
|
||
{
|
||
var dump = FindMysqldump();
|
||
if (dump == null)
|
||
{
|
||
Logger.Warn(Module, "mysqldump.exe nicht gefunden – DB-Backup übersprungen.");
|
||
return;
|
||
}
|
||
|
||
var db = _settings.Settings.Database;
|
||
var dumpFile = Path.Combine(dbDir, $"{db.Database}_{timestamp}.sql");
|
||
|
||
var args = $"--host={db.Host} --port={db.Port} " +
|
||
$"--user={db.User} --password={db.Password} " +
|
||
$"--single-transaction --routines --triggers " +
|
||
$"{db.Database} --result-file=\"{dumpFile}\"";
|
||
|
||
var psi = new System.Diagnostics.ProcessStartInfo
|
||
{
|
||
FileName = dump,
|
||
Arguments = args,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true
|
||
};
|
||
|
||
using var proc = System.Diagnostics.Process.Start(psi)
|
||
?? throw new InvalidOperationException("mysqldump konnte nicht gestartet werden.");
|
||
|
||
await proc.WaitForExitAsync(ct);
|
||
|
||
if (proc.ExitCode != 0)
|
||
{
|
||
var err = await proc.StandardError.ReadToEndAsync(ct);
|
||
Logger.Warn(Module, $"mysqldump Exitcode {proc.ExitCode}: {err}");
|
||
}
|
||
else
|
||
{
|
||
Logger.Info(Module, $"DB-Dump erstellt: {Path.GetFileName(dumpFile)}");
|
||
}
|
||
}
|
||
|
||
private static string? FindMysqldump()
|
||
{
|
||
// Häufige Installationspfade auf Windows-Server
|
||
var candidates = new[]
|
||
{
|
||
"mysqldump.exe",
|
||
@"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqldump.exe",
|
||
@"C:\Program Files\MySQL\MySQL Server 8.4\bin\mysqldump.exe",
|
||
@"C:\xampp\mysql\bin\mysqldump.exe"
|
||
};
|
||
|
||
foreach (var c in candidates)
|
||
if (File.Exists(c)) return c;
|
||
|
||
// PATH-Suche
|
||
var pathVar = Environment.GetEnvironmentVariable("PATH") ?? "";
|
||
foreach (var dir in pathVar.Split(';'))
|
||
{
|
||
var full = Path.Combine(dir.Trim(), "mysqldump.exe");
|
||
if (File.Exists(full)) return full;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// ─── Logs kopieren ────────────────────────────────────────────────────────
|
||
|
||
private void CopyLogs(string logsBackupDir, string timestamp)
|
||
{
|
||
try
|
||
{
|
||
var srcLogs = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||
if (!Directory.Exists(srcLogs)) return;
|
||
|
||
var destDir = Path.Combine(logsBackupDir, timestamp);
|
||
CopyDirectory(srcLogs, destDir);
|
||
Logger.Info(Module, $"Logs-Backup erstellt: {destDir}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Warn(Module, $"Logs-Backup fehlgeschlagen: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private static void CopyDirectory(string src, string dest)
|
||
{
|
||
Directory.CreateDirectory(dest);
|
||
foreach (var file in Directory.GetFiles(src))
|
||
File.Copy(file, Path.Combine(dest, Path.GetFileName(file)), overwrite: true);
|
||
foreach (var dir in Directory.GetDirectories(src))
|
||
CopyDirectory(dir, Path.Combine(dest, Path.GetFileName(dir)));
|
||
}
|
||
}
|