package storage import ( "encoding/binary" "encoding/json" "git.timovolkmann.de/gyrogpsc/core" "github.com/dgraph-io/badger/v2" "github.com/google/uuid" "github.com/sirupsen/logrus" "github.com/tidwall/pretty" "os" "path/filepath" "strconv" ) // Must implement Repo type badgerStore struct { trackingsDb *badger.DB recordsDb *badger.DB rawdataDb *badger.DB } func NewRepository(c *core.Configuration) *badgerStore { dir, _ := os.Getwd() logrus.Debug(dir) if _, err := os.Stat(filepath.Join(dir, "_db")); os.IsNotExist(err) { os.Mkdir(filepath.Join(dir, "_db"), os.ModePerm) } tr, err := badger.Open(badger.DefaultOptions("_db/trackings")) dp, err := badger.Open(badger.DefaultOptions("_db/records")) rd, err := badger.Open(badger.DefaultOptions("_db/raw")) if err != nil { logrus.Error(err) } return &badgerStore{trackingsDb: tr, recordsDb: dp, rawdataDb: rd} } func (r *badgerStore) isDbAvailable() bool { return r.trackingsDb.IsClosed() || r.recordsDb.IsClosed() || r.rawdataDb.IsClosed() } func (r *badgerStore) Save(tr core.Tracking) error { if ok := r.isDbAvailable(); ok { logrus.Error("unable to write to database. database closed!") return badger.ErrDBClosed } ts, err := tr.TimeCreated.MarshalText() if err != nil { logrus.Error(err, tr) } logrus.Info("save tracking:", tr.TimeCreated) meta, err := json.Marshal(tr.TrackingMetadata) if err != nil { logrus.Error(err, tr) return err } err = r.recordsDb.Update(func(txn *badger.Txn) error { for _, v := range tr.Records { k := createRecordKey(tr.UUID, v.RecordTimeKey.UnixNano()) j, err := json.Marshal(v.DataPair) logrus.Debugln("save record k/v:\n", tr.UUID.String(), strconv.FormatInt(v.RecordTimeKey.UnixNano(), 10)) logrus.Debugln(string(pretty.Pretty(j))) if err != nil { return err } txn.Set(k, j) } return nil }) if err != nil { logrus.Error(err, tr) return err } err = r.rawdataDb.Update(func(txn *badger.Txn) error { for _, v := range tr.Rawdata { k := createRecordKey(tr.UUID, v.RecordTimeKey.UnixNano()) j, err := json.Marshal(v) logrus.Debugln("save raw k/v:\n", tr.UUID.String(), strconv.FormatInt(v.RecordTimeKey.UnixNano(), 10)) logrus.Debugln(string(pretty.Pretty(j))) if err != nil { return err } txn.Set(k, j) } return nil }) if err != nil { logrus.Error(err, tr) return err } err = r.trackingsDb.Update(func(txn *badger.Txn) error { logrus.Debug("save tracking meta k/v:\n", string(ts), string(meta)) err := txn.Set(ts, meta) return err }) if err != nil { logrus.Error(err, tr) return err } r.trackingsDb.PrintHistogram(nil) dr := 0.5 err = r.trackingsDb.RunValueLogGC(dr) logrus.Debug("DB GC:", err) err = r.recordsDb.RunValueLogGC(dr) logrus.Debug("DB GC:", err) err = r.rawdataDb.RunValueLogGC(dr) logrus.Debug("DB GC:", err) r.trackingsDb.PrintHistogram(nil) logrus.Info("sucessfully saved tracking") return nil } //func (r *badgerStore) Save(tracking *core.Tracking) error { // ts, err := tracking.TimeCreated.MarshalText() // if err != nil { // logrus.Error(err, tracking) // } // logrus.Info("save tracking:", ts) // meta, err := json.Marshal(tracking.TrackingMetadata) // if err != nil { // logrus.Error(err, tracking) // return err // } // wg := sync.WaitGroup{} // wg.Add(3) // ch := make(chan error, 3) // go func() { // defer wg.Done() // err = r.recordsDb.Update(func(txn *badger.Txn) error { // for _, v := range tracking.Records { // k := createRecordKey(tracking.UUID, v.RecordTime.UnixNano()) // j, err := json.Marshal(v) // if err != nil { // return err // } // txn.Set(k, j) // } // return nil // }) // ch <- err // }() // go func() { // defer wg.Done() // err = r.recordsDb.Update(func(txn *badger.Txn) error { // for _, v := range tracking.Rawdata { // k := createRecordKey(tracking.UUID, v.Timestamp) // j, err := json.Marshal(v) // if err != nil { // return err // } // txn.Set(k, j) // } // return nil // }) // ch <- err // }() // go func() { // defer wg.Done() // err = r.trackingsDb.Update(func(txn *badger.Txn) error { // err := txn.Set(ts, meta) // return err // }) // ch <- err // }() // wg.Wait() // for { // select { // case err := <-ch: // if err != nil { // logrus.Error(err, tracking) // return err // } // default: // close(ch) // break // } // } // return nil //} func (r *badgerStore) LoadAll() ([]core.TrackingMetadata, error) { panic("implement me") } func (r *badgerStore) Load(id uuid.UUID) (core.Tracking, error) { panic("implement me") } func createRecordKey(uid uuid.UUID, timestamp int64) []byte { prefix, err := uid.MarshalText() if err != nil || timestamp < 0 { logrus.Error("unable to create key", err) } suffix := make([]byte, 8) binary.BigEndian.PutUint64(suffix, uint64(timestamp)) return append(prefix, suffix...) } func unmarshalDataKey(key []byte) (uuid.UUID, int64) { if len(key) != 24 { panic("corrupted key") } prefix := key[0:15] suffix := key[15:24] uid, err := uuid.FromBytes(prefix) if err != nil { panic("corrupted key") } timestamp := int64(binary.BigEndian.Uint64(suffix)) return uid, timestamp }