commit 7de399c30f03f832466bd8d438beabba150d2076
parent b4386fa0eaddb33c431c384f58edeed9a636b747
Author: lash <dev@holbrook.no>
Date: Sun, 1 Sep 2024 05:27:52 +0100
WIP fix missing language key translation handling in db
Diffstat:
8 files changed, 393 insertions(+), 62 deletions(-)
diff --git a/db/db.go b/db/db.go
@@ -47,6 +47,11 @@ type Db interface {
SetSession(sessionId string)
}
+type LookupKey struct {
+ Default []byte
+ Translation []byte
+}
+
// ToDbKey generates a key to use Db to store a value for a particular context.
//
// If language is nil, then default language storage context will be used.
@@ -54,7 +59,7 @@ type Db interface {
// If language is not nil, and the context does not support language, the language value will silently will be ignored.
func ToDbKey(typ uint8, b []byte, l *lang.Language) []byte {
k := []byte{typ}
- if l != nil && l.Code != "" && typ & (DATATYPE_MENU | DATATYPE_TEMPLATE) > 0 {
+ if l != nil && l.Code != "" && typ & (DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD) > 0 {
b = append(b, []byte("_" + l.Code)...)
//s += "_" + l.Code
}
@@ -66,6 +71,7 @@ type baseDb struct {
pfx uint8
sid []byte
lock uint8
+ lang *lang.Language
}
// ensures default locking of read-only entries
@@ -96,20 +102,28 @@ func(db *baseDb) checkPut() bool {
return db.pfx & db.lock == 0
}
+func(db *baseDb) SetLanguage(ln *lang.Language) {
+ db.lang = ln
+}
+
// ToKey creates a DbKey within the current session context.
-func(db *baseDb) ToKey(ctx context.Context, key []byte) ([]byte, error) {
+func(db *baseDb) ToKey(ctx context.Context, key []byte) (LookupKey, error) {
+ var lk LookupKey
var b []byte
if db.pfx == DATATYPE_UNKNOWN {
- return nil, errors.New("datatype prefix cannot be UNKNOWN")
+ return lk, errors.New("datatype prefix cannot be UNKNOWN")
}
if (db.pfx > datatype_sessioned_threshold) {
b = append(db.sid, key...)
} else {
b = key
}
- ln, ok := ctx.Value("Language").(lang.Language)
- if ok {
- return ToDbKey(db.pfx, b, &ln), nil
- }
- return ToDbKey(db.pfx, b, nil), nil
+ lk.Default = ToDbKey(db.pfx, b, nil)
+ if db.pfx & (DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD) > 0 {
+ ln, ok := ctx.Value("Language").(lang.Language)
+ if ok {
+ lk.Translation = ToDbKey(db.pfx, b, &ln)
+ }
+ }
+ return lk, nil
}
diff --git a/db/fs.go b/db/fs.go
@@ -3,11 +3,17 @@ package db
import (
"context"
"errors"
+ "io/fs"
"io/ioutil"
"os"
"path"
)
+type fsLookupKey struct {
+ Default string
+ Translation string
+}
+
// pure filesystem backend implementation if the Db interface.
type fsDb struct {
baseDb
@@ -36,22 +42,35 @@ func(fdb *fsDb) Connect(ctx context.Context, connStr string) error {
// Get implements the Db interface.
func(fdb *fsDb) Get(ctx context.Context, key []byte) ([]byte, error) {
- fp, err := fdb.pathFor(ctx, key)
+ var f *os.File
+ lk, err := fdb.ToKey(ctx, key)
if err != nil {
return nil, err
}
- logg.TraceCtxf(ctx, "trying fs get", "key", key, "path", fp)
- f, err := os.Open(fp)
+ flk, err := fdb.pathFor(ctx, &lk)
if err != nil {
- fp, err = fdb.altPathFor(ctx, key)
- if err != nil {
- return nil, err
+ return nil, err
+ }
+ flka, err := fdb.altPathFor(ctx, &lk)
+ if err != nil {
+ return nil, err
+ }
+ for i, fp := range([]string{flk.Translation, flka.Translation, flk.Default, flka.Default}) {
+ if fp == "" {
+ logg.TraceCtxf(ctx, "fs get skip missing", "i", i)
+ continue
}
- logg.TraceCtxf(ctx, "trying fs get alt", "key", key, "path", fp)
+ logg.TraceCtxf(ctx, "trying fs get", "i", i, "key", key, "path", fp)
f, err = os.Open(fp)
- if err != nil {
- return nil, NewErrNotFound([]byte(fp))
+ if err == nil {
+ break
}
+ if !errors.Is(err, fs.ErrNotExist) {
+ return nil, err
+ }
+ }
+ if f == nil {
+ return nil, NewErrNotFound(key)
}
defer f.Close()
b, err := ioutil.ReadAll(f)
@@ -66,37 +85,56 @@ func(fdb *fsDb) Put(ctx context.Context, key []byte, val []byte) error {
if !fdb.checkPut() {
return errors.New("unsafe put and safety set")
}
- fp, err := fdb.pathFor(ctx, key)
+ lk, err := fdb.ToKey(ctx, key)
+ if err != nil {
+ return err
+ }
+ flk, err := fdb.pathFor(ctx, &lk)
if err != nil {
return err
}
- return ioutil.WriteFile(fp, val, 0600)
+ if flk.Translation != "" {
+ err = ioutil.WriteFile(flk.Translation, val, 0600)
+ if err != nil {
+ return err
+ }
+ }
+ return ioutil.WriteFile(flk.Default, val, 0600)
}
-// Close implements the Db interface..
+// Close implements the Db interface.
func(fdb *fsDb) Close() error {
return nil
-}
+}
// create a key safe for the filesystem.
-func(fdb *fsDb) pathFor(ctx context.Context, key []byte) (string, error) {
- kb, err := fdb.ToKey(ctx, key)
- if err != nil {
- return "", err
+func(fdb *fsDb) pathFor(ctx context.Context, lk *LookupKey) (fsLookupKey, error) {
+ var flk fsLookupKey
+ lk.Default[0] += 0x30
+ flk.Default = path.Join(fdb.dir, string(lk.Default))
+ if lk.Translation != nil {
+ lk.Translation[0] += 0x30
+ flk.Translation = path.Join(fdb.dir, string(lk.Translation))
}
- kb[0] += 0x30
- return path.Join(fdb.dir, string(kb)), nil
+ return flk, nil
}
// create a key safe for the filesystem, matching legacy resource.FsResource name.
-func(fdb *fsDb) altPathFor(ctx context.Context, key []byte) (string, error) {
- kb, err := fdb.ToKey(ctx, key)
- if err != nil {
- return "", err
- }
- fb := string(kb[1:])
+func(fdb *fsDb) altPathFor(ctx context.Context, lk *LookupKey) (fsLookupKey, error) {
+ var flk fsLookupKey
+ fb := string(lk.Default[1:])
if fdb.pfx == DATATYPE_BIN {
fb += ".bin"
- }
- return path.Join(fdb.dir, fb), nil
+ }
+ flk.Default = path.Join(fdb.dir, fb)
+
+ if lk.Translation != nil {
+ fb = string(lk.Translation[1:])
+ if fdb.pfx == DATATYPE_BIN {
+ fb += ".bin"
+ }
+ flk.Translation = path.Join(fdb.dir, fb)
+ }
+
+ return flk, nil
}
diff --git a/db/gdbm.go b/db/gdbm.go
@@ -50,23 +50,35 @@ func(gdb *gdbmDb) Put(ctx context.Context, key []byte, val []byte) error {
if !gdb.checkPut() {
return errors.New("unsafe put and safety set")
}
- k, err := gdb.ToKey(ctx, key)
+ lk, err := gdb.ToKey(ctx, key)
if err != nil {
return err
}
- return gdb.conn.Store(k, val, true)
+ if lk.Translation != nil {
+ return gdb.conn.Store(lk.Translation, val, true)
+ }
+ return gdb.conn.Store(lk.Default, val, true)
}
// Get implements Db
func(gdb *gdbmDb) Get(ctx context.Context, key []byte) ([]byte, error) {
- k, err := gdb.ToKey(ctx, key)
+ var v []byte
+ lk, err := gdb.ToKey(ctx, key)
if err != nil {
return nil, err
}
- v, err := gdb.conn.Fetch(k)
+ if lk.Translation != nil {
+ v, err = gdb.conn.Fetch(lk.Translation)
+ if err != nil {
+ if !errors.Is(gdbm.ErrItemNotFound, err) {
+ return nil, err
+ }
+ }
+ }
+ v, err = gdb.conn.Fetch(lk.Default)
if err != nil {
if errors.Is(gdbm.ErrItemNotFound, err) {
- return nil, NewErrNotFound(k)
+ return nil, NewErrNotFound(key)
}
return nil, err
}
diff --git a/db/mem.go b/db/mem.go
@@ -6,6 +6,11 @@ import (
"errors"
)
+type memLookupKey struct {
+ Default string
+ Translation string
+}
+
// memDb is a memory backend implementation of the Db interface.
type memDb struct {
baseDb
@@ -29,37 +34,56 @@ func(mdb *memDb) Connect(ctx context.Context, connStr string) error {
}
// convert to a supported map key type
-func(mdb *memDb) toHexKey(ctx context.Context, key []byte) (string, error) {
- k, err := mdb.ToKey(ctx, key)
- return hex.EncodeToString(k), err
+func(mdb *memDb) toHexKey(ctx context.Context, key []byte) (memLookupKey, error) {
+ var mk memLookupKey
+ lk, err := mdb.ToKey(ctx, key)
+ mk.Default = hex.EncodeToString(lk.Default)
+ if lk.Translation != nil {
+ mk.Translation = hex.EncodeToString(lk.Translation)
+ }
+ return mk, err
}
// Get implements Db
func(mdb *memDb) Get(ctx context.Context, key []byte) ([]byte, error) {
- k, err := mdb.toHexKey(ctx, key)
+ var v []byte
+ var ok bool
+ mk, err := mdb.toHexKey(ctx, key)
if err != nil {
return nil, err
}
- logg.TraceCtxf(ctx, "mem get", "k", k)
- v, ok := mdb.store[k]
+ logg.TraceCtxf(ctx, "mem get", "k", mk)
+ if mk.Translation != "" {
+ v, ok = mdb.store[mk.Translation]
+ if ok {
+ return v, nil
+ }
+ }
+ v, ok = mdb.store[mk.Default]
if !ok {
- b, _ := hex.DecodeString(k)
- return nil, NewErrNotFound(b)
+ //b, _ := hex.DecodeString(k)
+ return nil, NewErrNotFound(key)
}
return v, nil
}
// Put implements Db
func(mdb *memDb) Put(ctx context.Context, key []byte, val []byte) error {
+ var k string
if !mdb.checkPut() {
return errors.New("unsafe put and safety set")
}
- k, err := mdb.toHexKey(ctx, key)
+ mk, err := mdb.toHexKey(ctx, key)
if err != nil {
return err
}
+ if mk.Translation != "" {
+ k = mk.Translation
+ } else {
+ k = mk.Default
+ }
mdb.store[k] = val
- logg.TraceCtxf(ctx, "mem put", "k", k, "v", val)
+ logg.TraceCtxf(ctx, "mem put", "k", k, "v", val)
return nil
}
diff --git a/db/pg.go b/db/pg.go
@@ -70,27 +70,42 @@ func(pdb *pgDb) Put(ctx context.Context, key []byte, val []byte) error {
// Get implements Db.
func(pdb *pgDb) Get(ctx context.Context, key []byte) ([]byte, error) {
- k, err := pdb.ToKey(ctx, key)
+ lk, err := pdb.ToKey(ctx, key)
if err != nil {
return nil, err
}
+ if lk.Translation != nil {
+ tx, err := pdb.conn.Begin(ctx)
+ if err != nil {
+ return nil, err
+ }
+ query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema)
+ rs, err := tx.Query(ctx, query, lk.Translation)
+ if err != nil {
+ return nil, err
+ }
+ defer rs.Close()
+ if rs.Next() {
+ r := rs.RawValues()
+ return r[0], nil
+ }
+ }
+
tx, err := pdb.conn.Begin(ctx)
if err != nil {
return nil, err
}
query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema)
- rs, err := tx.Query(ctx, query, k)
+ rs, err := tx.Query(ctx, query, lk.Translation)
if err != nil {
return nil, err
}
defer rs.Close()
if !rs.Next() {
- return nil, NewErrNotFound(k)
-
+ return nil, NewErrNotFound(key)
}
r := rs.RawValues()
- b := r[0]
- return b, nil
+ return r[0], nil
}
// Close implements Db.
diff --git a/examples/gdbm/main.go b/examples/gdbm/main.go
@@ -48,10 +48,15 @@ func main() {
rs = rs.WithTemplateGetter(tg.GetTemplate)
rs = rs.WithCodeGetter(tg.GetCode)
- rsf := resource.NewFsResource(scriptDir)
+ fsStore := db.NewFsDb()
+ fsStore.Connect(ctx, scriptDir)
+ rsf, err := resource.NewDbResource(fsStore, db.DATATYPE_MENU)
+ if err != nil {
+ panic(err)
+ }
rsf.AddLocalFunc("do", do)
- rs = rs.WithMenuGetter(rsf.GetMenu)
- rs = rs.WithEntryFuncGetter(rsf.FuncFor)
+ rs.WithMenuGetter(rsf.GetMenu)
+ rs.WithEntryFuncGetter(rsf.FuncFor)
ca := cache.NewCache()
if err != nil {
diff --git a/resource/resource.go b/resource/resource.go
@@ -27,7 +27,7 @@ type MenuFunc func(ctx context.Context, menuSym string) (string, error)
// TemplateFunc is the function signature for retrieving a render template for a given symbol.
type TemplateFunc func(ctx context.Context, nodeSym string) (string, error)
// FuncForFunc is a function that returns an EntryFunc associated with a LOAD instruction symbol.
-type FuncForFunc func(loadSym string) (EntryFunc, error)
+type FuncForFunc func(ctx context.Context, loadSym string) (EntryFunc, error)
// Resource implementation are responsible for retrieving values and templates for symbols, and can render templates from value dictionaries.
//
@@ -88,7 +88,7 @@ func(m *MenuResource) WithMenuGetter(menuGetter MenuFunc) *MenuResource {
// FuncFor implements Resource interface.
func(m MenuResource) FuncFor(ctx context.Context, sym string) (EntryFunc, error) {
- return m.funcFunc(sym)
+ return m.funcFunc(ctx, sym)
}
// GetCode implements Resource interface.
@@ -115,7 +115,7 @@ func(m *MenuResource) AddLocalFunc(sym string, fn EntryFunc) {
}
// FallbackFunc returns the default handler function for a given external function symbol.
-func(m *MenuResource) FallbackFunc(sym string) (EntryFunc, error) {
+func(m *MenuResource) FallbackFunc(ctx context.Context, sym string) (EntryFunc, error) {
fn, ok := m.fns[sym]
if !ok {
return nil, fmt.Errorf("unknown function: %s", sym)
diff --git a/testdata/testdata_legacy.go b/testdata/testdata_legacy.go
@@ -0,0 +1,223 @@
+package testdata
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+
+ testdataloader "github.com/peteole/testdata-loader"
+
+ "git.defalsify.org/vise.git/vm"
+)
+
+type genFunc func() error
+
+var (
+ BaseDir = testdataloader.GetBasePath()
+ DataDir = ""
+ dirLock = false
+)
+
+func outLegacy(sym string, b []byte, tpl string, data map[string]string) error {
+ fp := path.Join(DataDir, sym)
+ err := ioutil.WriteFile(fp, []byte(tpl), 0644)
+ if err != nil {
+ return err
+ }
+
+ fb := sym + ".bin"
+ fp = path.Join(DataDir, fb)
+ err = ioutil.WriteFile(fp, b, 0644)
+ if err != nil {
+ return err
+ }
+
+ if data == nil {
+ return nil
+ }
+
+ for k, v := range data {
+ fb := k + ".txt"
+ fp = path.Join(DataDir, fb)
+ err = ioutil.WriteFile(fp, []byte(v), 0644)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func root() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MOUT, []string{"do the foo", "1"}, nil, nil)
+ b = vm.NewLine(b, vm.MOUT, []string{"go to the bar", "2"}, nil, nil)
+ b = vm.NewLine(b, vm.MOUT, []string{"language template", "3"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"foo", "1"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"bar", "2"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"lang", "3"}, nil, nil)
+
+ tpl := "hello world"
+
+ return out("root", b, tpl, nil)
+}
+
+func foo() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MOUT, []string{"to foo", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.MOUT, []string{"go bar", "1"}, nil, nil)
+ b = vm.NewLine(b, vm.MOUT, []string{"see long", "2"}, nil, nil)
+ b = vm.NewLine(b, vm.LOAD, []string{"inky"}, []byte{20}, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"_", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"baz", "1"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"long", "2"}, nil, nil)
+
+ data := make(map[string]string)
+ data["inky"] = "one"
+
+ tpl := `this is in foo
+
+it has more lines`
+
+ return out("foo", b, tpl, data)
+}
+
+func bar() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.LOAD, []string{"pinky"}, []byte{0}, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"^", "*"}, nil, nil)
+
+ tpl := "this is bar - any input will return to top"
+
+ data := make(map[string]string)
+ data["pinky"] = "two"
+
+ return out("bar", b, tpl, data)
+}
+
+func baz() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MAP, []string{"inky"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+
+ tpl := "this is baz which uses the var {{.inky}} in the template."
+
+ return out("baz", b, tpl, nil)
+}
+
+func long() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MOUT, []string{"back", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.MNEXT, []string{"nexxt", "00"}, nil, nil)
+ b = vm.NewLine(b, vm.MPREV, []string{"prevvv", "11"}, nil, nil)
+ b = vm.NewLine(b, vm.LOAD, []string{"longdata"}, []byte{0x00}, nil)
+ b = vm.NewLine(b, vm.MAP, []string{"longdata"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"_", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{">", "00"}, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"<", "11"}, nil, nil)
+
+ tpl := `data
+{{.longdata}}`
+
+ data := make(map[string]string)
+ data["longdata"] = `INKY 12
+PINKY 5555
+BLINKY 3t7
+CLYDE 11
+TINKYWINKY 22
+DIPSY 666
+LALA 111
+POO 222
+`
+
+ return out("long", b, tpl, data)
+}
+
+func defaultCatch() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MOUT, []string{"back", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"_", "*"}, nil, nil)
+
+ tpl := "invalid input"
+
+ return out("_catch", b, tpl, nil)
+}
+
+func lang() error {
+ b := []byte{}
+ b = vm.NewLine(b, vm.MOUT, []string{"back", "0"}, nil, nil)
+ b = vm.NewLine(b, vm.LOAD, []string{"inky"}, []byte{20}, nil)
+ b = vm.NewLine(b, vm.MAP, []string{"inky"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+ b = vm.NewLine(b, vm.INCMP, []string{"_", "*"}, nil, nil)
+
+ tpl := "this changes with language {{.inky}}"
+
+ err := out("lang", b, tpl, nil)
+ if err != nil {
+ return err
+ }
+
+ tpl = "dette endrer med språket {{.inky}}"
+ fp := path.Join(DataDir, "lang_nor")
+ err = os.WriteFile(fp, []byte(tpl), 0600)
+ if err != nil {
+ return err
+ }
+
+ menu := "tilbake"
+ fp = path.Join(DataDir, "back_menu_nor")
+ return os.WriteFile(fp, []byte(menu), 0600)
+}
+
+func generateLegacy() error {
+ out = outLegacy
+ err := os.MkdirAll(DataDir, 0755)
+ if err != nil {
+ return err
+ }
+
+ fns := []genFunc{root, foo, bar, baz, long, lang, defaultCatch}
+ for _, fn := range fns {
+ err = fn()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Generate outputs bytecode, templates and content symbols to a temporary directory.
+//
+// This directory can in turn be used as data source for the the resource.FsResource object.
+func GenerateLegacy() (string, error) {
+ dir, err := ioutil.TempDir("", "vise_testdata_")
+ if err != nil {
+ return "", err
+ }
+ DataDir = dir
+ dirLock = true
+ err = generateLegacy()
+ return dir, err
+}
+
+
+// Generate outputs bytecode, templates and content symbols to a specified directory.
+//
+// The directory must exist, and must not have been used already in the same code execution.
+//
+// This directory can in turn be used as data source for the the resource.FsResource object.
+func GenerateLegacyTo(dir string) error {
+ if dirLock {
+ return fmt.Errorf("directory already overridden")
+ }
+ DataDir = dir
+ dirLock = true
+ return generateLegacy()
+}