Switch to uuids and simplify names

- bundles, settings and values now have uuids as identifier
- removed unnecessary name parts (SettingsBundle -> Bundle,
SettingsValue -> Value, ...)
This commit is contained in:
Benedikt Kulmann
2020-08-19 10:20:51 +02:00
parent 33720f2b5a
commit 3cb272e76e
17 changed files with 2631 additions and 1941 deletions
+63 -38
View File
@@ -2,55 +2,55 @@
package store
import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"sync"
"github.com/gofrs/uuid"
merrors "github.com/micro/go-micro/v2/errors"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
// ListBundles returns all bundles in the mountPath folder belonging to the given extension
func (s Store) ListBundles(identifier *proto.Identifier) ([]*proto.SettingsBundle, error) {
var records []*proto.SettingsBundle
bundlesFolder := s.buildFolderPathBundles(false)
extensionFolders, err := ioutil.ReadDir(bundlesFolder)
var m = &sync.RWMutex{}
// ListBundles returns all bundles in the dataPath folder that match the given type.
func (s Store) ListBundles(bundleType proto.Bundle_Type) ([]*proto.Bundle, error) {
// FIXME: list requests should be ran against a cache, not FS
m.RLock()
defer m.RUnlock()
var records []*proto.Bundle
bundlesFolder := s.buildFolderPathForBundles(false)
bundleFiles, err := ioutil.ReadDir(bundlesFolder)
if err != nil {
return records, nil
}
if len(identifier.Extension) < 1 {
s.Logger.Info().Msg("listing all bundles")
} else {
s.Logger.Info().Msgf("listing bundles by extension %v", identifier.Extension)
}
for _, extensionFolder := range extensionFolders {
extensionPath := path.Join(bundlesFolder, extensionFolder.Name())
bundleFiles, err := ioutil.ReadDir(extensionPath)
if err == nil {
for _, bundleFile := range bundleFiles {
record := proto.SettingsBundle{}
bundlePath := path.Join(extensionPath, bundleFile.Name())
err = s.parseRecordFromFile(&record, bundlePath)
if err != nil {
s.Logger.Warn().Msgf("error reading %v", bundlePath)
continue
}
if len(identifier.Extension) == 0 || identifier.Extension == record.Identifier.Extension {
records = append(records, &record)
}
}
} else {
s.Logger.Err(err).Msgf("error reading %v", extensionPath)
for _, bundleFile := range bundleFiles {
record := proto.Bundle{}
err = s.parseRecordFromFile(&record, filepath.Join(bundlesFolder, bundleFile.Name()))
if err != nil {
s.Logger.Warn().Msgf("error reading %v", bundleFile)
continue
}
if record.Type != bundleType {
continue
}
records = append(records, &record)
}
return records, nil
}
// ReadBundle tries to find a bundle by the given identifier within the mountPath.
// Extension and BundleKey within the identifier are required.
func (s Store) ReadBundle(identifier *proto.Identifier) (*proto.SettingsBundle, error) {
filePath := s.buildFilePathFromBundleArgs(identifier.Extension, identifier.BundleKey, false)
record := proto.SettingsBundle{}
// ReadBundle tries to find a bundle by the given id within the dataPath.
func (s Store) ReadBundle(bundleID string) (*proto.Bundle, error) {
// FIXME: locking should happen on the file here, not globally.
m.RLock()
defer m.RUnlock()
filePath := s.buildFilePathForBundle(bundleID, false)
record := proto.Bundle{}
if err := s.parseRecordFromFile(&record, filePath); err != nil {
return nil, err
}
@@ -59,10 +59,35 @@ func (s Store) ReadBundle(identifier *proto.Identifier) (*proto.SettingsBundle,
return &record, nil
}
// WriteBundle writes the given record into a file within the mountPath
// Extension and BundleKey within the record identifier are required.
func (s Store) WriteBundle(record *proto.SettingsBundle) (*proto.SettingsBundle, error) {
filePath := s.buildFilePathFromBundle(record, true)
// ReadSetting tries to find a setting by the given id within the dataPath.
func (s Store) ReadSetting(settingID string) (*proto.Setting, error) {
// FIXME: locking should happen on the file here, not globally.
m.RLock()
defer m.RUnlock()
bundles, err := s.ListBundles(proto.Bundle_TYPE_DEFAULT)
if err != nil {
return nil, err
}
for _, bundle := range bundles {
for _, setting := range bundle.Settings {
if setting.Id == settingID {
return setting, nil
}
}
}
return nil, merrors.NotFound(settingID, fmt.Sprintf("could not read setting: %v", settingID))
}
// WriteBundle writes the given record into a file within the dataPath.
func (s Store) WriteBundle(record *proto.Bundle) (*proto.Bundle, error) {
// FIXME: locking should happen on the file here, not globally.
m.Lock()
defer m.Unlock()
if record.Id == "" {
record.Id = uuid.Must(uuid.NewV4()).String()
}
filePath := s.buildFilePathForBundle(record.Id, true)
if err := s.writeRecordToFile(record, filePath); err != nil {
return nil, err
}
+18 -27
View File
@@ -2,52 +2,43 @@ package store
import (
"os"
"path"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"path/filepath"
)
const folderNameBundles = "bundles"
const folderNameValues = "values"
// Builds the folder path for storing settings bundles. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathBundles(mkdir bool) string {
folderPath := path.Join(s.mountPath, folderNameBundles)
// buildFolderPathForBundles builds the folder path for storing settings bundles. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathForBundles(mkdir bool) string {
folderPath := filepath.Join(s.mountPath, folderNameBundles)
if mkdir {
s.ensureFolderExists(folderPath)
}
return folderPath
}
// Builds a unique file name from the given settings bundle. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromBundle(bundle *proto.SettingsBundle, mkdir bool) string {
return s.buildFilePathFromBundleArgs(bundle.Identifier.Extension, bundle.Identifier.BundleKey, mkdir)
// buildFilePathForBundle builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathForBundle(bundleID string, mkdir bool) string {
extensionFolder := s.buildFolderPathForBundles(mkdir)
return filepath.Join(extensionFolder, bundleID+".json")
}
// Builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromBundleArgs(extension string, bundleKey string, mkdir bool) string {
extensionFolder := path.Join(s.mountPath, folderNameBundles, extension)
// buildFolderPathForValues builds the folder path for storing settings values. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathForValues(mkdir bool) string {
folderPath := filepath.Join(s.mountPath, folderNameValues)
if mkdir {
s.ensureFolderExists(extensionFolder)
s.ensureFolderExists(folderPath)
}
return path.Join(extensionFolder, bundleKey+".json")
return folderPath
}
// Builds a unique file name from the given settings value. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromValue(value *proto.SettingsValue, mkdir bool) string {
return s.buildFilePathFromValueArgs(value.Identifier.AccountUuid, value.Identifier.Extension, value.Identifier.BundleKey, mkdir)
// buildFilePathForValue builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathForValue(valueID string, mkdir bool) string {
extensionFolder := s.buildFolderPathForValues(mkdir)
return filepath.Join(extensionFolder, valueID+".json")
}
// Builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromValueArgs(accountUUID string, extension string, bundleKey string, mkdir bool) string {
extensionFolder := path.Join(s.mountPath, folderNameValues, accountUUID, extension)
if mkdir {
s.ensureFolderExists(extensionFolder)
}
return path.Join(extensionFolder, bundleKey+".json")
}
// Checks if the given path is an existing folder and creates one if not existing
// ensureFolderExists checks if the given path is an existing folder and creates one if not existing
func (s Store) ensureFolderExists(path string) {
if _, err := os.Stat(path); os.IsNotExist(err) {
err = os.MkdirAll(path, 0700)
+8 -2
View File
@@ -12,7 +12,7 @@ import (
var (
// Name is the default name for the settings store
Name = "ocis-settings-store"
Name = "ocis-settings"
managerName = "filesystem"
)
@@ -24,7 +24,13 @@ type Store struct {
// New creates a new store
func New(cfg *config.Config) settings.Manager {
s := Store{}
s := Store{
Logger: olog.NewLogger(
olog.Color(cfg.Log.Color),
olog.Pretty(cfg.Log.Pretty),
olog.Level(cfg.Log.Level),
),
}
dest := path.Join(cfg.Storage.RootMountPath, Name)
if _, err := os.Stat(dest); err != nil {
+75 -88
View File
@@ -2,108 +2,95 @@
package store
import (
"os"
"path"
"io/ioutil"
"path/filepath"
"github.com/gofrs/uuid"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
)
// ReadValue tries to find a value by the given identifier attributes within the mountPath
// All identifier fields are required.
func (s Store) ReadValue(identifier *proto.Identifier) (*proto.SettingsValue, error) {
filePath := s.buildFilePathFromValueArgs(identifier.AccountUuid, identifier.Extension, identifier.BundleKey, false)
values, err := s.readValuesMapFromFile(filePath)
// ListValues reads all values that match the given bundleId and accountUUID.
// If the bundleId is empty, it's ignored for filtering.
// If the accountUUID is empty, only values with empty accountUUID are returned.
// If the accountUUID is not empty, values with an empty or with a matching accountUUID are returned.
func (s Store) ListValues(bundleID, accountUUID string) ([]*proto.Value, error) {
var records []*proto.Value
valuesFolder := s.buildFolderPathForValues(false)
valueFiles, err := ioutil.ReadDir(valuesFolder)
if err != nil {
return nil, err
return records, nil
}
if value := values.Values[identifier.SettingKey]; value != nil {
return value, nil
for _, valueFile := range valueFiles {
record := proto.Value{}
err := s.parseRecordFromFile(&record, filepath.Join(valuesFolder, valueFile.Name()))
if err != nil {
s.Logger.Warn().Msgf("error reading %v", valueFile)
continue
}
if bundleID != "" && record.BundleId != bundleID {
continue
}
// if requested accountUUID empty -> fetch all system level values
if accountUUID == "" && record.AccountUuid != "" {
continue
}
// if requested accountUUID empty -> fetch all individual + all system level values
if accountUUID != "" && record.AccountUuid != "" && record.AccountUuid != accountUUID {
continue
}
records = append(records, &record)
}
// TODO: we want to return sensible defaults here, when the value was not found
return nil, gstatus.Error(codes.NotFound, "SettingsValue not set")
return records, nil
}
// WriteValue writes the given SettingsValue into a file within the mountPath
// All identifier fields within the value are required.
func (s Store) WriteValue(value *proto.SettingsValue) (*proto.SettingsValue, error) {
filePath := s.buildFilePathFromValue(value, true)
values, err := s.readValuesMapFromFile(filePath)
// ReadValue tries to find a value by the given valueId within the dataPath
func (s Store) ReadValue(valueID string) (*proto.Value, error) {
filePath := s.buildFilePathForValue(valueID, false)
record := proto.Value{}
if err := s.parseRecordFromFile(&record, filePath); err != nil {
return nil, err
}
s.Logger.Debug().Msgf("read contents from file: %v", filePath)
return &record, nil
}
// ReadValueByUniqueIdentifiers tries to find a value given a set of unique identifiers
func (s Store) ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*proto.Value, error) {
valuesFolder := s.buildFolderPathForValues(false)
files, err := ioutil.ReadDir(valuesFolder)
if err != nil {
return nil, err
}
values.Values[value.Identifier.SettingKey] = value
if err := s.writeRecordToFile(values, filePath); err != nil {
for i := range files {
if !files[i].IsDir() {
r := proto.Value{}
s.Logger.Debug().Msgf("reading contents from file: %v", filepath.Join(valuesFolder, files[i].Name()))
if err := s.parseRecordFromFile(&r, filepath.Join(valuesFolder, files[i].Name())); err != nil {
s.Logger.Debug().Msgf("match found: %v", filepath.Join(valuesFolder, files[i].Name()))
return &proto.Value{}, nil
}
if r.AccountUuid == accountUUID && r.SettingId == settingID {
return &r, nil
}
}
}
return &proto.Value{}, nil
}
// WriteValue writes the given value into a file within the dataPath
func (s Store) WriteValue(value *proto.Value) (*proto.Value, error) {
s.Logger.Debug().Str("value", value.String()).Msg("writing value")
if value.Id == "" {
value.Id = uuid.Must(uuid.NewV4()).String()
}
filePath := s.buildFilePathForValue(value.Id, true)
if err := s.writeRecordToFile(value, filePath); err != nil {
return nil, err
}
return value, nil
}
// ListValues reads all values within the scope of the given identifier
// AccountUuid is required.
func (s Store) ListValues(identifier *proto.Identifier) ([]*proto.SettingsValue, error) {
accountFolderPath := path.Join(s.mountPath, folderNameValues, identifier.AccountUuid)
var values []*proto.SettingsValue
if _, err := os.Stat(accountFolderPath); err != nil {
return values, nil
}
// depending on the set values in the identifier arg, collect all SettingValues files for the account
var valueFilePaths []string
if len(identifier.Extension) < 1 {
if err := filepath.Walk(accountFolderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
valueFilePaths = append(valueFilePaths, path)
return nil
}); err != nil {
s.Logger.Err(err).Msgf("error reading %v", accountFolderPath)
return values, nil
}
} else if len(identifier.BundleKey) < 1 {
extensionPath := path.Join(accountFolderPath, identifier.Extension)
if err := filepath.Walk(extensionPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
valueFilePaths = append(valueFilePaths, path)
return nil
}); err != nil {
s.Logger.Err(err).Msgf("error reading %v", extensionPath)
return values, nil
}
} else {
bundlePath := path.Join(accountFolderPath, identifier.Extension, identifier.BundleKey+".json")
valueFilePaths = append(valueFilePaths, bundlePath)
}
// parse the SettingValues from the collected files
for _, filePath := range valueFilePaths {
bundleValues, err := s.readValuesMapFromFile(filePath)
if err != nil {
s.Logger.Err(err).Msgf("error reading %v", filePath)
} else {
for _, value := range bundleValues.Values {
values = append(values, value)
}
}
}
return values, nil
}
// Reads SettingsValues as map from the given file or returns an empty map if the file doesn't exist.
func (s Store) readValuesMapFromFile(filePath string) (*proto.SettingsValues, error) {
values := &proto.SettingsValues{}
err := s.parseRecordFromFile(values, filePath)
if err != nil {
if os.IsNotExist(err) {
values.Values = map[string]*proto.SettingsValue{}
} else {
return nil, err
}
}
return values, nil
}