summaryrefslogtreecommitdiff
path: root/internal/gui/queue.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/gui/queue.go')
-rw-r--r--internal/gui/queue.go278
1 files changed, 278 insertions, 0 deletions
diff --git a/internal/gui/queue.go b/internal/gui/queue.go
new file mode 100644
index 0000000..7b1c5de
--- /dev/null
+++ b/internal/gui/queue.go
@@ -0,0 +1,278 @@
+package gui
+
+import (
+ "context"
+ "fmt"
+ "sync"
+ "time"
+)
+
+// WordJob represents a single word processing job
+type WordJob struct {
+ ID int
+ Word string
+ Translation string
+ AudioFile string
+ ImageFiles []string
+ Status JobStatus
+ Error error
+ StartedAt time.Time
+ CompletedAt time.Time
+}
+
+// JobStatus represents the current state of a job
+type JobStatus int
+
+const (
+ StatusQueued JobStatus = iota
+ StatusProcessing
+ StatusCompleted
+ StatusFailed
+)
+
+func (s JobStatus) String() string {
+ switch s {
+ case StatusQueued:
+ return "Queued"
+ case StatusProcessing:
+ return "Processing"
+ case StatusCompleted:
+ return "Completed"
+ case StatusFailed:
+ return "Failed"
+ default:
+ return "Unknown"
+ }
+}
+
+// WordQueue manages the queue of words to be processed
+type WordQueue struct {
+ jobs chan *WordJob
+ results map[int]*WordJob
+ processing map[int]*WordJob
+ completed []*WordJob
+
+ nextID int
+ mu sync.RWMutex
+
+ // Callbacks for UI updates
+ onStatusUpdate func(job *WordJob)
+ onJobComplete func(job *WordJob)
+
+ ctx context.Context
+ cancel context.CancelFunc
+ wg sync.WaitGroup
+}
+
+// NewWordQueue creates a new word processing queue
+func NewWordQueue(ctx context.Context) *WordQueue {
+ queueCtx, cancel := context.WithCancel(ctx)
+
+ q := &WordQueue{
+ jobs: make(chan *WordJob, 100),
+ results: make(map[int]*WordJob),
+ processing: make(map[int]*WordJob),
+ completed: make([]*WordJob, 0),
+ nextID: 1,
+ ctx: queueCtx,
+ cancel: cancel,
+ }
+
+ // Don't start a worker - the GUI will pull jobs
+
+ return q
+}
+
+// SetCallbacks sets the callback functions for UI updates
+func (q *WordQueue) SetCallbacks(onStatusUpdate func(*WordJob), onJobComplete func(*WordJob)) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.onStatusUpdate = onStatusUpdate
+ q.onJobComplete = onJobComplete
+}
+
+// AddWord adds a word to the processing queue
+func (q *WordQueue) AddWord(word string) *WordJob {
+ q.mu.Lock()
+ job := &WordJob{
+ ID: q.nextID,
+ Word: word,
+ Status: StatusQueued,
+ }
+ q.nextID++
+ q.results[job.ID] = job
+ q.mu.Unlock()
+
+ // Try to add to queue
+ select {
+ case q.jobs <- job:
+ q.updateJobStatus(job, StatusQueued)
+ return job
+ case <-q.ctx.Done():
+ job.Status = StatusFailed
+ job.Error = fmt.Errorf("queue is shutting down")
+ return job
+ }
+}
+
+// GetJob returns a job by ID
+func (q *WordQueue) GetJob(id int) *WordJob {
+ q.mu.RLock()
+ defer q.mu.RUnlock()
+ return q.results[id]
+}
+
+// GetQueueStatus returns the current queue statistics
+func (q *WordQueue) GetQueueStatus() (queued, processing, completed, failed int) {
+ q.mu.RLock()
+ defer q.mu.RUnlock()
+
+ // Count based on job statuses for accuracy
+ for _, job := range q.results {
+ switch job.Status {
+ case StatusQueued:
+ queued++
+ case StatusProcessing:
+ processing++
+ case StatusCompleted:
+ completed++
+ case StatusFailed:
+ failed++
+ }
+ }
+
+ return
+}
+
+// GetActiveJobs returns all jobs that are currently queued or processing
+func (q *WordQueue) GetActiveJobs() []*WordJob {
+ q.mu.RLock()
+ defer q.mu.RUnlock()
+
+ var jobs []*WordJob
+
+ // Add processing jobs
+ for _, job := range q.processing {
+ jobs = append(jobs, job)
+ }
+
+ // Add queued jobs from channel (non-blocking)
+ queuedJobs := make([]*WordJob, 0)
+ for {
+ select {
+ case job := <-q.jobs:
+ queuedJobs = append(queuedJobs, job)
+ default:
+ // Re-add jobs back to queue
+ for _, job := range queuedJobs {
+ q.jobs <- job
+ }
+ jobs = append(jobs, queuedJobs...)
+ return jobs
+ }
+ }
+}
+
+// GetCompletedJobs returns all completed jobs
+func (q *WordQueue) GetCompletedJobs() []*WordJob {
+ q.mu.RLock()
+ defer q.mu.RUnlock()
+ return append([]*WordJob{}, q.completed...)
+}
+
+// Stop gracefully shuts down the queue
+func (q *WordQueue) Stop() {
+ q.cancel()
+ close(q.jobs)
+}
+
+// CompleteJob marks a job as completed with results
+func (q *WordQueue) CompleteJob(jobID int, translation, audioFile string, imageFiles []string) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ if job, exists := q.results[jobID]; exists {
+ job.Status = StatusCompleted
+ job.Translation = translation
+ job.AudioFile = audioFile
+ job.ImageFiles = imageFiles
+ job.CompletedAt = time.Now()
+
+ delete(q.processing, jobID)
+ q.completed = append(q.completed, job)
+
+ if q.onJobComplete != nil {
+ q.onJobComplete(job)
+ }
+ }
+}
+
+// FailJob marks a job as failed with an error
+func (q *WordQueue) FailJob(jobID int, err error) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ if job, exists := q.results[jobID]; exists {
+ job.Status = StatusFailed
+ job.Error = err
+ job.CompletedAt = time.Now()
+
+ delete(q.processing, jobID)
+
+ if q.onJobComplete != nil {
+ q.onJobComplete(job)
+ }
+ }
+}
+
+// updateJobStatus updates the status of a job and calls the callback
+func (q *WordQueue) updateJobStatus(job *WordJob, status JobStatus) {
+ job.Status = status
+ if q.onStatusUpdate != nil {
+ q.onStatusUpdate(job)
+ }
+}
+
+// ProcessNextJob should be called by the GUI to process the next job in queue
+func (q *WordQueue) ProcessNextJob() *WordJob {
+ select {
+ case job := <-q.jobs:
+ q.mu.Lock()
+ q.processing[job.ID] = job
+ job.Status = StatusProcessing
+ job.StartedAt = time.Now()
+ q.mu.Unlock()
+
+ // Call the status update callback
+ if q.onStatusUpdate != nil {
+ q.onStatusUpdate(job)
+ }
+
+ return job
+
+ default:
+ return nil
+ }
+}
+
+// RemoveCompletedJobByWord removes a completed job for a specific word
+func (q *WordQueue) RemoveCompletedJobByWord(word string) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ // Remove from completed jobs list
+ newCompleted := make([]*WordJob, 0, len(q.completed))
+ for _, job := range q.completed {
+ if job.Word != word {
+ newCompleted = append(newCompleted, job)
+ }
+ }
+ q.completed = newCompleted
+
+ // Also remove from results map
+ for id, job := range q.results {
+ if job.Word == word && job.Status == StatusCompleted {
+ delete(q.results, id)
+ }
+ }
+} \ No newline at end of file