//go:build PSIPHON_USE_BADGER_DB // +build PSIPHON_USE_BADGER_DB /* * Copyright (c) 2018, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package psiphon import ( "os" "path/filepath" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" "github.com/dgraph-io/badger" "github.com/dgraph-io/badger/options" ) const ( DATA_STORE_DIRECTORY = "psiphon.badgerdb" ) type datastoreDB struct { badgerDB *badger.DB } type datastoreTx struct { badgerTx *badger.Txn } type datastoreBucket struct { name []byte tx *datastoreTx } type datastoreCursor struct { badgerIterator *badger.Iterator prefix []byte } func datastoreOpenDB( rootDataDirectory string, _ bool) (*datastoreDB, error) { dbDirectory := filepath.Join(rootDataDirectory, "psiphon.badgerdb") err := os.MkdirAll(dbDirectory, 0700) if err != nil { return nil, errors.Trace(err) } opts := badger.DefaultOptions opts.Dir = dbDirectory opts.ValueDir = dbDirectory opts.TableLoadingMode = options.FileIO opts.ValueLogLoadingMode = options.FileIO opts.MaxTableSize = 1 << 16 opts.ValueLogFileSize = 1 << 20 opts.NumMemtables = 1 opts.NumLevelZeroTables = 1 opts.NumLevelZeroTablesStall = 2 opts.NumCompactors = 1 db, err := badger.Open(opts) if err != nil { return nil, errors.Trace(err) } for { if db.RunValueLogGC(0.5) != nil { break } } return &datastoreDB{badgerDB: db}, nil } func (db *datastoreDB) close() error { return db.badgerDB.Close() } func (db *datastoreDB) getDataStoreMetrics() string { // TODO: report metrics return "" } func (db *datastoreDB) view(fn func(tx *datastoreTx) error) error { return db.badgerDB.View( func(tx *badger.Txn) error { err := fn(&datastoreTx{badgerTx: tx}) if err != nil { return errors.Trace(err) } return nil }) } func (db *datastoreDB) update(fn func(tx *datastoreTx) error) error { return db.badgerDB.Update( func(tx *badger.Txn) error { err := fn(&datastoreTx{badgerTx: tx}) if err != nil { return errors.Trace(err) } return nil }) } func (tx *datastoreTx) bucket(name []byte) *datastoreBucket { return &datastoreBucket{ name: name, tx: tx, } } func (tx *datastoreTx) clearBucket(name []byte) error { b := tx.bucket(name) c := b.cursor() for key := c.firstKey(); key != nil; key = c.nextKey() { err := tx.badgerTx.Delete(key) if err != nil { return errors.Trace(err) } } return nil } func (b *datastoreBucket) get(key []byte) []byte { keyWithPrefix := append(b.name, key...) item, err := b.tx.badgerTx.Get(keyWithPrefix) if err != nil { if err != badger.ErrKeyNotFound { // The original datastore interface does not return an error from // Get, so emit notice. NoticeWarning("get failed: %s: %s", string(keyWithPrefix), errors.Trace(err)) } return nil } value, err := item.Value() if err != nil { NoticeWarning("get failed: %s: %s", string(keyWithPrefix), errors.Trace(err)) return nil } return value } func (b *datastoreBucket) put(key, value []byte) error { keyWithPrefix := append(b.name, key...) err := b.tx.badgerTx.Set(keyWithPrefix, value) if err != nil { return errors.Trace(err) } return nil } func (b *datastoreBucket) delete(key []byte) error { keyWithPrefix := append(b.name, key...) err := b.tx.badgerTx.Delete(keyWithPrefix) if err != nil { return errors.Trace(err) } return nil } func (b *datastoreBucket) cursor() *datastoreCursor { opts := badger.DefaultIteratorOptions opts.PrefetchValues = false iterator := b.tx.badgerTx.NewIterator(opts) return &datastoreCursor{badgerIterator: iterator, prefix: b.name} } func (c *datastoreCursor) firstKey() []byte { c.badgerIterator.Seek(c.prefix) return c.currentKey() } func (c *datastoreCursor) currentKey() []byte { if !c.badgerIterator.ValidForPrefix(c.prefix) { return nil } item := c.badgerIterator.Item() return item.Key()[len(c.prefix):] } func (c *datastoreCursor) nextKey() []byte { c.badgerIterator.Next() return c.currentKey() } func (c *datastoreCursor) first() ([]byte, []byte) { c.badgerIterator.Seek(c.prefix) return c.current() } func (c *datastoreCursor) current() ([]byte, []byte) { if !c.badgerIterator.ValidForPrefix(c.prefix) { return nil, nil } item := c.badgerIterator.Item() value, err := item.Value() if err != nil { return nil, nil } return item.Key()[len(c.prefix):], value } func (c *datastoreCursor) next() ([]byte, []byte) { c.badgerIterator.Next() return c.current() } func (c *datastoreCursor) close() { c.badgerIterator.Close() }