package store import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "reflect" "sort" "vbom.ml/util/sortorder" ) const ( metadataDir = "meta" metaFile = "meta.json" ) type metadataStore struct { root string config Config } func (s *metadataStore) contextDir(id contextdir) string { return filepath.Join(s.root, string(id)) } func (s *metadataStore) createOrUpdate(meta Metadata) error { contextDir := s.contextDir(contextdirOf(meta.Name)) if err := os.MkdirAll(contextDir, 0755); err != nil { return err } bytes, err := json.Marshal(&meta) if err != nil { return err } return ioutil.WriteFile(filepath.Join(contextDir, metaFile), bytes, 0644) } func parseTypedOrMap(payload []byte, getter TypeGetter) (interface{}, error) { if len(payload) == 0 || string(payload) == "null" { return nil, nil } if getter == nil { var res map[string]interface{} if err := json.Unmarshal(payload, &res); err != nil { return nil, err } return res, nil } typed := getter() if err := json.Unmarshal(payload, typed); err != nil { return nil, err } return reflect.ValueOf(typed).Elem().Interface(), nil } func (s *metadataStore) get(id contextdir) (Metadata, error) { contextDir := s.contextDir(id) bytes, err := ioutil.ReadFile(filepath.Join(contextDir, metaFile)) if err != nil { return Metadata{}, convertContextDoesNotExist(err) } var untyped untypedContextMetadata r := Metadata{ Endpoints: make(map[string]interface{}), } if err := json.Unmarshal(bytes, &untyped); err != nil { return Metadata{}, err } r.Name = untyped.Name if r.Metadata, err = parseTypedOrMap(untyped.Metadata, s.config.contextType); err != nil { return Metadata{}, err } for k, v := range untyped.Endpoints { if r.Endpoints[k], err = parseTypedOrMap(v, s.config.endpointTypes[k]); err != nil { return Metadata{}, err } } return r, err } func (s *metadataStore) remove(id contextdir) error { contextDir := s.contextDir(id) return os.RemoveAll(contextDir) } func (s *metadataStore) list() ([]Metadata, error) { ctxDirs, err := listRecursivelyMetadataDirs(s.root) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } var res []Metadata for _, dir := range ctxDirs { c, err := s.get(contextdir(dir)) if err != nil { return nil, err } res = append(res, c) } sort.Slice(res, func(i, j int) bool { return sortorder.NaturalLess(res[i].Name, res[j].Name) }) return res, nil } func isContextDir(path string) bool { s, err := os.Stat(filepath.Join(path, metaFile)) if err != nil { return false } return !s.IsDir() } func listRecursivelyMetadataDirs(root string) ([]string, error) { fis, err := ioutil.ReadDir(root) if err != nil { return nil, err } var result []string for _, fi := range fis { if fi.IsDir() { if isContextDir(filepath.Join(root, fi.Name())) { result = append(result, fi.Name()) } subs, err := listRecursivelyMetadataDirs(filepath.Join(root, fi.Name())) if err != nil { return nil, err } for _, s := range subs { result = append(result, fmt.Sprintf("%s/%s", fi.Name(), s)) } } } return result, nil } func convertContextDoesNotExist(err error) error { if os.IsNotExist(err) { return &contextDoesNotExistError{} } return err } type untypedContextMetadata struct { Metadata json.RawMessage `json:"metadata,omitempty"` Endpoints map[string]json.RawMessage `json:"endpoints,omitempty"` Name string `json:"name,omitempty"` }