forked from electricdusk/rushlink
148 lines
3.5 KiB
Go
148 lines
3.5 KiB
Go
|
package db
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
|
||
|
bolt "go.etcd.io/bbolt"
|
||
|
|
||
|
gobmarsh "gitea.hashru.nl/dsprenkels/rushlink/pkg/gobmarsh"
|
||
|
)
|
||
|
|
||
|
// Database is the main rushlink database type.
|
||
|
//
|
||
|
// Open a database using DB.Open() and close it in the end using DB.Close().
|
||
|
// Only one instance of DB should exist in a program at any moment.
|
||
|
type Database struct {
|
||
|
Bolt *bolt.DB
|
||
|
}
|
||
|
|
||
|
// CurrentMigrateVersion holds the current "migrate version".
|
||
|
//
|
||
|
// If we alter the database format, we bump this number and write a new
|
||
|
// database migration in migrate().
|
||
|
const CurrentMigrateVersion = 2
|
||
|
|
||
|
// BucketConf holds the name for the "configuration" bucket.
|
||
|
//
|
||
|
// This bucket holds the database version, secret site-wide keys, etc.
|
||
|
const BucketConf = "conf"
|
||
|
|
||
|
// BucketPastes holds the name for the pastes bucket.
|
||
|
const BucketPastes = "pastes"
|
||
|
|
||
|
// BucketFileUpload holds the name for the file-upload bucket.
|
||
|
const BucketFileUpload = "fileUpload"
|
||
|
|
||
|
// KeyMigrateVersion stores the current migration version. If this value is less than
|
||
|
// CurrentMigrateVersion, the database has to be migrated.
|
||
|
const KeyMigrateVersion = "migrate_version"
|
||
|
|
||
|
// OpenDB opens a database file located at path.
|
||
|
func OpenDB(path string) (*Database, error) {
|
||
|
if path == "" {
|
||
|
return nil, errors.New("database not set")
|
||
|
}
|
||
|
|
||
|
db, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 1 * time.Second})
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrapf(err, "failed to open database at '%v'", path)
|
||
|
}
|
||
|
if err := db.Update(migrate); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &Database{db}, nil
|
||
|
}
|
||
|
|
||
|
// Close the bolt database
|
||
|
func (db *Database) Close() error {
|
||
|
if db == nil {
|
||
|
panic("no open database")
|
||
|
}
|
||
|
return db.Close()
|
||
|
}
|
||
|
|
||
|
// Initialize and migrate the database to the current version
|
||
|
func migrate(tx *bolt.Tx) error {
|
||
|
dbVersion, err := dbVersion(tx)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Migrate the database to version 1
|
||
|
if dbVersion < 1 {
|
||
|
log.Println("migrating database to version 1")
|
||
|
// Create conf bucket
|
||
|
_, err := tx.CreateBucket([]byte(BucketConf))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Create paste bucket
|
||
|
_, err = tx.CreateBucket([]byte(BucketPastes))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Update the version number
|
||
|
if err := setDBVersion(tx, 1); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if dbVersion < 2 {
|
||
|
log.Println("migrating database to version 2")
|
||
|
// Create fileUpload bucket
|
||
|
_, err := tx.CreateBucket([]byte(BucketFileUpload))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Update the version number
|
||
|
if err := setDBVersion(tx, 2); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Get the current migrate version from the database
|
||
|
func dbVersion(tx *bolt.Tx) (int, error) {
|
||
|
conf := tx.Bucket([]byte(BucketConf))
|
||
|
if conf == nil {
|
||
|
return 0, nil
|
||
|
}
|
||
|
dbVersionBytes := conf.Get([]byte(KeyMigrateVersion))
|
||
|
if dbVersionBytes == nil {
|
||
|
return 0, nil
|
||
|
}
|
||
|
|
||
|
// Version was already stored
|
||
|
var dbVersion int
|
||
|
if err := gobmarsh.Unmarshal(dbVersionBytes, &dbVersion); err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
if dbVersion == 0 {
|
||
|
return 0, fmt.Errorf("database version is invalid (%v)", dbVersion)
|
||
|
}
|
||
|
if dbVersion > CurrentMigrateVersion {
|
||
|
return 0, fmt.Errorf("database version is too recent (%v > %v)", dbVersion, CurrentMigrateVersion)
|
||
|
}
|
||
|
return dbVersion, nil
|
||
|
}
|
||
|
|
||
|
// Update the current migrate version in the database
|
||
|
func setDBVersion(tx *bolt.Tx, version int) error {
|
||
|
conf, err := tx.CreateBucketIfNotExists([]byte(BucketConf))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
versionBytes, err := gobmarsh.Marshal(version)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return conf.Put([]byte(KeyMigrateVersion), versionBytes)
|
||
|
}
|