visedriver/storage/storage_service.go

252 lines
6.3 KiB
Go
Raw Normal View History

2024-09-19 14:57:11 +02:00
package storage
import (
"context"
"errors"
2024-09-19 14:57:11 +02:00
"fmt"
"os"
"path"
2025-01-21 16:28:55 +01:00
2024-09-19 14:57:11 +02:00
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
memdb "git.defalsify.org/vise.git/db/mem"
2024-10-19 22:06:58 +02:00
"git.defalsify.org/vise.git/db/postgres"
2025-01-02 10:39:49 +01:00
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/logging"
2024-09-19 14:57:11 +02:00
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
gdbmstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db/gdbm"
2025-01-21 16:28:55 +01:00
"github.com/jackc/pgx/v5/pgxpool"
2024-09-19 14:57:11 +02:00
)
2024-09-21 22:32:02 +02:00
var (
logg = logging.NewVanilla().WithDomain("storage")
)
2024-09-21 22:32:02 +02:00
2024-09-19 14:57:11 +02:00
type StorageService interface {
GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) (db.Db, error)
GetResource(ctx context.Context) (resource.Resource, error)
2024-09-19 14:57:11 +02:00
}
type MenuStorageService struct {
2025-01-21 16:28:55 +01:00
conns Conns
poResource resource.Resource
store map[int8]db.Db
}
2024-09-19 14:57:11 +02:00
func NewMenuStorageService(conn Conns) *MenuStorageService {
return &MenuStorageService{
conns: conn,
store: make(map[int8]db.Db),
}
}
func (ms *MenuStorageService) WithDb(store db.Db, typ int8) *MenuStorageService {
var err error
if ms.store[typ] != nil {
panic(fmt.Errorf("db already set for typ: %d", typ))
}
ms.store[typ] = store
ms.conns[typ], err = ToConnData(store.Connection())
if err != nil {
panic(err)
}
2025-01-04 23:27:46 +01:00
return ms
}
2025-01-21 16:28:55 +01:00
func (ms *MenuStorageService) checkDb(ctx context.Context, typ int8) db.Db {
store := ms.store[typ]
if store != nil {
return store
}
connData := ms.conns[typ]
2025-01-22 13:31:35 +01:00
logg.DebugCtxf(ctx, "db check", "conn", connData, "store", DbStoreDebug[typ])
v := ms.conns.Have(&connData)
if v == -1 {
return nil
}
src := ms.store[v]
if src == nil {
return nil
}
ms.store[typ] = ms.store[v]
2025-01-21 16:28:55 +01:00
logg.DebugCtxf(ctx, "found existing db", "typ", typ, "srctyp", v, "store", ms.store[typ], "srcstore", ms.store[v], "conn", connData)
return ms.store[typ]
}
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, section string, typ int8) (db.Db, error) {
2025-01-04 10:37:12 +01:00
var err error
2024-10-19 14:27:23 +02:00
newDb := ms.checkDb(ctx, typ)
if newDb != nil {
logg.InfoCtxf(ctx, "using existing db", "typ", typ, "db", newDb)
return newDb, nil
2024-10-19 14:27:23 +02:00
}
connData := ms.conns[typ]
2025-01-22 13:31:35 +01:00
connStr := connData.Raw()
dbTyp := connData.DbType()
2025-01-04 10:37:12 +01:00
if dbTyp == DBTYPE_POSTGRES {
2025-01-09 08:42:09 +01:00
// TODO: move to vise
err = ensureSchemaExists(ctx, connData)
2025-01-09 08:42:09 +01:00
if err != nil {
return nil, err
}
newDb = postgres.NewPgDb().WithSchema(connData.Domain())
2025-01-04 10:37:12 +01:00
} else if dbTyp == DBTYPE_GDBM {
err = ms.ensureDbDir(connStr)
2025-01-04 10:37:12 +01:00
if err != nil {
return nil, err
}
connStr = path.Join(connStr, section)
2025-01-04 08:35:28 +01:00
newDb = gdbmstorage.NewThreadGdbmDb()
2025-01-15 01:08:44 +01:00
} else if dbTyp == DBTYPE_FS {
err = ms.ensureDbDir(connStr)
2025-01-15 01:08:44 +01:00
if err != nil {
return nil, err
}
fsdbInstance := fsdb.NewFsDb()
if connData.Mode() == DBMODE_BINARY {
fsdbInstance = fsdbInstance.WithBinary()
}
newDb = fsdbInstance
2025-01-15 01:08:44 +01:00
} else if dbTyp == DBTYPE_MEM {
logg.WarnCtxf(ctx, "using volatile storage (memdb)")
newDb = memdb.NewMemDb()
2025-01-04 10:37:12 +01:00
} else {
2025-01-22 13:31:35 +01:00
return nil, fmt.Errorf("unsupported connection string: '%s'\n", connData.Raw())
2024-10-19 14:27:23 +02:00
}
logg.InfoCtxf(ctx, "connecting to db", "conn", connData, "typ", typ)
err = newDb.Connect(ctx, connStr)
2024-10-19 14:27:23 +02:00
if err != nil {
return nil, err
}
ms.store[typ] = newDb
2024-10-19 14:27:23 +02:00
return newDb, nil
}
2025-01-02 19:13:37 +01:00
// WithGettext triggers use of gettext for translation of templates and menus.
//
2025-01-21 16:28:55 +01:00
// The first language in `lns` will be used as default language, to resolve node keys to
2025-01-02 19:13:37 +01:00
// language strings.
//
// If `lns` is an empty array, gettext will not be used.
2025-01-02 10:39:49 +01:00
func (ms *MenuStorageService) WithGettext(path string, lns []lang.Language) *MenuStorageService {
2025-01-02 19:13:37 +01:00
if len(lns) == 0 {
logg.Warnf("Gettext requested but no languages supplied")
return ms
2025-01-02 10:39:49 +01:00
}
2025-01-02 19:13:37 +01:00
rs := resource.NewPoResource(lns[0], path)
2025-01-02 10:39:49 +01:00
2025-01-21 16:28:55 +01:00
for _, ln := range lns {
2025-01-02 10:39:49 +01:00
rs = rs.WithLanguage(ln)
}
ms.poResource = rs
return ms
}
// ensureSchemaExists creates a new schema if it does not exist
2025-01-09 08:42:09 +01:00
func ensureSchemaExists(ctx context.Context, conn ConnData) error {
h, err := pgxpool.New(ctx, conn.Path())
if err != nil {
return fmt.Errorf("failed to connect to the database: %w", err)
}
2025-01-09 08:42:09 +01:00
defer h.Close()
2025-01-09 08:42:09 +01:00
query := fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", conn.Domain())
_, err = h.Exec(ctx, query)
if err != nil {
return fmt.Errorf("failed to create schema: %w", err)
}
return nil
}
func applySession(ctx context.Context, store db.Db) error {
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return fmt.Errorf("missing session to apply to store: %v", store)
}
store.SetSession(sessionId)
return nil
}
func (ms *MenuStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
2024-10-19 14:27:23 +02:00
stateStore, err := ms.GetStateStore(ctx)
2024-09-21 22:32:02 +02:00
if err != nil {
return nil, err
}
err = applySession(ctx, stateStore)
if err != nil {
return nil, err
}
2024-10-19 14:27:23 +02:00
pr := persist.NewPersister(stateStore)
logg.TraceCtxf(ctx, "menu storage service", "persist", pr, "store", stateStore)
2024-09-19 14:57:11 +02:00
return pr, nil
}
2024-09-21 22:32:02 +02:00
func (ms *MenuStorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
userStore, err := ms.getOrCreateDb(ctx, "userdata.gdbm", STORETYPE_USER)
if err != nil {
return nil, err
}
err = applySession(ctx, userStore)
if err != nil {
return nil, err
}
return userStore, nil
2024-09-19 14:57:11 +02:00
}
func (ms *MenuStorageService) GetResource(ctx context.Context) (resource.Resource, error) {
store, err := ms.getOrCreateDb(ctx, "resource.gdbm", STORETYPE_RESOURCE)
2024-09-19 14:57:11 +02:00
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
2025-01-02 10:39:49 +01:00
if ms.poResource != nil {
2025-01-02 19:13:37 +01:00
logg.InfoCtxf(ctx, "using poresource for menu and template")
2025-01-02 10:39:49 +01:00
rfs.WithMenuGetter(ms.poResource.GetMenu)
rfs.WithTemplateGetter(ms.poResource.GetTemplate)
}
2024-09-19 14:57:11 +02:00
return rfs, nil
}
func (ms *MenuStorageService) GetStateStore(ctx context.Context) (db.Db, error) {
return ms.getOrCreateDb(ctx, "state.gdbm", STORETYPE_STATE)
2024-09-19 14:57:11 +02:00
}
func (ms *MenuStorageService) ensureDbDir(path string) error {
err := os.MkdirAll(path, 0700)
2024-09-19 14:57:11 +02:00
if err != nil {
return fmt.Errorf("store dir create exited with error: %v\n", err)
2024-09-19 14:57:11 +02:00
}
return nil
}
2024-09-21 22:32:02 +02:00
2025-01-19 10:35:09 +01:00
// TODO: how to handle persister here?
2025-01-19 10:04:37 +01:00
func (ms *MenuStorageService) Close(ctx context.Context) error {
var errs []error
var haveErr bool
2025-01-21 16:28:55 +01:00
for i := range _STORETYPE_MAX {
err := ms.store[int8(i)].Close(ctx)
if err != nil {
haveErr = true
}
2025-01-21 16:28:55 +01:00
errs = append(errs, err)
}
if haveErr {
errStr := ""
2025-01-21 16:28:55 +01:00
for i, err := range errs {
errStr += fmt.Sprintf("(%d: %v)", i, err)
}
return errors.New(errStr)
2024-09-21 22:32:02 +02:00
}
return nil
}