package database import ( "encoding/json" "fmt" "io" "log" "os" "git.phc.dm.unipi.it/phc/storage/store" "golang.org/x/exp/slices" ) type MonitorWidget struct { Type string `json:"type"` Value any `json:"value"` } type DashboardState struct { Widgets []MonitorWidget `json:"widgets"` } type Database interface { // Dashboard DashboardState GetDashboardState() (DashboardState, error) SetDashboardState(DashboardState) error // Bucket Operations CreateBucket(bucket string, options ...any) error DeleteBucket(bucket string) error AllBuckets() ([]string, error) // Bucket Object Operations CreateBucketObject(bucket string, r io.Reader) (string, error) DeleteBucketObject(bucket, id string) error GetBucketObject(bucket, id string, w io.Writer) error SetBucketObject(bucket, id string, r io.Reader) error AllBucketObjects(bucket string) ([]string, error) } type jsonBucket struct { Name string `json:"name"` Path string `json:"path"` Objects []string `json:"objects"` } type jsonDB struct { file string PrefixSize int `json:"prefixSize"` DashboardState DashboardState `json:"dashboardState"` Buckets map[string]*jsonBucket `json:"buckets"` } func NewJSON(file string) Database { db := &jsonDB{ file: file, PrefixSize: 2, DashboardState: DashboardState{ Widgets: []MonitorWidget{}, }, Buckets: map[string]*jsonBucket{}, } 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 (db *jsonDB) GetDashboardState() (DashboardState, error) { db.load() return db.DashboardState, nil } func (db *jsonDB) SetDashboardState(state DashboardState) error { db.load() db.DashboardState = state db.store() return nil } func (db *jsonDB) CreateBucket(bucket string, options ...any) error { db.load() if _, found := db.Buckets[bucket]; found { return fmt.Errorf("bucket named %q already present", bucket) } bi := &jsonBucket{ Name: bucket, Path: bucket + "/", Objects: []string{}, } if len(options) > 0 { bi.Path = options[0].(string) } db.Buckets[bucket] = bi db.store() return nil } func (db *jsonDB) DeleteBucket(bucket string) error { db.load() 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) db.store() return nil } func (db *jsonDB) AllBuckets() ([]string, error) { db.load() 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) { db.load() b, found := db.Buckets[bucket] if !found { return "", fmt.Errorf("bucket named %q not found", bucket) } id, err := store.Create(b.Path, db.PrefixSize, r) if err != nil { return "", err } b.Objects = append(b.Objects, id) db.store() return id, err } func (db *jsonDB) SetBucketObject(bucket, id string, r io.Reader) error { db.load() b, found := db.Buckets[bucket] if !found { return fmt.Errorf("bucket named %q not found", bucket) } db.store() return store.Update(b.Path, db.PrefixSize, id, r) } func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error { db.load() b, found := db.Buckets[bucket] if !found { return fmt.Errorf("bucket named %q not found", bucket) } return store.Read(b.Path, db.PrefixSize, id, w) } func (db *jsonDB) DeleteBucketObject(bucket, id string) error { db.load() b, found := db.Buckets[bucket] if !found { return fmt.Errorf("bucket named %q not found", bucket) } if err := store.Delete(b.Path, db.PrefixSize, id); err != nil { return err } i := slices.Index(b.Objects, id) b.Objects = append(b.Objects[:i], b.Objects[i+1:]...) db.store() return nil } func (db *jsonDB) AllBucketObjects(bucket string) ([]string, error) { db.load() b, found := db.Buckets[bucket] if !found { return nil, fmt.Errorf("bucket named %q not found", bucket) } objects, err := store.All(b.Path) if err != nil { return nil, err } b.Objects = objects db.store() return b.Objects, nil }