Some checks failed
CI / Backend (push) Failing after 11s
Release / Check ui (push) Failing after 51s
Release / Docker / ui (push) Has been skipped
CI / UI (push) Failing after 55s
Release / Test backend (push) Failing after 1m9s
Release / Docker / backend (push) Has been skipped
Release / Docker / runner (push) Has been skipped
Release / Docker / caddy (push) Failing after 28s
Release / Gitea Release (push) Has been skipped
CI / UI (pull_request) Failing after 42s
CI / Backend (pull_request) Successful in 3m45s
- LibreTranslate client (chunks on blank lines, ≤4500 chars, 3-goroutine semaphore)
- Runner translation task loop (OTel, heartbeat, MinIO storage)
- PocketBase translation_jobs collection support (create/claim/finish/list)
- Per-chapter language switcher on chapter reader (EN/RU/ID/PT/FR, polls until done)
- Admin /admin/translation page: bulk enqueue form + live-polling jobs table
- New backend routes: POST /api/translation/{slug}/{n}, GET /api/translation/status,
GET /api/translation/{slug}/{n}, GET /api/admin/translation/jobs,
POST /api/admin/translation/bulk
- ListTranslationTasks added to taskqueue.Reader interface + store impl
- All builds and tests pass; svelte-check: 0 errors
104 lines
4.9 KiB
Go
104 lines
4.9 KiB
Go
// Package taskqueue defines the interfaces for creating and consuming
|
|
// scrape/audio tasks stored in PocketBase.
|
|
//
|
|
// Interface segregation:
|
|
// - Producer is used only by the backend (creates tasks, cancels tasks).
|
|
// - Consumer is used only by the runner (claims tasks, reports results).
|
|
// - Reader is used by the backend for status/history endpoints.
|
|
//
|
|
// Concrete implementations live in internal/storage.
|
|
package taskqueue
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/libnovel/backend/internal/domain"
|
|
)
|
|
|
|
// Producer is the write side of the task queue used by the backend service.
|
|
// It creates new tasks in PocketBase for the runner to pick up.
|
|
type Producer interface {
|
|
// CreateScrapeTask inserts a new scrape task with status=pending and
|
|
// returns the assigned PocketBase record ID.
|
|
// kind is one of "catalogue", "book", or "book_range".
|
|
// targetURL is the book URL (empty for catalogue-wide tasks).
|
|
CreateScrapeTask(ctx context.Context, kind, targetURL string, fromChapter, toChapter int) (string, error)
|
|
|
|
// CreateAudioTask inserts a new audio task with status=pending and
|
|
// returns the assigned PocketBase record ID.
|
|
CreateAudioTask(ctx context.Context, slug string, chapter int, voice string) (string, error)
|
|
|
|
// CreateTranslationTask inserts a new translation task with status=pending and
|
|
// returns the assigned PocketBase record ID.
|
|
CreateTranslationTask(ctx context.Context, slug string, chapter int, lang string) (string, error)
|
|
|
|
// CancelTask transitions a pending task to status=cancelled.
|
|
// Returns ErrNotFound if the task does not exist.
|
|
CancelTask(ctx context.Context, id string) error
|
|
}
|
|
|
|
// Consumer is the read/claim side of the task queue used by the runner.
|
|
type Consumer interface {
|
|
// ClaimNextScrapeTask atomically finds the oldest pending scrape task,
|
|
// sets its status=running and worker_id=workerID, and returns it.
|
|
// Returns (zero, false, nil) when the queue is empty.
|
|
ClaimNextScrapeTask(ctx context.Context, workerID string) (domain.ScrapeTask, bool, error)
|
|
|
|
// ClaimNextAudioTask atomically finds the oldest pending audio task,
|
|
// sets its status=running and worker_id=workerID, and returns it.
|
|
// Returns (zero, false, nil) when the queue is empty.
|
|
ClaimNextAudioTask(ctx context.Context, workerID string) (domain.AudioTask, bool, error)
|
|
|
|
// ClaimNextTranslationTask atomically finds the oldest pending translation task,
|
|
// sets its status=running and worker_id=workerID, and returns it.
|
|
// Returns (zero, false, nil) when the queue is empty.
|
|
ClaimNextTranslationTask(ctx context.Context, workerID string) (domain.TranslationTask, bool, error)
|
|
|
|
// FinishScrapeTask marks a running scrape task as done and records the result.
|
|
FinishScrapeTask(ctx context.Context, id string, result domain.ScrapeResult) error
|
|
|
|
// FinishAudioTask marks a running audio task as done and records the result.
|
|
FinishAudioTask(ctx context.Context, id string, result domain.AudioResult) error
|
|
|
|
// FinishTranslationTask marks a running translation task as done and records the result.
|
|
FinishTranslationTask(ctx context.Context, id string, result domain.TranslationResult) error
|
|
|
|
// FailTask marks a task (scrape, audio, or translation) as failed with an error message.
|
|
FailTask(ctx context.Context, id, errMsg string) error
|
|
|
|
// HeartbeatTask updates the heartbeat_at timestamp on a running task.
|
|
// Should be called periodically by the runner while the task is active so
|
|
// the reaper knows the task is still alive.
|
|
HeartbeatTask(ctx context.Context, id string) error
|
|
|
|
// ReapStaleTasks finds all running tasks whose heartbeat_at is older than
|
|
// staleAfter (or was never set) and resets them to pending so they can be
|
|
// re-claimed by a healthy runner. Returns the number of tasks reaped.
|
|
ReapStaleTasks(ctx context.Context, staleAfter time.Duration) (int, error)
|
|
}
|
|
|
|
// Reader is the read-only side used by the backend for status pages.
|
|
type Reader interface {
|
|
// ListScrapeTasks returns all scrape tasks sorted by started descending.
|
|
ListScrapeTasks(ctx context.Context) ([]domain.ScrapeTask, error)
|
|
|
|
// GetScrapeTask returns a single scrape task by ID.
|
|
// Returns (zero, false, nil) if not found.
|
|
GetScrapeTask(ctx context.Context, id string) (domain.ScrapeTask, bool, error)
|
|
|
|
// ListAudioTasks returns all audio tasks sorted by started descending.
|
|
ListAudioTasks(ctx context.Context) ([]domain.AudioTask, error)
|
|
|
|
// GetAudioTask returns the most recent audio task for cacheKey.
|
|
// Returns (zero, false, nil) if not found.
|
|
GetAudioTask(ctx context.Context, cacheKey string) (domain.AudioTask, bool, error)
|
|
|
|
// ListTranslationTasks returns all translation tasks sorted by started descending.
|
|
ListTranslationTasks(ctx context.Context) ([]domain.TranslationTask, error)
|
|
|
|
// GetTranslationTask returns the most recent translation task for cacheKey.
|
|
// Returns (zero, false, nil) if not found.
|
|
GetTranslationTask(ctx context.Context, cacheKey string) (domain.TranslationTask, bool, error)
|
|
}
|