Stage 10: admin console & dictionary ops (complaint review, hot-reload, broadcasts) (#11)
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 13s

This commit was merged in pull request #11.
This commit is contained in:
2026-06-04 07:27:49 +00:00
parent 4c4beace85
commit 3a640a17a4
49 changed files with 2548 additions and 200 deletions
+54
View File
@@ -2,6 +2,7 @@ package engine
import (
"fmt"
"os"
"path/filepath"
"sort"
"sync"
@@ -63,6 +64,36 @@ func Open(dir, version string, variants ...Variant) (*Registry, error) {
return r, nil
}
// OpenWithVersions builds a Registry by loading the boot version from the flat
// dir (every variant, as Open) and then every additional version held in an
// immediate subdirectory of dir: a subdirectory named V contributes, under
// version V, the variants whose committed DAWG it carries. This is the
// restart-side of the admin dictionary reload — a version reloaded into dir/<V>/
// at runtime is resident again after a restart. A subdirectory named like the
// boot version is skipped (the flat dir already is the boot version). A partially
// loaded registry is closed before any error is returned.
func OpenWithVersions(dir, bootVersion string) (*Registry, error) {
r, err := Open(dir, bootVersion)
if err != nil {
return nil, err
}
entries, err := os.ReadDir(dir)
if err != nil {
_ = r.Close()
return nil, fmt.Errorf("engine: scan dictionary dir %s: %w", dir, err)
}
for _, e := range entries {
if !e.IsDir() || e.Name() == bootVersion {
continue
}
if _, err := r.LoadAvailable(filepath.Join(dir, e.Name()), e.Name()); err != nil {
_ = r.Close()
return nil, err
}
}
return r, nil
}
// Load reads the committed DAWG of variant from dir, builds a solver over it and
// registers it under version. Reloading the same (variant, version) replaces the
// previous entry, closing its finder. The most recently loaded version of a
@@ -91,6 +122,29 @@ func (r *Registry) Load(v Variant, version, dir string) error {
return nil
}
// LoadAvailable loads, under version, every variant whose committed DAWG is
// present in dir, skipping a variant whose file is absent. It backs the admin
// dictionary reload (a version subdirectory may carry only the variants that were
// rebuilt) and OpenWithVersions' boot-time scan. It returns the variants it
// loaded, in catalogue order, or the first load error.
func (r *Registry) LoadAvailable(dir, version string) ([]Variant, error) {
var loaded []Variant
for _, v := range Variants() {
path := filepath.Join(dir, dictFiles[v])
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
continue
}
return loaded, fmt.Errorf("engine: stat %s dictionary %q in %s: %w", v, version, dir, err)
}
if err := r.Load(v, version, dir); err != nil {
return loaded, err
}
loaded = append(loaded, v)
}
return loaded, nil
}
// Solver returns the solver for the (variant, version) pair, or ErrUnknownVariant
// when the variant is absent and ErrUnknownVersion when only the version is.
func (r *Registry) Solver(v Variant, version string) (*scrabble.Solver, error) {