refactor: Clean up whitespace and improve formatting in backup update logic

This commit is contained in:
Peter Wood
2025-05-30 08:57:28 -04:00
parent 137e5e8e2f
commit 374da47bf5

View File

@@ -479,10 +479,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.backupStatus[msg.service] == nil { if m.backupStatus[msg.service] == nil {
m.backupStatus[msg.service] = &BackupStatus{} m.backupStatus[msg.service] = &BackupStatus{}
} }
// Create context for this backup // Create context for this backup
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
m.backupStatus[msg.service].Service = msg.service m.backupStatus[msg.service].Service = msg.service
m.backupStatus[msg.service].Status = "running" m.backupStatus[msg.service].Status = "running"
m.backupStatus[msg.service].LastRun = time.Now() m.backupStatus[msg.service].LastRun = time.Now()
@@ -490,9 +490,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.backupStatus[msg.service].Progress = 0 m.backupStatus[msg.service].Progress = 0
m.backupStatus[msg.service].Context = ctx m.backupStatus[msg.service].Context = ctx
m.backupStatus[msg.service].Cancel = cancel m.backupStatus[msg.service].Cancel = cancel
m.viewport.SetContent(fmt.Sprintf("🚀 Starting backup for %s...\n\nInitializing backup process...\nPress 'x' to cancel if needed.", msg.service)) m.viewport.SetContent(fmt.Sprintf("🚀 Starting backup for %s...\n\nInitializing backup process...\nPress 'x' to cancel if needed.", msg.service))
// Start progress ticker for time-based progress estimation // Start progress ticker for time-based progress estimation
cmds = append(cmds, m.startProgressTicker(msg.service)) cmds = append(cmds, m.startProgressTicker(msg.service))
@@ -547,18 +547,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.backupStatus[msg.service].EndTime = time.Now() m.backupStatus[msg.service].EndTime = time.Now()
m.backupStatus[msg.service].Duration = m.backupStatus[msg.service].EndTime.Sub(m.backupStatus[msg.service].StartTime).String() m.backupStatus[msg.service].Duration = m.backupStatus[msg.service].EndTime.Sub(m.backupStatus[msg.service].StartTime).String()
m.backupStatus[msg.service].Progress = 100 m.backupStatus[msg.service].Progress = 100
// Clean up context // Clean up context
if m.backupStatus[msg.service].Cancel != nil { if m.backupStatus[msg.service].Cancel != nil {
m.backupStatus[msg.service].Cancel() m.backupStatus[msg.service].Cancel()
} }
} }
delete(m.runningBackups, msg.service) delete(m.runningBackups, msg.service)
// Parse output for additional info // Parse output for additional info
lines := strings.Split(msg.output, "\n") lines := strings.Split(msg.output, "\n")
var summary strings.Builder var summary strings.Builder
// Extract meaningful summary information // Extract meaningful summary information
for _, line := range lines { for _, line := range lines {
if strings.Contains(line, "Total files:") || if strings.Contains(line, "Total files:") ||
@@ -569,18 +569,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
summary.WriteString("📊 " + strings.TrimSpace(line) + "\n") summary.WriteString("📊 " + strings.TrimSpace(line) + "\n")
} }
} }
summaryText := summary.String() summaryText := summary.String()
if summaryText == "" { if summaryText == "" {
summaryText = "📊 Backup completed successfully" summaryText = "📊 Backup completed successfully"
} }
duration := "" duration := ""
if m.backupStatus[msg.service] != nil { if m.backupStatus[msg.service] != nil {
duration = m.backupStatus[msg.service].Duration duration = m.backupStatus[msg.service].Duration
} }
m.viewport.SetContent(fmt.Sprintf("✅ Backup completed successfully for %s!\n\nDuration: %s\n\n%s\n\n📋 Full Output:\n%s", m.viewport.SetContent(fmt.Sprintf("✅ Backup completed successfully for %s!\n\nDuration: %s\n\n%s\n\n📋 Full Output:\n%s",
msg.service, duration, summaryText, msg.output)) msg.service, duration, summaryText, msg.output))
case backupErrorMsg: case backupErrorMsg:
@@ -589,19 +589,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.backupStatus[msg.service].Error = msg.error m.backupStatus[msg.service].Error = msg.error
m.backupStatus[msg.service].EndTime = time.Now() m.backupStatus[msg.service].EndTime = time.Now()
m.backupStatus[msg.service].Duration = m.backupStatus[msg.service].EndTime.Sub(m.backupStatus[msg.service].StartTime).String() m.backupStatus[msg.service].Duration = m.backupStatus[msg.service].EndTime.Sub(m.backupStatus[msg.service].StartTime).String()
// Clean up context // Clean up context
if m.backupStatus[msg.service].Cancel != nil { if m.backupStatus[msg.service].Cancel != nil {
m.backupStatus[msg.service].Cancel() m.backupStatus[msg.service].Cancel()
} }
} }
delete(m.runningBackups, msg.service) delete(m.runningBackups, msg.service)
duration := "" duration := ""
if m.backupStatus[msg.service] != nil { if m.backupStatus[msg.service] != nil {
duration = m.backupStatus[msg.service].Duration duration = m.backupStatus[msg.service].Duration
} }
// Provide helpful error analysis // Provide helpful error analysis
var errorAnalysis string var errorAnalysis string
if strings.Contains(msg.error, "permission denied") { if strings.Contains(msg.error, "permission denied") {
@@ -613,8 +613,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} else if strings.Contains(msg.error, "space") { } else if strings.Contains(msg.error, "space") {
errorAnalysis = "\n💡 This might be a disk space issue. Check available storage." errorAnalysis = "\n💡 This might be a disk space issue. Check available storage."
} }
m.viewport.SetContent(fmt.Sprintf("❌ Backup failed for %s!\n\nDuration: %s%s\n\n🚨 Error Details:\n%s", m.viewport.SetContent(fmt.Sprintf("❌ Backup failed for %s!\n\nDuration: %s%s\n\n🚨 Error Details:\n%s",
msg.service, duration, errorAnalysis, msg.error)) msg.service, duration, errorAnalysis, msg.error))
case spinner.TickMsg: case spinner.TickMsg:
@@ -663,11 +663,11 @@ func (m *Model) updateViewportForRunningService(service string, outputForDisplay
totalEstimatedDuration := time.Duration(totalEstimatedRaw) totalEstimatedDuration := time.Duration(totalEstimatedRaw)
remainingDuration := totalEstimatedDuration - elapsed remainingDuration := totalEstimatedDuration - elapsed
if remainingDuration > 0 { if remainingDuration > 0 {
etaStr = fmt.Sprintf("\\nETA: %s", remainingDuration.Round(time.Second)) etaStr = fmt.Sprintf("\nETA: %s", remainingDuration.Round(time.Second))
} }
} }
m.viewport.SetContent(fmt.Sprintf("⏳ Backup in progress for %s...\\n\\n%s %d%%%%\\nElapsed: %s%s\\n\\n📋 Latest output:\\n%s\\n\\nPress 'x' to stop the backup.", m.viewport.SetContent(fmt.Sprintf("⏳ Backup in progress for %s...\n\n%s %d%%\nElapsed: %s%s\n\n📋 Latest output:\n%s\n\nPress 'x' to stop the backup.",
service, progressBar, progress, elapsed.Round(time.Second), etaStr, outputForDisplay)) service, progressBar, progress, elapsed.Round(time.Second), etaStr, outputForDisplay))
} }
@@ -702,7 +702,7 @@ func (m Model) View() string {
if m.activePanel == 1 { if m.activePanel == 1 {
viewportStyle = viewportStyle.BorderStyle(lipgloss.ThickBorder()).BorderForeground(lipgloss.Color("#7D56F4")) viewportStyle = viewportStyle.BorderStyle(lipgloss.ThickBorder()).BorderForeground(lipgloss.Color("#7D56F4"))
} }
// Add view indicator to viewport title // Add view indicator to viewport title
viewportTitle := "" viewportTitle := ""
switch m.currentView { switch m.currentView {
@@ -715,7 +715,7 @@ func (m Model) View() string {
default: default:
viewportTitle = "💻 Output" viewportTitle = "💻 Output"
} }
// Layout based on active panel // Layout based on active panel
return lipgloss.JoinVertical(lipgloss.Left, return lipgloss.JoinVertical(lipgloss.Left,
titleStyle.Render("🔧 Media & Plex Backup Manager"), titleStyle.Render("🔧 Media & Plex Backup Manager"),
@@ -833,7 +833,7 @@ func (m *Model) showStatus() {
for _, service := range services { for _, service := range services {
status := m.backupStatus[service] status := m.backupStatus[service]
content.WriteString(fmt.Sprintf("🔧 Service: %s\n", strings.ToUpper(service))) content.WriteString(fmt.Sprintf("🔧 Service: %s\n", strings.ToUpper(service)))
// Status with emoji // Status with emoji
statusIcon := "⭕" statusIcon := "⭕"
switch status.Status { switch status.Status {
@@ -847,34 +847,34 @@ func (m *Model) showStatus() {
statusIcon = "🛑" statusIcon = "🛑"
} }
content.WriteString(fmt.Sprintf(" Status: %s %s\n", statusIcon, status.Status)) content.WriteString(fmt.Sprintf(" Status: %s %s\n", statusIcon, status.Status))
if !status.LastRun.IsZero() { if !status.LastRun.IsZero() {
content.WriteString(fmt.Sprintf(" Last Run: %s\n", status.LastRun.Format("2006-01-02 15:04:05"))) content.WriteString(fmt.Sprintf(" Last Run: %s\n", status.LastRun.Format("2006-01-02 15:04:05")))
} }
if status.Duration != "" { if status.Duration != "" {
content.WriteString(fmt.Sprintf(" Duration: %s\n", status.Duration)) content.WriteString(fmt.Sprintf(" Duration: %s\n", status.Duration))
} }
if status.Progress > 0 && status.Status == "running" { if status.Progress > 0 && status.Status == "running" {
content.WriteString(fmt.Sprintf(" Progress: %d%%\n", status.Progress)) content.WriteString(fmt.Sprintf(" Progress: %d%%\n", status.Progress))
} }
if status.BackupSize != "" { if status.BackupSize != "" {
content.WriteString(fmt.Sprintf(" Size: %s\n", status.BackupSize)) content.WriteString(fmt.Sprintf(" Size: %s\n", status.BackupSize))
} }
if status.FilesCount > 0 { if status.FilesCount > 0 {
content.WriteString(fmt.Sprintf(" Files: %d\n", status.FilesCount)) content.WriteString(fmt.Sprintf(" Files: %d\n", status.FilesCount))
} }
if status.Error != "" { if status.Error != "" {
content.WriteString(fmt.Sprintf(" Error: %s\n", status.Error[:min(100, len(status.Error))])) content.WriteString(fmt.Sprintf(" Error: %s\n", status.Error[:min(100, len(status.Error))]))
} }
content.WriteString("\n") content.WriteString("\n")
} }
// Add summary // Add summary
running := 0 running := 0
success := 0 success := 0
@@ -889,7 +889,7 @@ func (m *Model) showStatus() {
failed++ failed++
} }
} }
content.WriteString("📊 SUMMARY:\n") content.WriteString("📊 SUMMARY:\n")
content.WriteString(fmt.Sprintf(" Running: %d | Success: %d | Failed: %d\n", running, success, failed)) content.WriteString(fmt.Sprintf(" Running: %d | Success: %d | Failed: %d\n", running, success, failed))
} }
@@ -916,7 +916,7 @@ func (m *Model) showConfig() {
if len(item.args) > 0 { if len(item.args) > 0 {
content.WriteString(fmt.Sprintf(" Args: %s\n", strings.Join(item.args, " "))) content.WriteString(fmt.Sprintf(" Args: %s\n", strings.Join(item.args, " ")))
} }
// Check if script exists // Check if script exists
if _, err := os.Stat(item.script); os.IsNotExist(err) { if _, err := os.Stat(item.script); os.IsNotExist(err) {
content.WriteString(" ⚠️ Status: Script not found\n") content.WriteString(" ⚠️ Status: Script not found\n")
@@ -950,10 +950,10 @@ func (m *Model) stopBackup(service string, process *RunningProcess) tea.Cmd {
if process.Cancel != nil { if process.Cancel != nil {
process.Cancel() process.Cancel()
} }
// Give process time to shut down gracefully // Give process time to shut down gracefully
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
// Force kill if still running // Force kill if still running
if process.Cmd != nil && process.Cmd.Process != nil { if process.Cmd != nil && process.Cmd.Process != nil {
if process.Cmd.ProcessState == nil || !process.Cmd.ProcessState.Exited() { if process.Cmd.ProcessState == nil || !process.Cmd.ProcessState.Exited() {
@@ -977,11 +977,11 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
// Create context for cancellation // Create context for cancellation
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
// Execute the backup script with context // Execute the backup script with context
cmd := exec.CommandContext(ctx, item.script, item.args...) cmd := exec.CommandContext(ctx, item.script, item.args...)
cmd.Dir = m.shellPath cmd.Dir = m.shellPath
// Create pipes for stdout and stderr // Create pipes for stdout and stderr
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
@@ -990,7 +990,7 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
error: fmt.Sprintf("Failed to create stdout pipe: %s", err.Error()), error: fmt.Sprintf("Failed to create stdout pipe: %s", err.Error()),
} }
} }
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
return backupErrorMsg{ return backupErrorMsg{
@@ -998,7 +998,7 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
error: fmt.Sprintf("Failed to create stderr pipe: %s", err.Error()), error: fmt.Sprintf("Failed to create stderr pipe: %s", err.Error()),
} }
} }
// Start the command // Start the command
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return backupErrorMsg{ return backupErrorMsg{
@@ -1006,14 +1006,14 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
error: fmt.Sprintf("Failed to start backup: %s", err.Error()), error: fmt.Sprintf("Failed to start backup: %s", err.Error()),
} }
} }
var outputBuilder strings.Builder var outputBuilder strings.Builder
var allOutput []string var allOutput []string
// Monitor stdout and stderr in goroutines // Monitor stdout and stderr in goroutines
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
// Read stdout // Read stdout
go func() { go func() {
defer wg.Done() defer wg.Done()
@@ -1024,7 +1024,7 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
allOutput = append(allOutput, line) allOutput = append(allOutput, line)
} }
}() }()
// Read stderr // Read stderr
go func() { go func() {
defer wg.Done() defer wg.Done()
@@ -1035,21 +1035,21 @@ func (m Model) runBackupCommand(item BackupItem) tea.Cmd {
allOutput = append(allOutput, "[STDERR] "+line) allOutput = append(allOutput, "[STDERR] "+line)
} }
}() }()
// Wait for the command to complete // Wait for the command to complete
err = cmd.Wait() err = cmd.Wait()
// Wait for output readers to finish // Wait for output readers to finish
wg.Wait() wg.Wait()
finalOutput := outputBuilder.String() finalOutput := outputBuilder.String()
if err != nil { if err != nil {
// Check if it was cancelled // Check if it was cancelled
if ctx.Err() == context.Canceled { if ctx.Err() == context.Canceled {
return backupStopMsg{service: item.service} return backupStopMsg{service: item.service}
} }
return backupErrorMsg{ return backupErrorMsg{
service: item.service, service: item.service,
error: fmt.Sprintf("Backup failed: %s\n\n📋 Output:\n%s", err.Error(), finalOutput), error: fmt.Sprintf("Backup failed: %s\n\n📋 Output:\n%s", err.Error(), finalOutput),
@@ -1068,7 +1068,7 @@ func estimateProgress(output []string, service string) int {
if len(output) == 0 { if len(output) == 0 {
return 0 return 0
} }
switch service { switch service {
case "plex": case "plex":
// Plex backup stages: stop service (10%), backup files (60%), verify (20%), archive (10%) // Plex backup stages: stop service (10%), backup files (60%), verify (20%), archive (10%)
@@ -1080,7 +1080,7 @@ func estimateProgress(output []string, service string) int {
"archiving": 90, "archiving": 90,
"completed": 100, "completed": 100,
} }
for _, line := range output { for _, line := range output {
line = strings.ToLower(line) line = strings.ToLower(line)
for stage, progress := range stages { for stage, progress := range stages {
@@ -1089,7 +1089,7 @@ func estimateProgress(output []string, service string) int {
} }
} }
} }
case "immich": case "immich":
// Immich stages: database dump (50%), uploads backup (40%), upload to B2 (10%) // Immich stages: database dump (50%), uploads backup (40%), upload to B2 (10%)
stages := map[string]int{ stages := map[string]int{
@@ -1099,7 +1099,7 @@ func estimateProgress(output []string, service string) int {
"uploading to": 90, "uploading to": 90,
"completed": 100, "completed": 100,
} }
for _, line := range output { for _, line := range output {
line = strings.ToLower(line) line = strings.ToLower(line)
for stage, progress := range stages { for stage, progress := range stages {
@@ -1108,26 +1108,26 @@ func estimateProgress(output []string, service string) int {
} }
} }
} }
case "media": case "media":
// Media services backup: each service adds progress // Media services backup: each service adds progress
services := []string{"sonarr", "radarr", "prowlarr", "audiobookshelf", "tautulli", "sabnzbd", "jellyseerr"} services := []string{"sonarr", "radarr", "prowlarr", "audiobookshelf", "tautulli", "sabnzbd", "jellyseerr"}
completed := 0 completed := 0
for _, service := range services { for _, service := range services {
for _, line := range output { for _, line := range output {
if strings.Contains(strings.ToLower(line), service) && if strings.Contains(strings.ToLower(line), service) &&
(strings.Contains(strings.ToLower(line), "success") || (strings.Contains(strings.ToLower(line), "success") ||
strings.Contains(strings.ToLower(line), "completed")) { strings.Contains(strings.ToLower(line), "completed")) {
completed++ completed++
break break
} }
} }
} }
return (completed * 100) / len(services) return (completed * 100) / len(services)
} }
// Default progress estimation based on line count // Default progress estimation based on line count
if len(output) < 10 { if len(output) < 10 {
return 20 return 20
@@ -1136,7 +1136,7 @@ func estimateProgress(output []string, service string) int {
} else if len(output) < 100 { } else if len(output) < 100 {
return 80 return 80
} }
return 90 return 90
} }
@@ -1248,11 +1248,11 @@ func createProgressBar(progress int) string {
if progress > 100 { if progress > 100 {
progress = 100 progress = 100
} }
const barWidth = 30 const barWidth = 30
filled := (progress * barWidth) / 100 filled := (progress * barWidth) / 100
empty := barWidth - filled empty := barWidth - filled
bar := strings.Repeat("█", filled) + strings.Repeat("░", empty) bar := strings.Repeat("█", filled) + strings.Repeat("░", empty)
return fmt.Sprintf("[%s]", bar) return fmt.Sprintf("[%s]", bar)
} }
@@ -1263,7 +1263,7 @@ func (m Model) startProgressTicker(service string) tea.Cmd {
// Check if backup is still running // Check if backup is still running
if status, exists := m.backupStatus[service]; exists && status.Status == "running" { if status, exists := m.backupStatus[service]; exists && status.Status == "running" {
elapsed := time.Since(status.StartTime) elapsed := time.Since(status.StartTime)
// Estimate progress based on typical backup durations // Estimate progress based on typical backup durations
var estimatedDuration time.Duration var estimatedDuration time.Duration
switch service { switch service {
@@ -1276,13 +1276,13 @@ func (m Model) startProgressTicker(service string) tea.Cmd {
default: default:
estimatedDuration = 5 * time.Minute estimatedDuration = 5 * time.Minute
} }
// Calculate progress (cap at 95% until actual completion) // Calculate progress (cap at 95% until actual completion)
progress := int((elapsed.Seconds() / estimatedDuration.Seconds()) * 95) progress := int((elapsed.Seconds() / estimatedDuration.Seconds()) * 95)
if progress > 95 { if progress > 95 {
progress = 95 progress = 95
} }
return backupProgressMsg{ return backupProgressMsg{
service: service, service: service,
progress: progress, progress: progress,