Files
LehrerApp/LehrerApp.Core/Services/BackupService.cs
adminandClaude Sonnet 5 48d07a2b99 Datensicherheit: Backup, Verschlüsselung, App-Sperre (Kapitel 13.3)
Automatisches rollierendes Backup der Datenbank beim Start mit
Wiederherstellung über die Einstellungen, versionierte Schema-Migration,
optionale Passwort-Verschlüsselung der LiteDB-Datei und eine App-Sperre
nach Inaktivität mit eigenem Passwort.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 13:07:36 +02:00

64 lines
2.5 KiB
C#

namespace LehrerApp.Core.Services;
public record BackupInfo(string Path, DateTime CreatedAt, long SizeBytes);
/// <summary>
/// Rollierendes lokales Backup der LiteDB-Datei
/// (<c>&lt;AppData&gt;/LehrerApp/backups/lehrerapp-JJJJ-MM-TT_HH-mm-ss.db</c>).
/// Reine Dateikopie — unabhängig davon, ob die Datenbank verschlüsselt ist.
/// </summary>
public class BackupService
{
private const int DefaultKeepCount = 10;
private readonly string _backupDirectory;
public string BackupDirectory => _backupDirectory;
public BackupService(string appDataPath)
{
_backupDirectory = Path.Combine(appDataPath, "backups");
Directory.CreateDirectory(_backupDirectory);
}
/// Erstellt ein Backup der übergebenen Datenbankdatei, falls sie existiert.
/// Gibt den Pfad des neuen Backups zurück, oder null, wenn keine Datei vorhanden war.
public string? CreateBackup(string databasePath, int keepCount = DefaultKeepCount)
{
if (!File.Exists(databasePath)) return null;
// Millisekunden + Zufallssuffix, damit mehrere Backups innerhalb derselben Sekunde
// (z.B. mehrfaches Klicken auf "Jetzt sichern") sich nicht gegenseitig überschreiben.
var suffix = Guid.NewGuid().ToString("N")[..8];
var fileName = $"lehrerapp-{DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}-{suffix}.db";
var target = Path.Combine(_backupDirectory, fileName);
File.Copy(databasePath, target, overwrite: true);
PruneOldBackups(keepCount);
return target;
}
public List<BackupInfo> ListBackups() =>
Directory.GetFiles(_backupDirectory, "lehrerapp-*.db")
.Select(p => new BackupInfo(p, File.GetLastWriteTime(p), new FileInfo(p).Length))
.OrderByDescending(b => b.CreatedAt)
.ToList();
public void PruneOldBackups(int keepCount = DefaultKeepCount)
{
var backups = ListBackups();
foreach (var stale in backups.Skip(keepCount))
{
try { File.Delete(stale.Path); }
catch { /* nächster Lauf versucht es erneut */ }
}
}
/// Kopiert ein Backup an die Zielposition (z.B. über die aktive Datenbankdatei).
/// Der Aufrufer ist dafür verantwortlich, alle offenen Verbindungen vorher zu schließen.
public void RestoreBackup(string backupPath, string databasePath)
{
if (!File.Exists(backupPath)) throw new FileNotFoundException("Backup nicht gefunden.", backupPath);
File.Copy(backupPath, databasePath, overwrite: true);
}
}