mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-05 19:59:37 -06:00
227 lines
5.1 KiB
Go
227 lines
5.1 KiB
Go
package disk
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
idxerrs "github.com/owncloud/ocis/ocis-pkg/indexer/errors"
|
|
|
|
"github.com/owncloud/ocis/ocis-pkg/indexer/index"
|
|
"github.com/owncloud/ocis/ocis-pkg/indexer/option"
|
|
"github.com/owncloud/ocis/ocis-pkg/indexer/registry"
|
|
)
|
|
|
|
// Autoincrement are fields for an index of type autoincrement.
|
|
type Autoincrement struct {
|
|
indexBy string
|
|
typeName string
|
|
filesDir string
|
|
indexBaseDir string
|
|
indexRootDir string
|
|
|
|
bound *option.Bound
|
|
}
|
|
|
|
func init() {
|
|
registry.IndexConstructorRegistry["disk"]["autoincrement"] = NewAutoincrementIndex
|
|
}
|
|
|
|
// NewAutoincrementIndex instantiates a new AutoincrementIndex instance. Init() MUST be called upon instantiation.
|
|
func NewAutoincrementIndex(o ...option.Option) index.Index {
|
|
opts := &option.Options{}
|
|
for _, opt := range o {
|
|
opt(opts)
|
|
}
|
|
|
|
if opts.Entity == nil {
|
|
panic("invalid autoincrement index: configured without entity")
|
|
}
|
|
|
|
k, err := getKind(opts.Entity, opts.IndexBy)
|
|
if !isValidKind(k) || err != nil {
|
|
panic("invalid autoincrement index: configured on non-numeric field")
|
|
}
|
|
|
|
return &Autoincrement{
|
|
indexBy: opts.IndexBy,
|
|
typeName: opts.TypeName,
|
|
filesDir: opts.FilesDir,
|
|
bound: opts.Bound,
|
|
indexBaseDir: path.Join(opts.DataDir, "index.disk"),
|
|
indexRootDir: path.Join(path.Join(opts.DataDir, "index.disk"), strings.Join([]string{"autoincrement", opts.TypeName, opts.IndexBy}, ".")),
|
|
}
|
|
}
|
|
|
|
// Init initializes an autoincrement index.
|
|
func (idx *Autoincrement) Init() error {
|
|
if _, err := os.Stat(idx.filesDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.MkdirAll(idx.indexRootDir, 0777); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Lookup exact lookup by value.
|
|
func (idx *Autoincrement) Lookup(v string) ([]string, error) {
|
|
searchPath := path.Join(idx.indexRootDir, v)
|
|
if err := isValidSymlink(searchPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = &idxerrs.NotFoundErr{TypeName: idx.typeName, Key: idx.indexBy, Value: v}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
p, err := os.Readlink(searchPath)
|
|
if err != nil {
|
|
return []string{}, nil
|
|
}
|
|
|
|
return []string{p}, err
|
|
}
|
|
|
|
// Add a new value to the index.
|
|
func (idx *Autoincrement) Add(id, v string) (string, error) {
|
|
nextID, err := idx.next()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
oldName := filepath.Join(idx.filesDir, id)
|
|
var newName string
|
|
if v == "" {
|
|
newName = filepath.Join(idx.indexRootDir, strconv.Itoa(nextID))
|
|
} else {
|
|
newName = filepath.Join(idx.indexRootDir, v)
|
|
}
|
|
err = os.Symlink(oldName, newName)
|
|
if errors.Is(err, os.ErrExist) {
|
|
return "", &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, Key: idx.indexBy, Value: v}
|
|
}
|
|
|
|
return newName, err
|
|
}
|
|
|
|
// Remove a value v from an index.
|
|
func (idx *Autoincrement) Remove(id string, v string) error {
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
searchPath := path.Join(idx.indexRootDir, v)
|
|
return os.Remove(searchPath)
|
|
}
|
|
|
|
// Update index from <oldV> to <newV>.
|
|
func (idx *Autoincrement) Update(id, oldV, newV string) error {
|
|
oldPath := path.Join(idx.indexRootDir, oldV)
|
|
if err := isValidSymlink(oldPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &idxerrs.NotFoundErr{TypeName: idx.TypeName(), Key: idx.IndexBy(), Value: oldV}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
newPath := path.Join(idx.indexRootDir, newV)
|
|
err := isValidSymlink(newPath)
|
|
if err == nil {
|
|
return &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, Key: idx.indexBy, Value: newV}
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
err = os.Rename(oldPath, newPath)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Search allows for glob search on the index.
|
|
func (idx *Autoincrement) Search(pattern string) ([]string, error) {
|
|
paths, err := filepath.Glob(path.Join(idx.indexRootDir, pattern))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(paths) == 0 {
|
|
return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, Key: idx.indexBy, Value: pattern}
|
|
}
|
|
|
|
res := make([]string, 0)
|
|
for _, p := range paths {
|
|
if err := isValidSymlink(p); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
src, err := os.Readlink(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res = append(res, src)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// CaseInsensitive undocumented.
|
|
func (idx *Autoincrement) CaseInsensitive() bool {
|
|
return false
|
|
}
|
|
|
|
// IndexBy undocumented.
|
|
func (idx *Autoincrement) IndexBy() string {
|
|
return idx.indexBy
|
|
}
|
|
|
|
// TypeName undocumented.
|
|
func (idx *Autoincrement) TypeName() string {
|
|
return idx.typeName
|
|
}
|
|
|
|
// FilesDir undocumented.
|
|
func (idx *Autoincrement) FilesDir() string {
|
|
return idx.filesDir
|
|
}
|
|
|
|
func (idx *Autoincrement) next() (int, error) {
|
|
files, err := readDir(idx.indexRootDir)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
return int(idx.bound.Lower), nil
|
|
}
|
|
|
|
latest, err := lastValueFromTree(files)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
if int64(latest) < idx.bound.Lower {
|
|
return int(idx.bound.Lower), nil
|
|
}
|
|
|
|
return latest + 1, nil
|
|
}
|
|
|
|
// Delete deletes the index root folder from the configured storage.
|
|
func (idx *Autoincrement) Delete() error {
|
|
return os.RemoveAll(idx.indexRootDir)
|
|
}
|
|
|
|
func lastValueFromTree(files []os.FileInfo) (int, error) {
|
|
latest, err := strconv.Atoi(path.Base(files[len(files)-1].Name()))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
return latest, nil
|
|
}
|