This commit adds and passes context around to (almost) every function. This allows cancelling (almost) every function call globally.
219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package meta
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
|
"github.com/tkw1536/goprogram/lib/collection"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Key represents a key for metadata.
|
|
type Key string
|
|
|
|
// ErrMetadatumNotSet is returned by various [MetaStorage] functions when a metadatum is not set
|
|
var ErrMetadatumNotSet = errors.New("metadatum not set")
|
|
|
|
// Storage manages metadata for either the entire distillery, or a single slug
|
|
type Storage struct {
|
|
Slug string
|
|
sql *sql.SQL
|
|
}
|
|
|
|
// Get retrieves metadata with the provided key and deserializes the first one into target.
|
|
// If no metadatum exists, returns [ErrMetadatumNotSet].
|
|
func (s Storage) Get(ctx context.Context, key Key, target any) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// read the datum from the database
|
|
var datum models.Metadatum
|
|
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Order("pk DESC").Find(&datum)
|
|
|
|
// check if there was an error
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if e actually found it!
|
|
if status.RowsAffected == 0 {
|
|
return ErrMetadatumNotSet
|
|
}
|
|
|
|
// and do the unmarshaling!
|
|
return json.Unmarshal(datum.Value, target)
|
|
}
|
|
|
|
// GetAll receives all metadata with the provided keys.
|
|
// For each received value, the targets function is called with the current index, and total number of results.
|
|
// The function is intended to return a target for deserialization.
|
|
//
|
|
// When no metadatum exists, targets is not called, and nil error is returned.
|
|
func (s Storage) GetAll(ctx context.Context, key Key, target func(index, total int) any) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// read the datum from the database
|
|
var data []models.Metadatum
|
|
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Find(&data)
|
|
|
|
// check if there was an error
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// unpack all of them into the destination
|
|
for index, datum := range data {
|
|
err := json.Unmarshal(datum.Value, target(index, len(data)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes all metadata with the provided key.
|
|
func (s Storage) Delete(ctx context.Context, key Key) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// delete all the values
|
|
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Set serializes value and stores it with the provided key.
|
|
// Any other metadata with the same key is deleted.
|
|
func (s Storage) Set(ctx context.Context, key Key, value any) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// marshal the value
|
|
bytes, err := json.Marshal(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return table.Transaction(func(tx *gorm.DB) error {
|
|
// delete the old values
|
|
status := tx.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// create the new item to insert
|
|
status = tx.Create(&models.Metadatum{
|
|
Key: string(key),
|
|
Slug: s.Slug,
|
|
Value: bytes,
|
|
})
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Set serializes values and stores them with the provided key.
|
|
// Any other metadata with the same key is deleted.
|
|
func (s Storage) SetAll(ctx context.Context, key Key, values ...any) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return table.Transaction(func(tx *gorm.DB) error {
|
|
// delete the old values
|
|
status := tx.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, value := range values {
|
|
bytes, err := json.Marshal(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// create the new item to insert
|
|
status := tx.Create(&models.Metadatum{
|
|
Key: string(key),
|
|
Slug: s.Slug,
|
|
Value: bytes,
|
|
})
|
|
if err := status.Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Purge removes all metadata, regardless of key.
|
|
func (s Storage) Purge(ctx context.Context) error {
|
|
table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status := table.Where("slug = ?", s.Slug).Delete(&models.Metadatum{})
|
|
if status.Error != nil {
|
|
return status.Error
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TypedKey represents a convenience wrapper for a given with a given value.
|
|
type TypedKey[Value any] Key
|
|
|
|
func (f TypedKey[Value]) Get(ctx context.Context, s *Storage) (value Value, err error) {
|
|
err = s.Get(ctx, Key(f), &value)
|
|
return
|
|
}
|
|
|
|
func (f TypedKey[Value]) GetOrSet(ctx context.Context, s *Storage, dflt Value) (value Value, err error) {
|
|
value, err = f.Get(ctx, s)
|
|
if err == ErrMetadatumNotSet {
|
|
value = dflt
|
|
err = f.Set(ctx, s, value)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (f TypedKey[Value]) GetAll(ctx context.Context, m *Storage) (values []Value, err error) {
|
|
err = m.GetAll(ctx, Key(f), func(index, total int) any {
|
|
if values == nil {
|
|
values = make([]Value, total)
|
|
}
|
|
return &values[index]
|
|
})
|
|
return values, err
|
|
}
|
|
|
|
func (f TypedKey[Value]) Set(ctx context.Context, m *Storage, value Value) error {
|
|
return m.Set(ctx, Key(f), value)
|
|
}
|
|
|
|
func (f TypedKey[Value]) SetAll(ctx context.Context, m *Storage, values ...Value) error {
|
|
return m.SetAll(ctx, Key(f), collection.AsAny(values)...)
|
|
}
|
|
|
|
func (f TypedKey[Value]) Delete(ctx context.Context, m *Storage) error {
|
|
return m.Delete(ctx, Key(f))
|
|
}
|