Json database refactoring

main
Antonio De Lucreziis 2 years ago
parent f893f0e751
commit abf86ae549

@ -1,15 +1,7 @@
package database package database
import ( import (
"encoding/json"
"fmt"
"io" "io"
"log"
"os"
"git.phc.dm.unipi.it/phc/storage/store"
"git.phc.dm.unipi.it/phc/storage/utils"
"golang.org/x/exp/slices"
) )
type MonitorWidget struct { type MonitorWidget struct {
@ -53,391 +45,3 @@ type Database interface {
SetBucketObject(bucket, id string, r io.Reader) error SetBucketObject(bucket, id string, r io.Reader) error
DeleteBucketObject(bucket, id string) error DeleteBucketObject(bucket, id string) error
} }
type JsonBucketSettings struct {
Path string `json:"path"`
}
type jsonBucket struct {
Name string `json:"name"`
Objects []string `json:"objects"`
Settings *JsonBucketSettings `json:"settings"`
}
type jsonDB struct {
file string
PrefixSize int `json:"prefixSize"`
DashboardState DashboardState `json:"dashboardState"`
Buckets map[string]*jsonBucket `json:"buckets"`
APIKeys []string `json:"api-keys"`
}
func NewJSON(file string) Database {
db := &jsonDB{
file: file,
PrefixSize: 2,
DashboardState: DashboardState{
Widgets: []MonitorWidget{},
},
Buckets: map[string]*jsonBucket{},
APIKeys: []string{},
}
err := db.load()
if err != nil {
panic(err)
}
return db
}
// func writeTransaction[T any](db *jsonDB, transactionFunc func(db *jsonDB) (T, error)) (T, error) {
// var zero T
// if err := db.load(); err != nil {
// return zero, err
// }
// v, err := transactionFunc(db)
// if err := db.store(); err != nil {
// return zero, err
// }
// return v, err
// }
func (db *jsonDB) setup() error {
if _, err := os.Stat(db.file); !os.IsNotExist(err) {
return nil
}
log.Printf("missing %q, creating empty database file", db.file)
f, err := os.Create(db.file)
defer f.Close()
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(&db); err != nil {
return err
}
return nil
}
func (db *jsonDB) store() error {
f, err := os.Create(db.file)
defer f.Close()
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(&db); err != nil {
return err
}
return nil
}
func (db *jsonDB) load() error {
if err := db.setup(); err != nil {
return err
}
f, err := os.Open(db.file)
defer f.Close()
if err != nil {
return err
}
if err := json.NewDecoder(f).Decode(&db); err != nil {
return err
}
return nil
}
func (db *jsonDB) GetDashboardState() (DashboardState, error) {
if err := db.load(); err != nil {
return DashboardState{}, err
}
return db.DashboardState, nil
}
func (db *jsonDB) SetDashboardState(state DashboardState) error {
if err := db.load(); err != nil {
return err
}
db.DashboardState = state
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) CreateAPIKey() (string, error) {
if err := db.load(); err != nil {
return "", err
}
key := utils.GenerateRandomString(32)
db.APIKeys = append(db.APIKeys, key)
if err := db.store(); err != nil {
return "", err
}
return key, nil
}
func (db *jsonDB) AllAPIKeys() ([]string, error) {
if err := db.load(); err != nil {
return nil, err
}
return db.APIKeys, nil
}
func (db *jsonDB) CheckAPIKey(key string) error {
if err := db.load(); err != nil {
return err
}
if !slices.Contains(db.APIKeys, key) {
return fmt.Errorf("the given api key %q is invalid", key)
}
return nil
}
func (db *jsonDB) RemoveAPIKey(key string) error {
if err := db.load(); err != nil {
return err
}
i := slices.Index(db.APIKeys, key)
if i == -1 {
return fmt.Errorf("the given api key %q is invalid", key)
}
db.APIKeys = append(db.APIKeys[:i], db.APIKeys[i+1:]...)
if err := db.store(); err != nil {
return err
}
return nil
}
//
// Bucket Methods
//
func (db *jsonDB) CreateBucket(bucket string, settings any) error {
if err := db.load(); err != nil {
return err
}
if _, found := db.Buckets[bucket]; found {
return fmt.Errorf("bucket named %q already present", bucket)
}
bi := &jsonBucket{
Name: bucket,
Settings: &JsonBucketSettings{
Path: bucket + "/",
},
Objects: []string{},
}
if settings != nil {
bi.Settings = settings.(*JsonBucketSettings)
}
db.Buckets[bucket] = bi
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) GetBucketSettings(bucket string) (any, error) {
if err := db.load(); err != nil {
return nil, err
}
if _, found := db.Buckets[bucket]; !found {
return nil, fmt.Errorf("bucket named %q not found", bucket)
}
return db.Buckets[bucket].Settings, nil
}
func (db *jsonDB) SetBucketSettings(bucket string, settings any) error {
if err := db.load(); err != nil {
return err
}
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
b.Settings = settings.(*JsonBucketSettings)
log.Printf("[Warning] Bucket %q settings changed but bucket migration is not yet implemented (move the folder yourself)", bucket)
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) DeleteBucket(bucket string) error {
if err := db.load(); err != nil {
return err
}
if _, found := db.Buckets[bucket]; !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
delete(db.Buckets, bucket)
log.Printf("The bucket named %q was removed but its files are still on disk", bucket)
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) AllBuckets() ([]string, error) {
if err := db.load(); err != nil {
return nil, err
}
buckets := make([]string, 0, len(db.Buckets))
for _, b := range db.Buckets {
buckets = append(buckets, b.Name)
}
return buckets, nil
}
func (db *jsonDB) CreateBucketObject(bucket string, r io.Reader) (string, error) {
if err := db.load(); err != nil {
return "", err
}
b, found := db.Buckets[bucket]
if !found {
return "", fmt.Errorf("bucket named %q not found", bucket)
}
id, err := store.Create(b.Settings.Path, db.PrefixSize, r)
if err != nil {
return "", err
}
b.Objects = append(b.Objects, id)
if err := db.store(); err != nil {
return "", err
}
return id, err
}
func (db *jsonDB) SetBucketObject(bucket, id string, r io.Reader) error {
if err := db.load(); err != nil {
return err
}
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
if err := db.store(); err != nil {
return err
}
return store.Update(b.Settings.Path, db.PrefixSize, id, r)
}
func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error {
if err := db.load(); err != nil {
return err
}
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
return store.Read(b.Settings.Path, db.PrefixSize, id, w)
}
func (db *jsonDB) DeleteBucketObject(bucket, id string) error {
if err := db.load(); err != nil {
return err
}
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
if err := store.Delete(b.Settings.Path, db.PrefixSize, id); err != nil {
return err
}
i := slices.Index(b.Objects, id)
b.Objects = append(b.Objects[:i], b.Objects[i+1:]...)
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) AllBucketObjects(bucket string) ([]string, error) {
if err := db.load(); err != nil {
return nil, err
}
b, found := db.Buckets[bucket]
if !found {
return nil, fmt.Errorf("bucket named %q not found", bucket)
}
objects, err := store.All(b.Settings.Path)
if err != nil {
return nil, err
}
b.Objects = objects
if err := db.store(); err != nil {
return nil, err
}
return b.Objects, nil
}

@ -0,0 +1,379 @@
package database
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"sync"
"git.phc.dm.unipi.it/phc/storage/store"
"git.phc.dm.unipi.it/phc/storage/utils"
"golang.org/x/exp/slices"
)
type JsonBucketSettings struct {
Path string `json:"path"`
}
type jsonBucket struct {
Name string `json:"name"`
Objects []string `json:"objects"`
Settings *JsonBucketSettings `json:"settings"`
}
type jsonDB struct {
mu sync.Mutex
file string
PrefixSize int `json:"prefixSize"`
DashboardState DashboardState `json:"dashboardState"`
Buckets map[string]*jsonBucket `json:"buckets"`
APIKeys []string `json:"api-keys"`
}
func NewJSON(file string) Database {
db := &jsonDB{
file: file,
PrefixSize: 2,
DashboardState: DashboardState{
Widgets: []MonitorWidget{},
},
Buckets: map[string]*jsonBucket{},
APIKeys: []string{},
}
err := db.load()
if err != nil {
panic(err)
}
return db
}
func (db *jsonDB) setup() error {
if _, err := os.Stat(db.file); !os.IsNotExist(err) {
return nil
}
log.Printf("missing %q, creating empty database file", db.file)
f, err := os.Create(db.file)
defer f.Close()
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(&db); err != nil {
return err
}
return nil
}
func (db *jsonDB) store() error {
f, err := os.Create(db.file)
defer f.Close()
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(&db); err != nil {
return err
}
return nil
}
func (db *jsonDB) load() error {
if err := db.setup(); err != nil {
return err
}
f, err := os.Open(db.file)
defer f.Close()
if err != nil {
return err
}
if err := json.NewDecoder(f).Decode(&db); err != nil {
return err
}
return nil
}
func withValue[T any](
transactorFunc func(func() error) error,
workFunc func() (T, error)) (T, error) {
var value T
if err := transactorFunc(func() error {
var err error
value, err = workFunc()
return err
}); err != nil {
var zero T
return zero, err
}
return value, nil
}
func (db *jsonDB) writeTransaction(workFunc func() error) error {
db.mu.Lock()
defer db.mu.Unlock()
if err := db.load(); err != nil {
return err
}
if err := workFunc(); err != nil {
return err
}
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) readTransaction(transactionFunc func() error) error {
db.mu.Lock()
defer db.mu.Unlock()
if err := db.load(); err != nil {
return err
}
if err := transactionFunc(); err != nil {
return err
}
return nil
}
func (db *jsonDB) GetDashboardState() (DashboardState, error) {
return withValue(db.readTransaction, func() (DashboardState, error) {
return db.DashboardState, nil
})
}
func (db *jsonDB) SetDashboardState(state DashboardState) error {
if err := db.load(); err != nil {
return err
}
db.DashboardState = state
if err := db.store(); err != nil {
return err
}
return nil
}
func (db *jsonDB) CreateAPIKey() (string, error) {
return withValue(db.writeTransaction, func() (string, error) {
key := utils.GenerateRandomString(32)
db.APIKeys = append(db.APIKeys, key)
return key, nil
})
}
func (db *jsonDB) AllAPIKeys() ([]string, error) {
return withValue(db.readTransaction, func() ([]string, error) {
return db.APIKeys, nil
})
}
func (db *jsonDB) CheckAPIKey(key string) error {
return db.readTransaction(func() error {
if !slices.Contains(db.APIKeys, key) {
return fmt.Errorf("the given api key %q is invalid", key)
}
return nil
})
}
func (db *jsonDB) RemoveAPIKey(key string) error {
return db.writeTransaction(func() error {
i := slices.Index(db.APIKeys, key)
if i == -1 {
return fmt.Errorf("the given api key %q is invalid", key)
}
db.APIKeys = append(db.APIKeys[:i], db.APIKeys[i+1:]...)
return nil
})
}
//
// Bucket Methods
//
func (db *jsonDB) CreateBucket(bucket string, settings any) error {
return db.writeTransaction(func() error {
if _, found := db.Buckets[bucket]; found {
return fmt.Errorf("bucket named %q already present", bucket)
}
bi := &jsonBucket{
Name: bucket,
Settings: &JsonBucketSettings{
Path: bucket + "/",
},
Objects: []string{},
}
if settings != nil {
bi.Settings = settings.(*JsonBucketSettings)
}
db.Buckets[bucket] = bi
return nil
})
}
func (db *jsonDB) GetBucketSettings(bucket string) (any, error) {
return withValue(db.readTransaction, func() (any, error) {
if _, found := db.Buckets[bucket]; !found {
return nil, fmt.Errorf("bucket named %q not found", bucket)
}
return db.Buckets[bucket].Settings, nil
})
}
func (db *jsonDB) SetBucketSettings(bucket string, settings any) error {
return db.writeTransaction(func() error {
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
b.Settings = settings.(*JsonBucketSettings)
log.Printf("[Warning] Bucket %q settings changed but bucket migration is not yet implemented (move the folder yourself)", bucket)
return nil
})
}
func (db *jsonDB) DeleteBucket(bucket string) error {
return db.writeTransaction(func() error {
if _, found := db.Buckets[bucket]; !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
delete(db.Buckets, bucket)
log.Printf("The bucket named %q was removed but its files are still on disk", bucket)
return nil
})
}
func (db *jsonDB) AllBuckets() ([]string, error) {
return withValue(db.readTransaction, func() ([]string, error) {
buckets := make([]string, 0, len(db.Buckets))
for _, b := range db.Buckets {
buckets = append(buckets, b.Name)
}
return buckets, nil
})
}
func (db *jsonDB) CreateBucketObject(bucket string, r io.Reader) (string, error) {
return withValue(db.writeTransaction, func() (string, error) {
b, found := db.Buckets[bucket]
if !found {
return "", fmt.Errorf("bucket named %q not found", bucket)
}
id, err := store.Create(b.Settings.Path, db.PrefixSize, r)
if err != nil {
return "", err
}
b.Objects = append(b.Objects, id)
return id, err
})
}
func (db *jsonDB) SetBucketObject(bucket, id string, r io.Reader) error {
return db.writeTransaction(func() error {
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
return store.Update(b.Settings.Path, db.PrefixSize, id, r)
})
}
func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error {
return db.readTransaction(func() error {
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
return store.Read(b.Settings.Path, db.PrefixSize, id, w)
})
}
func (db *jsonDB) DeleteBucketObject(bucket, id string) error {
return db.writeTransaction(func() error {
b, found := db.Buckets[bucket]
if !found {
return fmt.Errorf("bucket named %q not found", bucket)
}
if err := store.Delete(b.Settings.Path, db.PrefixSize, id); err != nil {
return err
}
i := slices.Index(b.Objects, id)
b.Objects = append(b.Objects[:i], b.Objects[i+1:]...)
return nil
})
}
func (db *jsonDB) AllBucketObjects(bucket string) ([]string, error) {
return withValue(db.writeTransaction, func() ([]string, error) {
b, found := db.Buckets[bucket]
if !found {
return nil, fmt.Errorf("bucket named %q not found", bucket)
}
objects, err := store.All(b.Settings.Path)
if err != nil {
return nil, err
}
b.Objects = objects
return b.Objects, nil
})
}
Loading…
Cancel
Save