Implement queue priority for Convert Now and auto-cleanup for failed conversions

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 <noreply@anthropic.com>
This commit is contained in:
Stu Leak 2025-12-28 20:14:21 -05:00
parent b887142401
commit ff65928ba0
2 changed files with 66 additions and 10 deletions

View File

@ -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()

43
main.go
View File

@ -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 {