ipld-eth-server/vendor/github.com/ipfs/go-ipfs/repo/fsrepo/datastores.go

249 lines
6.3 KiB
Go

package fsrepo
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/ipfs/go-ipfs/repo"
ds "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/mount"
dssync "github.com/ipfs/go-datastore/sync"
"github.com/ipfs/go-ds-measure"
)
// ConfigFromMap creates a new datastore config from a map
type ConfigFromMap func(map[string]interface{}) (DatastoreConfig, error)
// DatastoreConfig is an abstraction of a datastore config. A "spec"
// is first converted to a DatastoreConfig and then Create() is called
// to instantiate a new datastore
type DatastoreConfig interface {
// DiskSpec returns a minimal configuration of the datastore
// represting what is stored on disk. Run time values are
// excluded.
DiskSpec() DiskSpec
// Create instantiate a new datastore from this config
Create(path string) (repo.Datastore, error)
}
// DiskSpec is a minimal representation of the characteristic values of the
// datastore. If two diskspecs are the same, the loader assumes that they refer
// to exactly the same datastore. If they differ at all, it is assumed they are
// completely different datastores and a migration will be performed. Runtime
// values such as cache options or concurrency options should not be added
// here.
type DiskSpec map[string]interface{}
// Bytes returns a minimal JSON encoding of the DiskSpec
func (spec DiskSpec) Bytes() []byte {
b, err := json.Marshal(spec)
if err != nil {
// should not happen
panic(err)
}
return bytes.TrimSpace(b)
}
// String returns a minimal JSON encoding of the DiskSpec
func (spec DiskSpec) String() string {
return string(spec.Bytes())
}
var datastores map[string]ConfigFromMap
func init() {
datastores = map[string]ConfigFromMap{
"mount": MountDatastoreConfig,
"mem": MemDatastoreConfig,
"log": LogDatastoreConfig,
"measure": MeasureDatastoreConfig,
}
}
func AddDatastoreConfigHandler(name string, dsc ConfigFromMap) error {
_, ok := datastores[name]
if ok {
return fmt.Errorf("already have a datastore named %q", name)
}
datastores[name] = dsc
return nil
}
// AnyDatastoreConfig returns a DatastoreConfig from a spec based on
// the "type" parameter
func AnyDatastoreConfig(params map[string]interface{}) (DatastoreConfig, error) {
which, ok := params["type"].(string)
if !ok {
return nil, fmt.Errorf("'type' field missing or not a string")
}
fun, ok := datastores[which]
if !ok {
return nil, fmt.Errorf("unknown datastore type: %s", which)
}
return fun(params)
}
type mountDatastoreConfig struct {
mounts []premount
}
type premount struct {
ds DatastoreConfig
prefix ds.Key
}
// MountDatastoreConfig returns a mount DatastoreConfig from a spec
func MountDatastoreConfig(params map[string]interface{}) (DatastoreConfig, error) {
var res mountDatastoreConfig
mounts, ok := params["mounts"].([]interface{})
if !ok {
return nil, fmt.Errorf("'mounts' field is missing or not an array")
}
for _, iface := range mounts {
cfg, ok := iface.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("expected map for mountpoint")
}
child, err := AnyDatastoreConfig(cfg)
if err != nil {
return nil, err
}
prefix, found := cfg["mountpoint"]
if !found {
return nil, fmt.Errorf("no 'mountpoint' on mount")
}
res.mounts = append(res.mounts, premount{
ds: child,
prefix: ds.NewKey(prefix.(string)),
})
}
sort.Slice(res.mounts,
func(i, j int) bool {
return res.mounts[i].prefix.String() > res.mounts[j].prefix.String()
})
return &res, nil
}
func (c *mountDatastoreConfig) DiskSpec() DiskSpec {
cfg := map[string]interface{}{"type": "mount"}
mounts := make([]interface{}, len(c.mounts))
for i, m := range c.mounts {
c := m.ds.DiskSpec()
if c == nil {
c = make(map[string]interface{})
}
c["mountpoint"] = m.prefix.String()
mounts[i] = c
}
cfg["mounts"] = mounts
return cfg
}
func (c *mountDatastoreConfig) Create(path string) (repo.Datastore, error) {
mounts := make([]mount.Mount, len(c.mounts))
for i, m := range c.mounts {
ds, err := m.ds.Create(path)
if err != nil {
return nil, err
}
mounts[i].Datastore = ds
mounts[i].Prefix = m.prefix
}
return mount.New(mounts), nil
}
type memDatastoreConfig struct {
cfg map[string]interface{}
}
// MemDatastoreConfig returns a memory DatastoreConfig from a spec
func MemDatastoreConfig(params map[string]interface{}) (DatastoreConfig, error) {
return &memDatastoreConfig{params}, nil
}
func (c *memDatastoreConfig) DiskSpec() DiskSpec {
return nil
}
func (c *memDatastoreConfig) Create(string) (repo.Datastore, error) {
return dssync.MutexWrap(ds.NewMapDatastore()), nil
}
type logDatastoreConfig struct {
child DatastoreConfig
name string
}
// LogDatastoreConfig returns a log DatastoreConfig from a spec
func LogDatastoreConfig(params map[string]interface{}) (DatastoreConfig, error) {
childField, ok := params["child"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("'child' field is missing or not a map")
}
child, err := AnyDatastoreConfig(childField)
if err != nil {
return nil, err
}
name, ok := params["name"].(string)
if !ok {
return nil, fmt.Errorf("'name' field was missing or not a string")
}
return &logDatastoreConfig{child, name}, nil
}
func (c *logDatastoreConfig) Create(path string) (repo.Datastore, error) {
child, err := c.child.Create(path)
if err != nil {
return nil, err
}
return ds.NewLogDatastore(child, c.name), nil
}
func (c *logDatastoreConfig) DiskSpec() DiskSpec {
return c.child.DiskSpec()
}
type measureDatastoreConfig struct {
child DatastoreConfig
prefix string
}
// MeasureDatastoreConfig returns a measure DatastoreConfig from a spec
func MeasureDatastoreConfig(params map[string]interface{}) (DatastoreConfig, error) {
childField, ok := params["child"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("'child' field is missing or not a map")
}
child, err := AnyDatastoreConfig(childField)
if err != nil {
return nil, err
}
prefix, ok := params["prefix"].(string)
if !ok {
return nil, fmt.Errorf("'prefix' field was missing or not a string")
}
return &measureDatastoreConfig{child, prefix}, nil
}
func (c *measureDatastoreConfig) DiskSpec() DiskSpec {
return c.child.DiskSpec()
}
func (c measureDatastoreConfig) Create(path string) (repo.Datastore, error) {
child, err := c.child.Create(path)
if err != nil {
return nil, err
}
return measure.New(c.prefix, child), nil
}