repo_description: An Efficient Enterprise-class Container Engine
repo_topics: cloud-native containers efficiency go isolation kubernetes oci package security
code: package meta
  
  import (
  	"encoding/json"
  	"fmt"
  	"reflect"
  	"sync"
  
  	"github.com/tchap/go-patricia/patricia"
  )
  
  var (
  	// MetaJSONFile is the default name of json file.
  	MetaJSONFile = "meta.json"
  )
  
  // Bucket is a Bucket.
  type Bucket struct {
  	Name string
  	Type reflect.Type
  }
  
  // NewObject returns Object.
  func (b *Bucket) NewObject() Object {
  	return reflect.New(b.Type).Interface().(Object)
  }
  
  // Config represents the configurations used for metadata store.
  type Config struct {
  	Driver  string
  	Buckets []Bucket
  	BaseDir string
  }
  
  // Object is an interface.
  type Object interface {
  	Key() string
  }
  
  // Store defines what a metadata store should be like.
  type Store struct {
  	Config
  	trieLock *sync.Mutex // trieLock use to protect 'trie'.
  	trie     *patricia.Trie
  	current  *Bucket
  	backend  Backend
  }
  
  // NewStore creates a backend storage.
  func NewStore(cfg Config) (*Store, error) {
  	if len(cfg.Buckets) < 1 {
  		return nil, fmt.Errorf("config bucket can not be empty")
  	}
  
  	if cfg.Driver == "" {
  		cfg.Driver = DefaultStore
  	}
  
  	if _, ok := backendFactory[cfg.Driver]; !ok {
  		return nil, fmt.Errorf("store driver %s not found", cfg.Driver)
  	}
  	backend, err := backendFactory[cfg.Driver](cfg)
  	if err != nil {
  		return nil, fmt.Errorf("create driver %s failed: %v", cfg.Driver, err)
  	}
  
  	s := &Store{
  		Config:   cfg,
  		backend:  backend,
  		trieLock: new(sync.Mutex),
  		trie:     patricia.NewTrie(),
  	}
  
  	keys := []string{}
  	for _, bucket := range cfg.Buckets {
  		k, err := s.backend.Keys(bucket.Name)
  		if err != nil {
  			return nil, err
  		}
  
  		keys = append(keys, k...)
  	}
  
  	for _, key := range keys {
  		s.trie.Insert(patricia.Prefix(key), struct{}{})
  	}
  
  	s = s.Bucket(cfg.Buckets[0].Name)
  	if s == nil {
  		return nil, fmt.Errorf("failed to new bucket store")
  	}
  
  	return s, nil
  }
  
  // Bucket returns a specific store instance.
  // And name is used to specify the bucket's name that will be write.
  func (s *Store) Bucket(name string) *Store {
  	if name == "" {
  		if s.current == nil {
  			return nil
  		}
  		return s
  	}
  
  	var pb *Bucket
  
  	for _, b := range s.Buckets {
  		if name == b.Name {
  			pb = &b
  			break
  		}
  	}
  	if pb == nil {
  		return nil
  	}
  
  	return &Store{
  		Config:   s.Config,
  		backend:  s.backend,
  		current:  pb,
  		trieLock: s.trieLock,
  		trie:     s.trie,
  	}
  }
  
  // Put writes the 'obj' into backend storage.
  func (s *Store) Put(obj Object) error {
  	value, err := json.Marshal(obj)
  	if err != nil {
  		return fmt.Errorf("failed to encode meta data: %v", err)
  	}
  	if err := s.backend.Put(s.current.Name, obj.Key(), value); err != nil {
  		return fmt.Errorf("failed to put meta data: %v", err)
  	}
  
  	// add key into trie tree.
  	s.trieLock.Lock()
  	s.trie.Insert(patricia.Prefix(obj.Key()), struct{}{})
  	s.trieLock.Unlock()
  
  	return nil
  }
  
  // Fetch uses to get meta data and decode it into 'obj'.
  func (s *Store) Fetch(obj Object) error {
  	value, err := s.backend.Get(s.current.Name, obj.Key())
  	if err != nil {
  		return err
  	}
  
  	if err := json.Unmarshal(value, obj); err != nil {
  		return fmt.Errorf("failed to decode meta data: %v", err)
  	}
  	return nil
  }
  
  // Get uses to get meta data.
  func (s *Store) Get(key string) (Object, error) {
  	value, err := s.backend.Get(s.current.Name, key)
  	if err != nil {
  		return nil, err
  	}
  
  	obj := s.current.NewObject()
  	if err := json.Unmarshal(value, obj); err != nil {
  		return nil, fmt.Errorf("failed to decode meta data: %v", err)
  	}
  	return obj, nil
  }
  
  // Remove calls it to remove a object.
  func (s *Store) Remove(key string) error {
  	if err := s.backend.Remove(s.current.Name, key); err != nil {
  		return err
  	}
  
  	// delete key from trie tree.
  	s.trieLock.Lock()
  	s.trie.Delete(patricia.Prefix(key))
  	s.trieLock.Unlock()
  
  	return nil
  }
  
  // ForEach uses to handle every object by sequence.
  func (s *Store) ForEach(handle func(Object) error) error {
  	values, err := s.backend.List(s.current.Name)
  	if err != nil {
  		return fmt.Errorf("failed to list meta data: %v", err)
  	}
  	for _, v := range values {
  		obj := s.current.NewObject()
  		if err := json.Unmarshal(v, obj); err != nil {
  			return fmt.Errorf("failed to decode meta data: %v", err)
  		}
  		if err := handle(obj); err != nil {
  			return err
  		}
  	}
  	return nil
  }
  
  // List returns all objects as map.
  func (s *Store) List() (map[string]Object, error) {
  	objs := make(map[string]Object, 32)
  
  	handle := func(obj Object) error {
  		objs[obj.Key()] = obj
  		return nil
  	}
  	if err := s.ForEach(handle); err != nil {
  		return nil, err
  	}
  	return objs, nil
  }
  
  // Keys returns all keys only.
  func (s *Store) Keys() ([]string, error) {
  	keys, err := s.backend.Keys(s.current.Name)
  	if err != nil {
  		return nil, fmt.Errorf("failed to get keys: %v", err)
  	}
  	return keys, nil
  }
  
  // GetWithPrefix return objects matching prefix.
  func (s *Store) GetWithPrefix(prefix string) ([]Object, error) {
  	keys, err := s.KeysWithPrefix(prefix)
  	if err != nil {
  		return nil, err
  	}
  
  	var objects []Object
  
  	for _, key := range keys {
  		obj, err := s.Get(key)
  		if err != nil {
  			return nil, err
  		}
  		objects = append(objects, obj)
  	}
  
  	return objects, nil
  }
  
  // KeysWithPrefix return all keys matching prefix.
  func (s *Store) KeysWithPrefix(prefix string) ([]string, error) {
  	var keys []string
  
  	if len(prefix) == 0 {
  		return keys, nil
  	}
  
  	fn := func(prefix patricia.Prefix, item patricia.Item) error {
  		keys = append(keys, string(prefix))
  		return nil
  	}
  
  	s.trieLock.Lock()
  	defer s.trieLock.Unlock()
  
  	err := s.trie.VisitSubtree(patricia.Prefix(prefix), fn)
  	return keys, err
  }
  
  // Path returns the path with specified key.
  func (s *Store) Path(key string) string {
  	return s.backend.Path(key)
  }
  
  // Shutdown releases all resources used by the backend
  func (s *Store) Shutdown() error {
  	return s.backend.Close()
  }
  