steps 6-8: wire HybridStore into orchestrator, server, and main

- Add storage/hybrid.go: HybridStore composing PocketBase + MinIO backends
- Rewrite orchestrator to accept storage.Store instead of *writer.Writer
- Replace *writer.Writer with storage.Store in server.go and ui.go
- Wire audio cache, reading progress, chapter reads/writes through store
- Add rankingCacheAdapter in main.go to bridge context-free RankingPageCacher
  interface to HybridStore's context-aware methods
This commit is contained in:
Admin
2026-03-02 14:44:02 +05:00
parent 9add9033b9
commit 18e76c9668
5 changed files with 443 additions and 87 deletions

View File

@@ -19,7 +19,7 @@ import (
"sync"
"github.com/libnovel/scraper/internal/scraper"
"github.com/libnovel/scraper/internal/writer"
"github.com/libnovel/scraper/internal/storage"
)
// Config holds tunable parameters for the orchestrator.
@@ -28,7 +28,8 @@ type Config struct {
// Defaults to runtime.NumCPU() when 0.
Workers int
// StaticRoot is the path to the static/books output directory.
// StaticRoot is kept for backwards-compatibility but is no longer used
// when a Store is provided.
StaticRoot string
// SingleBookURL when non-empty causes the orchestrator to scrape only
@@ -40,13 +41,13 @@ type Config struct {
type Orchestrator struct {
cfg Config
novel scraper.NovelScraper
writer *writer.Writer
store storage.Store
log *slog.Logger
workers int
}
// New returns a new Orchestrator.
func New(cfg Config, novel scraper.NovelScraper, log *slog.Logger) *Orchestrator {
// New returns a new Orchestrator backed by the provided Store.
func New(cfg Config, novel scraper.NovelScraper, log *slog.Logger, store storage.Store) *Orchestrator {
workers := cfg.Workers
if workers <= 0 {
workers = runtime.NumCPU()
@@ -54,7 +55,7 @@ func New(cfg Config, novel scraper.NovelScraper, log *slog.Logger) *Orchestrator
return &Orchestrator{
cfg: cfg,
novel: novel,
writer: writer.New(cfg.StaticRoot),
store: store,
log: log,
workers: workers,
}
@@ -66,7 +67,6 @@ func (o *Orchestrator) Run(ctx context.Context) error {
o.log.Info("orchestrator starting",
"source", o.novel.SourceName(),
"workers", o.workers,
"static_root", o.cfg.StaticRoot,
)
// chapterWork is the shared queue consumed by chapter worker goroutines.
@@ -89,8 +89,8 @@ func (o *Orchestrator) Run(ctx context.Context) error {
default:
}
// Skip if already on disk.
if o.writer.ChapterExists(job.slug, job.ref) {
// Skip if already stored.
if o.store.ChapterExists(ctx, job.slug, job.ref) {
o.log.Debug("chapter already exists, skipping",
"book", job.slug, "chapter", job.ref.Number)
continue
@@ -107,7 +107,7 @@ func (o *Orchestrator) Run(ctx context.Context) error {
continue
}
if err := o.writer.WriteChapter(job.slug, chapter); err != nil {
if err := o.store.WriteChapter(ctx, job.slug, chapter); err != nil {
o.log.Error("chapter write failed",
"book", job.slug,
"chapter", job.ref.Number,
@@ -135,8 +135,8 @@ func (o *Orchestrator) Run(ctx context.Context) error {
return
}
// Persist / update metadata.yaml.
if err := o.writer.WriteMetadata(meta); err != nil {
// Persist / update metadata.
if err := o.store.WriteMetadata(ctx, meta); err != nil {
o.log.Error("metadata write failed", "slug", meta.Slug, "err", err)
// Continue — chapters can still be scraped.
}