github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/indexer/index/unique.go (about)

     1  // Copyright 2018-2022 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package index
    20  
    21  import (
    22  	"context"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	idxerrs "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/errors"
    29  	"github.com/cs3org/reva/v2/pkg/storage/utils/indexer/option"
    30  	metadata "github.com/cs3org/reva/v2/pkg/storage/utils/metadata"
    31  )
    32  
    33  // Unique are fields for an index of type unique.
    34  type Unique struct {
    35  	caseInsensitive bool
    36  	indexBy         option.IndexBy
    37  	typeName        string
    38  	filesDir        string
    39  	indexBaseDir    string
    40  	indexRootDir    string
    41  
    42  	storage metadata.Storage
    43  }
    44  
    45  // NewUniqueIndexWithOptions instantiates a new UniqueIndex instance. Init() should be
    46  // called afterward to ensure correct on-disk structure.
    47  func NewUniqueIndexWithOptions(storage metadata.Storage, o ...option.Option) Index {
    48  	opts := &option.Options{}
    49  	for _, opt := range o {
    50  		opt(opts)
    51  	}
    52  
    53  	u := &Unique{
    54  		storage:         storage,
    55  		caseInsensitive: opts.CaseInsensitive,
    56  		indexBy:         opts.IndexBy,
    57  		typeName:        opts.TypeName,
    58  		filesDir:        opts.FilesDir,
    59  		indexBaseDir:    path.Join(opts.Prefix, "index."+storage.Backend()),
    60  		indexRootDir:    path.Join(opts.Prefix, "index."+storage.Backend(), strings.Join([]string{"unique", opts.TypeName, opts.IndexBy.String()}, ".")),
    61  	}
    62  
    63  	return u
    64  }
    65  
    66  // Init initializes a unique index.
    67  func (idx *Unique) Init() error {
    68  	if err := idx.storage.MakeDirIfNotExist(context.Background(), idx.indexBaseDir); err != nil {
    69  		return err
    70  	}
    71  
    72  	return idx.storage.MakeDirIfNotExist(context.Background(), idx.indexRootDir)
    73  }
    74  
    75  // Lookup exact lookup by value.
    76  func (idx *Unique) Lookup(v string) ([]string, error) {
    77  	return idx.LookupCtx(context.Background(), v)
    78  }
    79  
    80  // LookupCtx retieves multiple exact values and allows passing in a context
    81  func (idx *Unique) LookupCtx(ctx context.Context, values ...string) ([]string, error) {
    82  	var allValues map[string]struct{}
    83  	if len(values) != 1 {
    84  		// prefetch all values with one request
    85  		entries, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		// convert known values to set
    90  		allValues = make(map[string]struct{}, len(entries))
    91  		for _, e := range entries {
    92  			allValues[path.Base(e)] = struct{}{}
    93  		}
    94  	}
    95  
    96  	// convert requested values to set
    97  	valueSet := make(map[string]struct{}, len(values))
    98  	if idx.caseInsensitive {
    99  		for _, v := range values {
   100  			valueSet[strings.ToLower(v)] = struct{}{}
   101  		}
   102  	} else {
   103  		for _, v := range values {
   104  			valueSet[v] = struct{}{}
   105  		}
   106  	}
   107  
   108  	var matches = make([]string, 0)
   109  	for v := range valueSet {
   110  		if _, ok := allValues[v]; ok || len(allValues) == 0 {
   111  			oldname, err := idx.storage.ResolveSymlink(context.Background(), path.Join(idx.indexRootDir, v))
   112  			if err != nil {
   113  				continue
   114  			}
   115  			matches = append(matches, oldname)
   116  		}
   117  	}
   118  
   119  	if len(matches) == 0 {
   120  		var v string
   121  		switch len(values) {
   122  		case 0:
   123  			v = "none"
   124  		case 1:
   125  			v = values[0]
   126  		default:
   127  			v = "multiple"
   128  		}
   129  		return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v}
   130  	}
   131  
   132  	return matches, nil
   133  }
   134  
   135  // Add adds a value to the index, returns the path to the root-document
   136  func (idx *Unique) Add(id, v string) (string, error) {
   137  	if v == "" {
   138  		return "", nil
   139  	}
   140  	if idx.caseInsensitive {
   141  		v = strings.ToLower(v)
   142  	}
   143  	target := path.Join(idx.filesDir, id)
   144  	newName := path.Join(idx.indexRootDir, v)
   145  	if err := idx.storage.CreateSymlink(context.Background(), target, newName); err != nil {
   146  		if os.IsExist(err) {
   147  			return "", &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v}
   148  		}
   149  
   150  		return "", err
   151  	}
   152  
   153  	return newName, nil
   154  }
   155  
   156  // Remove a value v from an index.
   157  func (idx *Unique) Remove(_ string, v string) error {
   158  	if v == "" {
   159  		return nil
   160  	}
   161  	if idx.caseInsensitive {
   162  		v = strings.ToLower(v)
   163  	}
   164  	searchPath := path.Join(idx.indexRootDir, v)
   165  	_, err := idx.storage.ResolveSymlink(context.Background(), searchPath)
   166  	if err != nil {
   167  		if os.IsNotExist(err) {
   168  			err = &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v}
   169  		}
   170  
   171  		return err
   172  	}
   173  
   174  	deletePath := path.Join(idx.indexRootDir, v)
   175  	return idx.storage.Delete(context.Background(), deletePath)
   176  }
   177  
   178  // Update index from <oldV> to <newV>.
   179  func (idx *Unique) Update(id, oldV, newV string) error {
   180  	if idx.caseInsensitive {
   181  		oldV = strings.ToLower(oldV)
   182  		newV = strings.ToLower(newV)
   183  	}
   184  
   185  	if err := idx.Remove(id, oldV); err != nil {
   186  		return err
   187  	}
   188  
   189  	if _, err := idx.Add(id, newV); err != nil {
   190  		return err
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  // Search allows for glob search on the index.
   197  func (idx *Unique) Search(pattern string) ([]string, error) {
   198  	if idx.caseInsensitive {
   199  		pattern = strings.ToLower(pattern)
   200  	}
   201  
   202  	paths, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	searchPath := idx.indexRootDir
   208  	matches := make([]string, 0)
   209  	for _, p := range paths {
   210  		if found, err := filepath.Match(pattern, path.Base(p)); found {
   211  			if err != nil {
   212  				return nil, err
   213  			}
   214  
   215  			oldPath, err := idx.storage.ResolveSymlink(context.Background(), path.Join(searchPath, path.Base(p)))
   216  			if err != nil {
   217  				return nil, err
   218  			}
   219  			matches = append(matches, oldPath)
   220  		}
   221  	}
   222  
   223  	if len(matches) == 0 {
   224  		return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: pattern}
   225  	}
   226  
   227  	return matches, nil
   228  }
   229  
   230  // CaseInsensitive undocumented.
   231  func (idx *Unique) CaseInsensitive() bool {
   232  	return idx.caseInsensitive
   233  }
   234  
   235  // IndexBy undocumented.
   236  func (idx *Unique) IndexBy() option.IndexBy {
   237  	return idx.indexBy
   238  }
   239  
   240  // TypeName undocumented.
   241  func (idx *Unique) TypeName() string {
   242  	return idx.typeName
   243  }
   244  
   245  // FilesDir undocumented.
   246  func (idx *Unique) FilesDir() string {
   247  	return idx.filesDir
   248  }
   249  
   250  // Delete deletes the index folder from its storage.
   251  func (idx *Unique) Delete() error {
   252  	return idx.storage.Delete(context.Background(), idx.indexRootDir)
   253  }