2019-05-24 06:47:08 +03:00
|
|
|
package wtdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/coreos/bbolt"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// dbFilePermission requests read+write access to the db file.
|
|
|
|
dbFilePermission = 0600
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// metadataBkt stores all the meta information concerning the state of
|
|
|
|
// the database.
|
|
|
|
metadataBkt = []byte("metadata-bucket")
|
|
|
|
|
|
|
|
// dbVersionKey is a static key used to retrieve the database version
|
|
|
|
// number from the metadataBkt.
|
|
|
|
dbVersionKey = []byte("version")
|
|
|
|
|
|
|
|
// ErrUninitializedDB signals that top-level buckets for the database
|
|
|
|
// have not been initialized.
|
|
|
|
ErrUninitializedDB = errors.New("db not initialized")
|
|
|
|
|
|
|
|
// ErrNoDBVersion signals that the database contains no version info.
|
|
|
|
ErrNoDBVersion = errors.New("db has no version")
|
|
|
|
|
|
|
|
// byteOrder is the default endianness used when serializing integers.
|
|
|
|
byteOrder = binary.BigEndian
|
|
|
|
)
|
|
|
|
|
|
|
|
// fileExists returns true if the file exists, and false otherwise.
|
|
|
|
func fileExists(path string) bool {
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// createDBIfNotExist opens the boltdb database at dbPath/name, creating one if
|
|
|
|
// one doesn't exist. The boolean returned indicates if the database did not
|
|
|
|
// exist before, or if it has been created but no version metadata exists within
|
|
|
|
// it.
|
|
|
|
func createDBIfNotExist(dbPath, name string) (*bbolt.DB, bool, error) {
|
|
|
|
path := filepath.Join(dbPath, name)
|
|
|
|
|
|
|
|
// If the database file doesn't exist, this indicates we much initialize
|
|
|
|
// a fresh database with the latest version.
|
|
|
|
firstInit := !fileExists(path)
|
|
|
|
if firstInit {
|
|
|
|
// Ensure all parent directories are initialized.
|
|
|
|
err := os.MkdirAll(dbPath, 0700)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 01:41:25 +03:00
|
|
|
// Specify bbolt freelist options to reduce heap pressure in case the
|
|
|
|
// freelist grows to be very large.
|
|
|
|
options := &bbolt.Options{
|
|
|
|
NoFreelistSync: true,
|
|
|
|
FreelistType: bbolt.FreelistMapType,
|
|
|
|
}
|
|
|
|
|
|
|
|
bdb, err := bbolt.Open(path, dbFilePermission, options)
|
2019-05-24 06:47:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the file existed previously, we'll now check to see that the
|
|
|
|
// metadata bucket is properly initialized. It could be the case that
|
|
|
|
// the database was created, but we failed to actually populate any
|
|
|
|
// metadata. If the metadata bucket does not actually exist, we'll
|
|
|
|
// set firstInit to true so that we can treat is initialize the bucket.
|
|
|
|
if !firstInit {
|
|
|
|
var metadataExists bool
|
|
|
|
err = bdb.View(func(tx *bbolt.Tx) error {
|
|
|
|
metadataExists = tx.Bucket(metadataBkt) != nil
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !metadataExists {
|
|
|
|
firstInit = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bdb, firstInit, nil
|
|
|
|
}
|