github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/indexer/index/autoincrement.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 "sort" 27 "strconv" 28 "strings" 29 30 idxerrs "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/errors" 31 "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/option" 32 metadata "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 33 ) 34 35 // Autoincrement are fields for an index of type autoincrement. 36 type Autoincrement struct { 37 indexBy option.IndexBy 38 typeName string 39 filesDir string 40 indexBaseDir string 41 indexRootDir string 42 43 bound *option.Bound 44 storage metadata.Storage 45 } 46 47 // NewAutoincrementIndex instantiates a new AutoincrementIndex instance. 48 func NewAutoincrementIndex(storage metadata.Storage, o ...option.Option) Index { 49 opts := &option.Options{} 50 for _, opt := range o { 51 opt(opts) 52 } 53 54 u := &Autoincrement{ 55 storage: storage, 56 indexBy: opts.IndexBy, 57 typeName: opts.TypeName, 58 filesDir: opts.FilesDir, 59 bound: opts.Bound, 60 indexBaseDir: path.Join(opts.Prefix, "index."+storage.Backend()), 61 indexRootDir: path.Join(opts.Prefix, "index."+storage.Backend(), strings.Join([]string{"autoincrement", opts.TypeName, opts.IndexBy.String()}, ".")), 62 } 63 64 return u 65 } 66 67 // Init initializes an autoincrement index. 68 func (idx *Autoincrement) Init() error { 69 if err := idx.storage.MakeDirIfNotExist(context.Background(), idx.indexBaseDir); err != nil { 70 return err 71 } 72 73 return idx.storage.MakeDirIfNotExist(context.Background(), idx.indexRootDir) 74 } 75 76 // Lookup exact lookup by value. 77 func (idx *Autoincrement) Lookup(v string) ([]string, error) { 78 return idx.LookupCtx(context.Background(), v) 79 } 80 81 // LookupCtx retieves multiple exact values and allows passing in a context 82 func (idx *Autoincrement) LookupCtx(ctx context.Context, values ...string) ([]string, error) { 83 var allValues map[string]struct{} 84 if len(values) != 1 { 85 // prefetch all values with one request 86 entries, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir) 87 if err != nil { 88 return nil, err 89 } 90 // convert known values to set 91 allValues = make(map[string]struct{}, len(entries)) 92 for _, e := range entries { 93 allValues[path.Base(e)] = struct{}{} 94 } 95 } 96 97 // convert requested values to set 98 valueSet := make(map[string]struct{}, len(values)) 99 for _, v := range values { 100 valueSet[v] = struct{}{} 101 } 102 103 var matches = []string{} 104 for v := range valueSet { 105 if _, ok := allValues[v]; ok || len(allValues) == 0 { 106 oldname, err := idx.storage.ResolveSymlink(context.Background(), path.Join(idx.indexRootDir, v)) 107 if err != nil { 108 continue 109 } 110 matches = append(matches, oldname) 111 } 112 } 113 114 if len(matches) == 0 { 115 var v string 116 switch len(values) { 117 case 0: 118 v = "none" 119 case 1: 120 v = values[0] 121 default: 122 v = "multiple" 123 } 124 return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 125 } 126 127 return matches, nil 128 } 129 130 // Add a new value to the index. 131 func (idx *Autoincrement) Add(id, v string) (string, error) { 132 var newName string 133 if v == "" { 134 next, err := idx.next() 135 if err != nil { 136 return "", err 137 } 138 newName = path.Join(idx.indexRootDir, strconv.Itoa(next)) 139 } else { 140 newName = path.Join(idx.indexRootDir, v) 141 } 142 if err := idx.storage.CreateSymlink(context.Background(), id, newName); err != nil { 143 if os.IsExist(err) { 144 return "", &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 145 } 146 147 return "", err 148 } 149 150 return newName, nil 151 } 152 153 // Remove a value v from an index. 154 func (idx *Autoincrement) Remove(_ string, v string) error { 155 if v == "" { 156 return nil 157 } 158 searchPath := path.Join(idx.indexRootDir, v) 159 _, err := idx.storage.ResolveSymlink(context.Background(), searchPath) 160 if err != nil { 161 if os.IsNotExist(err) { 162 err = &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 163 } 164 165 return err 166 } 167 168 deletePath := path.Join(idx.indexRootDir, v) 169 return idx.storage.Delete(context.Background(), deletePath) 170 } 171 172 // Update index from <oldV> to <newV>. 173 func (idx *Autoincrement) Update(id, oldV, newV string) error { 174 if err := idx.Remove(id, oldV); err != nil { 175 return err 176 } 177 178 _, err := idx.Add(id, newV) 179 return err 180 } 181 182 // Search allows for glob search on the index. 183 func (idx *Autoincrement) Search(pattern string) ([]string, error) { 184 paths, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir) 185 if err != nil { 186 return nil, err 187 } 188 189 searchPath := idx.indexRootDir 190 matches := make([]string, 0) 191 for _, p := range paths { 192 if found, err := filepath.Match(pattern, path.Base(p)); found { 193 if err != nil { 194 return nil, err 195 } 196 197 oldPath, err := idx.storage.ResolveSymlink(context.Background(), path.Join(searchPath, path.Base(p))) 198 if err != nil { 199 return nil, err 200 } 201 matches = append(matches, oldPath) 202 } 203 } 204 205 return matches, nil 206 } 207 208 // CaseInsensitive undocumented. 209 func (idx *Autoincrement) CaseInsensitive() bool { 210 return false 211 } 212 213 // IndexBy undocumented. 214 func (idx *Autoincrement) IndexBy() option.IndexBy { 215 return idx.indexBy 216 } 217 218 // TypeName undocumented. 219 func (idx *Autoincrement) TypeName() string { 220 return idx.typeName 221 } 222 223 // FilesDir undocumented. 224 func (idx *Autoincrement) FilesDir() string { 225 return idx.filesDir 226 } 227 228 func (idx *Autoincrement) next() (int, error) { 229 paths, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir) 230 231 if err != nil { 232 return -1, err 233 } 234 235 if len(paths) == 0 { 236 return int(idx.bound.Lower), nil 237 } 238 239 sort.Slice(paths, func(i, j int) bool { 240 a, _ := strconv.Atoi(path.Base(paths[i])) 241 b, _ := strconv.Atoi(path.Base(paths[j])) 242 return a < b 243 }) 244 245 latest, err := strconv.Atoi(path.Base(paths[len(paths)-1])) // would returning a string be a better interface? 246 if err != nil { 247 return -1, err 248 } 249 250 if int64(latest) < idx.bound.Lower { 251 return int(idx.bound.Lower), nil 252 } 253 254 return latest + 1, nil 255 } 256 257 // Delete deletes the index folder from its storage. 258 func (idx *Autoincrement) Delete() error { 259 return idx.storage.Delete(context.Background(), idx.indexRootDir) 260 }