Add retry mechanism and failure notifications for nCore/qBittorrent setup

- Implemented a retry mechanism for external calls, allowing up to 3 attempts before failing.
- Enhanced error handling to send failure notifications when setup steps fail, including detailed error messages.
- Updated RunSummary model to include status, error step, and error message fields for better tracking of run outcomes.
- Modified database schema to store failure metadata for runs.
- Updated README.md to reflect changes in error handling and notification behavior.
This commit is contained in:
Zsolt Alföldi
2026-05-07 12:03:00 +02:00
parent 696d0227a3
commit 89835b237c
6 changed files with 268 additions and 41 deletions

View File

@@ -135,10 +135,17 @@ func (s *Store) MarkManualNeeded(id int64, now time.Time) error {
}
func (s *Store) InsertRun(summary model.RunSummary) error {
status := summary.Status
if status == "" {
status = "success"
}
_, err := s.db.Exec(`
INSERT INTO runs (started_at, dry_run, total_risk, matched, unmatched, force_started, reannounced, manual_needed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, summary.StartedAt.Format(time.RFC3339), boolInt(summary.DryRun), summary.TotalRisk, summary.Matched, summary.Unmatched, summary.ForceStarted, summary.Reannounced, summary.ManualNeeded)
INSERT INTO runs (
started_at, status, error_step, error_message, dry_run,
total_risk, matched, unmatched, force_started, reannounced, manual_needed
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, summary.StartedAt.Format(time.RFC3339), status, summary.ErrorStep, summary.ErrorMessage, boolInt(summary.DryRun), summary.TotalRisk, summary.Matched, summary.Unmatched, summary.ForceStarted, summary.Reannounced, summary.ManualNeeded)
return err
}
@@ -215,11 +222,22 @@ func (s *Store) lastRun() (*model.RunRecord, error) {
var run model.RunRecord
var dryRun int
err := s.db.QueryRow(`
SELECT started_at, dry_run, total_risk, matched, unmatched, force_started, reannounced, manual_needed
SELECT
started_at,
status,
COALESCE(error_step, ''),
COALESCE(error_message, ''),
dry_run,
total_risk,
matched,
unmatched,
force_started,
reannounced,
manual_needed
FROM runs
ORDER BY id DESC
LIMIT 1
`).Scan(&run.StartedAt, &dryRun, &run.TotalRisk, &run.Matched, &run.Unmatched, &run.ForceStarted, &run.Reannounced, &run.ManualNeeded)
`).Scan(&run.StartedAt, &run.Status, &run.ErrorStep, &run.ErrorMessage, &dryRun, &run.TotalRisk, &run.Matched, &run.Unmatched, &run.ForceStarted, &run.Reannounced, &run.ManualNeeded)
if err == sql.ErrNoRows {
return nil, nil
}
@@ -327,6 +345,9 @@ func (s *Store) migrate() error {
CREATE TABLE IF NOT EXISTS runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
started_at TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'success',
error_step TEXT,
error_message TEXT,
dry_run INTEGER NOT NULL,
total_risk INTEGER NOT NULL,
matched INTEGER NOT NULL,
@@ -336,6 +357,47 @@ func (s *Store) migrate() error {
manual_needed INTEGER NOT NULL
);
`)
if err != nil {
return err
}
if err := s.addColumnIfMissing("runs", "status", "TEXT NOT NULL DEFAULT 'success'"); err != nil {
return err
}
if err := s.addColumnIfMissing("runs", "error_step", "TEXT"); err != nil {
return err
}
if err := s.addColumnIfMissing("runs", "error_message", "TEXT"); err != nil {
return err
}
return err
}
func (s *Store) addColumnIfMissing(table string, column string, definition string) error {
rows, err := s.db.Query(fmt.Sprintf(`PRAGMA table_info(%s)`, table))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var cid int
var name string
var columnType string
var notNull int
var defaultValue sql.NullString
var pk int
if err := rows.Scan(&cid, &name, &columnType, &notNull, &defaultValue, &pk); err != nil {
return err
}
if name == column {
return rows.Err()
}
}
if err := rows.Err(); err != nil {
return err
}
_, err = s.db.Exec(fmt.Sprintf(`ALTER TABLE %s ADD COLUMN %s %s`, table, column, definition))
return err
}