package jobs import ( "context" "time" "github.com/google/uuid" "github.com/syncova/syncova/packages/scheduler" ) // ExecutionRequest beschreibt einen auszuführenden Sicherungslauf. type ExecutionRequest struct { // Job ist der auszuführende Auftrag. Job *Job // RunID ist die Kennung des Laufs. RunID uuid.UUID // CorrelationID verbindet den Lauf mit seinen Protokollzeilen. CorrelationID uuid.UUID // AttemptNumber ist die Nummer des Versuchs, beginnend bei 1. AttemptNumber int // ProgressCallback meldet den Fortschritt. // // Die Schleife nutzt ihn für die Lebendmeldung: Ein Lauf, der stundenlang // nichts von sich hören lässt, wäre von einem abgestürzten Server nicht zu // unterscheiden. ProgressCallback func(ExecutionProgress) } // ExecutionProgress meldet den Stand eines laufenden Vorgangs. type ExecutionProgress struct { // BytesProcessed ist die bisher gelesene Datenmenge. BytesProcessed int64 // FilesProcessed ist die Zahl bisher bearbeiteter Objekte. FilesProcessed int64 // CurrentSource benennt die gerade bearbeitete Quelle. CurrentSource string } // ExecutionResult ist das Ergebnis eines Sicherungslaufs. type ExecutionResult struct { // BytesProcessed ist die gelesene Datenmenge. BytesProcessed int64 // BytesWritten ist die abgelegte Datenmenge. BytesWritten int64 // FilesProcessed ist die Zahl bearbeiteter Objekte. FilesProcessed int64 // FilesSkipped ist die Zahl übergangener Objekte. // // Ist sie größer als 0, ist der Lauf ein Teilfehler — unabhängig davon, // ob der Executor einen Fehler meldet (PROMPT.md §138). FilesSkipped int64 // SkipReasons erklären die übergangenen Objekte. // // Eine Zahl allein hilft nicht weiter: „17 Objekte übergangen" ist keine // Auskunft, „17 Objekte wegen fehlender Leseberechtigung" schon. SkipReasons []string } // Executor führt einen Sicherungslauf aus. // // Die Schnittstelle hält die Ausführungsschleife frei von Backup Engine, // Repository und Providern. Das ist nicht nur Ordnung: Ohne sie liesse sich das // Zusammenspiel von Zeitplan, Wartungsfenster, Nebenläufigkeit und Wiederholung // nur mit einem echten Repository prüfen — also praktisch gar nicht. // // Eine Umsetzung muss zwei Regeln einhalten: // // 1. Der Kontext wird beachtet. Ein Abbruch muss den Lauf beenden, sonst // hinge das Herunterfahren des Dienstes am längsten Backup. // 2. Übergangene Objekte werden in FilesSkipped gemeldet, nicht verschwiegen. // Ein Lauf mit übergangenen Objekten ist niemals ein Erfolg. type Executor interface { // Execute führt den Lauf aus. Execute(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error) } // ExecutorFunc erlaubt es, eine Funktion als Executor zu verwenden. type ExecutorFunc func(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error) // Execute erfüllt die Executor-Schnittstelle. func (executorFunction ExecutorFunc) Execute(executionContext context.Context, executionRequest ExecutionRequest) (ExecutionResult, error) { return executorFunction(executionContext, executionRequest) } // NotImplementedExecutor meldet, dass keine Ausführung eingerichtet ist. // // Er ist der Standard, solange die Anbindung an die Backup Engine fehlt. Ein // Executor, der stillschweigend Erfolg meldete, wäre das gefährlichste // Fake-Feature der ganzen Anlage: Die Oberfläche zeigte grüne Läufe, und im // Repository läge nichts. type NotImplementedExecutor struct{} // Execute meldet die fehlende Ausführung als Fehler. func (executor *NotImplementedExecutor) Execute(_ context.Context, _ ExecutionRequest) (ExecutionResult, error) { return ExecutionResult{}, &ExecutionError{ Code: "EXECUTOR_NOT_CONFIGURED", Message: "Für diesen Dienst ist keine Ausführung eingerichtet; der Auftrag wurde nicht gesichert.", FailureClass: scheduler.FailureConfiguration, } } // ExecutionError ist ein eingeordneter Fehler eines Sicherungslaufs. // // Die Einordnung entscheidet über die Wiederholung. Ein Executor, der nur einen // gewöhnlichen Fehler zurückgibt, bekommt die sichere Voreinstellung // „dauerhaft" — es wird also nicht wiederholt. Wer Wiederholungen will, muss // die Fehlerart ausdrücklich benennen. type ExecutionError struct { // Code ist die Fehlerkennung in SCREAMING_SNAKE_CASE. Code string // Message ist die verständliche Meldung. // // Sie enthält niemals Geheimnisse (PROMPT.md §141). Message string // FailureClass ordnet den Fehler ein. FailureClass scheduler.FailureClass // Cause ist der zugrunde liegende Fehler. Cause error } // Error erfüllt die Fehlerschnittstelle. func (executionError *ExecutionError) Error() string { return executionError.Message } // Unwrap gibt die Ursache frei. func (executionError *ExecutionError) Unwrap() error { return executionError.Cause } // ExecutionOutcome ist das ausgewertete Ergebnis eines Laufs. type ExecutionOutcome struct { // Status ist der erreichte Zustand. Status RunStatus // Result sind die Kennzahlen. Result ExecutionResult // FailureClass ordnet einen Fehler ein. FailureClass scheduler.FailureClass // ErrorCode ist die Fehlerkennung. ErrorCode string // ErrorMessage ist die verständliche Meldung. ErrorMessage string // Duration ist die Dauer des Laufs. Duration time.Duration }