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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user