package app import ( "context" "log" "time" "sanity/internal/backup" "sanity/internal/config" "sanity/internal/discord" ) type Runner struct { cfg config.Config executor *backup.Executor discord *discord.Client } func New(cfg config.Config) (*Runner, error) { return &Runner{ cfg: cfg, executor: backup.NewExecutor(), discord: discord.New(cfg.ServerName, cfg.DiscordWebhookURL, cfg.NotifyRoleID, cfg.RequestTimeout), }, nil } func (r *Runner) Run(ctx context.Context) error { if err := r.runBackup(ctx); err != nil { log.Printf("initial backup run failed: %v", err) } ticker := time.NewTicker(r.cfg.Backup.Interval) defer ticker.Stop() for { select { case <-ctx.Done(): return nil case <-ticker.C: if err := r.runBackup(ctx); err != nil { log.Printf("backup run failed: %v", err) } } } } func (r *Runner) runBackup(ctx context.Context) error { result, err := r.executor.Run(ctx, r.cfg) if err != nil { dirSizeBytes := int64(-1) if totalDirSize, totalDirSizeErr := backup.DirectorySize(r.cfg.Backup.OutputDir); totalDirSizeErr == nil { dirSizeBytes = totalDirSize } if notifyErr := r.discord.SendFailure(ctx, time.Now().UTC(), err.Error(), r.cfg.Backup.SourceFile, r.cfg.Backup.OutputDir, dirSizeBytes); notifyErr != nil { log.Printf("send failure notification failed: %v", notifyErr) } return err } if err := r.discord.SendSuccess(ctx, result, r.cfg.Backup.Interval); err != nil { log.Printf("send success notification failed: %v", err) } return nil }