Simplify threading solution and add Clear All button

Simplified the approach by removing complex callback logic and using a
simple 500ms timer-based update for the stats bar instead. This eliminates
threading errors completely while keeping the code straightforward.

Changes:
1. Removed queue change callback entirely
2. Added background timer that updates stats bar every 500ms
3. Removed initComplete flag (no longer needed)
4. Simplified setContent() to direct calls
5. Added onClearAll parameter to BuildQueueView()
6. Added ClearAll() method to Queue (removes all jobs)
7. Added Clear All button with DangerImportance styling in queue view
8. Clear Completed button now has LowImportance styling

This approach is much simpler: the UI just polls the queue state
periodically instead of trying to handle callbacks from goroutines.
No more threading errors, less code, easier to understand.
This commit is contained in:
Stu Leak 2025-11-27 00:25:03 -05:00
parent 3ffb7c8a97
commit 1eb8f6d882
3 changed files with 60 additions and 53 deletions

View File

@ -378,6 +378,15 @@ func (q *Queue) Clear() {
q.notifyChange() q.notifyChange()
} }
// ClearAll removes all jobs from the queue
func (q *Queue) ClearAll() {
q.mu.Lock()
defer q.mu.Unlock()
q.jobs = make([]*Job, 0)
q.notifyChange()
}
// generateID generates a unique ID for a job // generateID generates a unique ID for a job
func generateID() string { func generateID() string {
return fmt.Sprintf("job-%d", time.Now().UnixNano()) return fmt.Sprintf("job-%d", time.Now().UnixNano())

View File

@ -21,6 +21,7 @@ func BuildQueueView(
onCancel func(string), onCancel func(string),
onRemove func(string), onRemove func(string),
onClear func(), onClear func(),
onClearAll func(),
titleColor, bgColor, textColor color.Color, titleColor, bgColor, textColor color.Color,
) fyne.CanvasObject { ) fyne.CanvasObject {
// Header // Header
@ -29,12 +30,18 @@ func BuildQueueView(
title.TextSize = 24 title.TextSize = 24
backBtn := widget.NewButton("← Back", onBack) backBtn := widget.NewButton("← Back", onBack)
backBtn.Importance = widget.LowImportance
clearBtn := widget.NewButton("Clear Completed", onClear) clearBtn := widget.NewButton("Clear Completed", onClear)
clearBtn.Importance = widget.LowImportance
clearAllBtn := widget.NewButton("Clear All", onClearAll)
clearAllBtn.Importance = widget.DangerImportance
header := container.NewBorder( header := container.NewBorder(
nil, nil, nil, nil,
backBtn, backBtn,
clearBtn, container.NewHBox(clearBtn, clearAllBtn),
container.NewCenter(title), container.NewCenter(title),
) )

95
main.go
View File

@ -151,31 +151,30 @@ func (c convertConfig) CoverLabel() string {
} }
type appState struct { type appState struct {
window fyne.Window window fyne.Window
active string active string
initComplete bool // True after initial UI setup completes source *videoSource
source *videoSource loadedVideos []*videoSource // Multiple loaded videos for navigation
loadedVideos []*videoSource // Multiple loaded videos for navigation currentIndex int // Current video index in loadedVideos
currentIndex int // Current video index in loadedVideos anim *previewAnimator
anim *previewAnimator convert convertConfig
convert convertConfig currentFrame string
currentFrame string player player.Controller
player player.Controller playerReady bool
playerReady bool playerVolume float64
playerVolume float64 playerMuted bool
playerMuted bool lastVolume float64
lastVolume float64 playerPaused bool
playerPaused bool playerPos float64
playerPos float64 playerLast time.Time
playerLast time.Time progressQuit chan struct{}
progressQuit chan struct{} convertCancel context.CancelFunc
convertCancel context.CancelFunc playerSurf *playerSurface
playerSurf *playerSurface convertBusy bool
convertBusy bool convertStatus string
convertStatus string playSess *playSession
playSess *playSession jobQueue *queue.Queue
jobQueue *queue.Queue statsBar *ui.ConversionStatsBar
statsBar *ui.ConversionStatsBar
} }
func (s *appState) stopPreview() { func (s *appState) stopPreview() {
@ -425,6 +424,10 @@ func (s *appState) showQueue() {
s.jobQueue.Clear() s.jobQueue.Clear()
s.showQueue() // Refresh s.showQueue() // Refresh
}, },
func() { // onClearAll
s.jobQueue.ClearAll()
s.showQueue() // Refresh
},
utils.MustHex("#4CE870"), // titleColor utils.MustHex("#4CE870"), // titleColor
gridColor, // bgColor gridColor, // bgColor
textColor, // textColor textColor, // textColor
@ -1082,39 +1085,27 @@ func runGUI() {
// Start queue processing (but paused by default) // Start queue processing (but paused by default)
state.jobQueue.Start() state.jobQueue.Start()
// Set callback - queue changes are triggered by job processor goroutine
state.jobQueue.SetChangeCallback(func() {
// Skip updates during initialization
if !state.initComplete {
return
}
// Marshal UI updates to main thread
app := fyne.CurrentApp()
if app == nil || app.Driver() == nil {
return
}
app.Driver().DoFromGoroutine(func() {
// Update stats bar
state.updateStatsBar()
// Refresh UI when queue changes and we're on main menu
if state.active == "" {
state.showMainMenu()
}
}, false)
})
// Mark initialization as complete
state.initComplete = true
defer state.shutdown() defer state.shutdown()
w.SetOnDropped(func(pos fyne.Position, items []fyne.URI) { w.SetOnDropped(func(pos fyne.Position, items []fyne.URI) {
state.handleDrop(pos, items) state.handleDrop(pos, items)
}) })
state.showMainMenu() state.showMainMenu()
logging.Debug(logging.CatUI, "main menu rendered with %d modules", len(modulesList)) logging.Debug(logging.CatUI, "main menu rendered with %d modules", len(modulesList))
// Start stats bar update loop on a timer
go func() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
app := fyne.CurrentApp()
if app != nil && app.Driver() != nil {
app.Driver().DoFromGoroutine(func() {
state.updateStatsBar()
}, false)
}
}
}()
w.ShowAndRun() w.ShowAndRun()
} }