From ff65928ba0585ee21b8128f77133c0e5ff10d885 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Sun, 28 Dec 2025 20:14:21 -0500 Subject: [PATCH] Implement queue priority for Convert Now and auto-cleanup for failed conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Queue Priority Changes: - Added AddNext() method to queue package that inserts jobs after running jobs - "Convert Now" now adds to top of queue when conversions are already running - "Add to Queue" continues to add to end of queue - User feedback message indicates when job was added to top vs started fresh Auto-Cleanup for Failed Files: - Convert jobs now automatically delete incomplete/broken output files on failure - Prevents accumulation of partial files from failed conversions - Success tracking ensures complete files are never removed Benefits: - Better workflow when adding files during active conversions - "Convert Now" truly prioritizes the current file - No more broken partial files cluttering output directories - Cleaner error handling and disk space management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- internal/queue/queue.go | 33 ++++++++++++++++++++++++++++++- main.go | 43 ++++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/internal/queue/queue.go b/internal/queue/queue.go index f081979..dd852a3 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -95,7 +95,7 @@ func (q *Queue) notifyChange() { } } -// Add adds a job to the queue +// Add adds a job to the queue (at the end) func (q *Queue) Add(job *Job) { q.mu.Lock() @@ -115,6 +115,37 @@ func (q *Queue) Add(job *Job) { q.notifyChange() } +// AddNext adds a job to the front of the pending queue (right after any running job) +func (q *Queue) AddNext(job *Job) { + q.mu.Lock() + + if job.ID == "" { + job.ID = generateID() + } + if job.CreatedAt.IsZero() { + job.CreatedAt = time.Now() + } + if job.Status == "" { + job.Status = JobStatusPending + } + + // Find the position after any running jobs + insertPos := 0 + for i, j := range q.jobs { + if j.Status == JobStatusRunning { + insertPos = i + 1 + } else { + break + } + } + + // Insert at the calculated position + q.jobs = append(q.jobs[:insertPos], append([]*Job{job}, q.jobs[insertPos:]...)...) + q.rebalancePrioritiesLocked() + q.mu.Unlock() + q.notifyChange() +} + // Remove removes a job from the queue by ID func (q *Queue) Remove(id string) error { q.mu.Lock() diff --git a/main.go b/main.go index 0b6645a..25cb5de 100644 --- a/main.go +++ b/main.go @@ -2012,15 +2012,15 @@ func (s *appState) stopQueueAutoRefresh() { } // addConvertToQueue adds a conversion job to the queue -func (s *appState) addConvertToQueue() error { +func (s *appState) addConvertToQueue(addToTop bool) error { if s.source == nil { return fmt.Errorf("no video loaded") } - return s.addConvertToQueueForSource(s.source) + return s.addConvertToQueueForSource(s.source, addToTop) } -func (s *appState) addConvertToQueueForSource(src *videoSource) error { +func (s *appState) addConvertToQueueForSource(src *videoSource, addToTop bool) error { outputBase := s.resolveOutputBase(src, true) cfg := s.convert cfg.OutputBase = outputBase @@ -2106,8 +2106,14 @@ func (s *appState) addConvertToQueueForSource(src *videoSource) error { Config: config, } - s.jobQueue.Add(job) - logging.Debug(logging.CatSystem, "added convert job to queue: %s", job.ID) + // Add to top (after running job) if requested and queue is running + if addToTop && s.jobQueue.IsRunning() { + s.jobQueue.AddNext(job) + logging.Debug(logging.CatSystem, "added convert job to top of queue: %s", job.ID) + } else { + s.jobQueue.Add(job) + logging.Debug(logging.CatSystem, "added convert job to queue: %s", job.ID) + } return nil } @@ -3994,6 +4000,18 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre inputPath := cfg["inputPath"].(string) outputPath := cfg["outputPath"].(string) + // Track success to clean up broken files on failure + var success bool + defer func() { + if !success && outputPath != "" { + // Remove incomplete/broken output file on failure + if _, err := os.Stat(outputPath); err == nil { + logging.Debug(logging.CatFFMPEG, "removing incomplete output file: %s", outputPath) + os.Remove(outputPath) + } + } + }() + // If a direct conversion is running, wait until it finishes before starting queued jobs. for s.convertBusy { select { @@ -4706,6 +4724,8 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre }() } + // Mark as successful to prevent cleanup of output file + success = true return nil } @@ -6079,7 +6099,8 @@ func runLogsCLI() error { } func (s *appState) executeAddToQueue() { - if err := s.addConvertToQueue(); err != nil { + // Add to end of queue + if err := s.addConvertToQueue(false); err != nil { dialog.ShowError(err, s.window) } else { // Update queue button to show new count @@ -6109,8 +6130,8 @@ func (s *appState) executeAddAllToQueue() { } func (s *appState) executeConversion() { - // Add job to queue and start immediately - if err := s.addConvertToQueue(); err != nil { + // Add job to queue (at top if queue is already running) + if err := s.addConvertToQueue(true); err != nil { dialog.ShowError(err, s.window) return } @@ -6125,7 +6146,11 @@ func (s *appState) executeConversion() { s.clearVideo() // Show success message - dialog.ShowInformation("Convert", "Conversion started! View progress in Job Queue.", s.window) + if s.jobQueue != nil && s.jobQueue.IsRunning() { + dialog.ShowInformation("Convert", "Added to top of queue! View progress in Job Queue.", s.window) + } else { + dialog.ShowInformation("Convert", "Conversion started! View progress in Job Queue.", s.window) + } } func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {