github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/objlist_utils.go (about) 1 // Package cmn provides common constants, types, and utilities for AIS clients 2 // and AIStore. 3 /* 4 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package cmn 7 8 import ( 9 "path/filepath" 10 "sort" 11 "strings" 12 13 "github.com/NVIDIA/aistore/api/apc" 14 "github.com/NVIDIA/aistore/cmn/cos" 15 "github.com/NVIDIA/aistore/cmn/debug" 16 ) 17 18 var nilEntry LsoEnt 19 20 //////////////// 21 // LsoEntries // 22 //////////////// 23 24 func (entries LsoEntries) cmp(i, j int) bool { return entries[i].less(entries[j]) } 25 26 func appSorted(entries LsoEntries, ne *LsoEnt) LsoEntries { 27 for i := range entries { 28 if ne.Name > entries[i].Name { 29 continue 30 } 31 // dedup 32 if ne.Name == entries[i].Name { 33 if ne.Status() < entries[i].Status() { 34 entries[i] = ne 35 } 36 return entries 37 } 38 // append or insert 39 if i == len(entries)-1 { 40 entries = append(entries, ne) 41 entries[i], entries[i+1] = entries[i+1], entries[i] 42 return entries 43 } 44 entries = append(entries, &nilEntry) 45 copy(entries[i+1:], entries[i:]) // shift right 46 entries[i] = ne 47 return entries 48 } 49 50 entries = append(entries, ne) 51 return entries 52 } 53 54 //////////// 55 // LsoEnt // 56 //////////// 57 58 // The terms "cached" and "present" are interchangeable: 59 // "object is cached" and "is present" is actually the same thing 60 func (be *LsoEnt) IsPresent() bool { return be.Flags&apc.EntryIsCached != 0 } 61 func (be *LsoEnt) SetPresent() { be.Flags |= apc.EntryIsCached } 62 63 // see also: "latest-ver", QparamLatestVer, et al. 64 func (be *LsoEnt) SetVerChanged() { be.Flags |= apc.EntryVerChanged } 65 func (be *LsoEnt) IsVerChanged() bool { return be.Flags&apc.EntryVerChanged != 0 } 66 func (be *LsoEnt) SetVerRemoved() { be.Flags |= apc.EntryVerRemoved } 67 func (be *LsoEnt) IsVerRemoved() bool { return be.Flags&apc.EntryVerRemoved != 0 } 68 69 func (be *LsoEnt) IsStatusOK() bool { return be.Status() == 0 } 70 func (be *LsoEnt) Status() uint16 { return be.Flags & apc.EntryStatusMask } 71 func (be *LsoEnt) IsDir() bool { return be.Flags&apc.EntryIsDir != 0 } 72 func (be *LsoEnt) IsInsideArch() bool { return be.Flags&apc.EntryInArch != 0 } 73 func (be *LsoEnt) IsListedArch() bool { return be.Flags&apc.EntryIsArchive != 0 } 74 func (be *LsoEnt) String() string { return "{" + be.Name + "}" } 75 76 func (be *LsoEnt) less(oe *LsoEnt) bool { 77 if be.Name == oe.Name { 78 return be.Status() < oe.Status() 79 } 80 return be.Name < oe.Name 81 } 82 83 func (be *LsoEnt) CopyWithProps(propsSet cos.StrSet) (ne *LsoEnt) { 84 ne = &LsoEnt{Name: be.Name} 85 if propsSet.Contains(apc.GetPropsSize) { 86 ne.Size = be.Size 87 } 88 if propsSet.Contains(apc.GetPropsChecksum) { 89 ne.Checksum = be.Checksum 90 } 91 if propsSet.Contains(apc.GetPropsAtime) { 92 ne.Atime = be.Atime 93 } 94 if propsSet.Contains(apc.GetPropsVersion) { 95 ne.Version = be.Version 96 } 97 if propsSet.Contains(apc.GetPropsLocation) { 98 ne.Location = be.Location 99 } 100 if propsSet.Contains(apc.GetPropsCustom) { 101 ne.Custom = be.Custom 102 } 103 if propsSet.Contains(apc.GetPropsCopies) { 104 ne.Copies = be.Copies 105 } 106 return 107 } 108 109 // 110 // sorting and merging functions 111 // 112 113 func SortLso(entries LsoEntries) { sort.Slice(entries, entries.cmp) } 114 115 func DedupLso(entries LsoEntries, maxSize int) []*LsoEnt { 116 var j int 117 for _, obj := range entries { 118 if j > 0 && entries[j-1].Name == obj.Name { 119 continue 120 } 121 entries[j] = obj 122 j++ 123 124 if maxSize > 0 && j == maxSize { 125 break 126 } 127 } 128 clear(entries[j:]) 129 return entries[:j] 130 } 131 132 // MergeLso merges list-objects results received from targets. For the same 133 // object name (ie., the same object) the corresponding properties are merged. 134 // If maxSize is greater than 0, the resulting list is sorted and truncated. 135 func MergeLso(lists []*LsoRes, maxSize int) *LsoRes { 136 if len(lists) == 0 { 137 return &LsoRes{} 138 } 139 resList := lists[0] 140 token := resList.ContinuationToken 141 if len(lists) == 1 { 142 SortLso(resList.Entries) 143 resList.Entries = DedupLso(resList.Entries, maxSize) 144 resList.ContinuationToken = token 145 return resList 146 } 147 148 tmp := make(map[string]*LsoEnt, len(resList.Entries)*len(lists)) 149 for _, l := range lists { 150 resList.Flags |= l.Flags 151 if token < l.ContinuationToken { 152 token = l.ContinuationToken 153 } 154 for _, e := range l.Entries { 155 entry, exists := tmp[e.Name] 156 if !exists { 157 tmp[e.Name] = e 158 continue 159 } 160 // merge the props 161 if !entry.IsPresent() && e.IsPresent() { 162 e.Version = cos.Either(e.Version, entry.Version) 163 tmp[e.Name] = e 164 } else { 165 entry.Location = cos.Either(entry.Location, e.Location) 166 entry.Version = cos.Either(entry.Version, e.Version) 167 } 168 } 169 } 170 171 // grow cap 172 for cap(resList.Entries) < len(tmp) { 173 l := min(len(resList.Entries), len(tmp)-cap(resList.Entries)) 174 resList.Entries = append(resList.Entries, resList.Entries[:l]...) 175 } 176 177 // cleanup and sort 178 clear(resList.Entries) 179 resList.Entries = resList.Entries[:0] 180 resList.ContinuationToken = token 181 182 for _, entry := range tmp { 183 resList.Entries = appSorted(resList.Entries, entry) 184 } 185 if maxSize > 0 && len(resList.Entries) > maxSize { 186 clear(resList.Entries[maxSize:]) 187 resList.Entries = resList.Entries[:maxSize] 188 } 189 190 clear(tmp) 191 return resList 192 } 193 194 // Returns true if the continuation token >= object's name (in other words, the object is 195 // already listed and must be skipped). Note that string `>=` is lexicographic. 196 func TokenGreaterEQ(token, objName string) bool { return token >= objName } 197 198 // Directory has to either: 199 // - include (or match) prefix, or 200 // - be contained in prefix - motivation: don't SkipDir a/b when looking for a/b/c 201 // An alternative name for this function could be smth. like SameBranch() 202 func DirHasOrIsPrefix(dirPath, prefix string) bool { 203 return prefix == "" || (strings.HasPrefix(prefix, dirPath) || strings.HasPrefix(dirPath, prefix)) 204 } 205 206 func ObjHasPrefix(objName, prefix string) bool { 207 return prefix == "" || strings.HasPrefix(objName, prefix) 208 } 209 210 // no recursion (LsNoRecursion) helper function: 211 // - check the level of nesting 212 // - possibly, return virtual directory (to be included in LsoRes) and/or filepath.SkipDir 213 func HandleNoRecurs(prefix, relPath string) (*LsoEnt, error) { 214 debug.Assert(relPath != "") 215 if prefix == "" || prefix == cos.PathSeparator { 216 return &LsoEnt{Name: relPath, Flags: apc.EntryIsDir}, filepath.SkipDir 217 } 218 219 prefix = cos.TrimLastB(prefix, '/') 220 suffix := strings.TrimPrefix(relPath, prefix) 221 if suffix == relPath { 222 // wrong subtree (unlikely, given higher-level traversal logic) 223 return nil, filepath.SkipDir 224 } 225 226 if strings.Contains(suffix, cos.PathSeparator) { 227 // nesting-wise, we are deeper than allowed by the prefix 228 return nil, filepath.SkipDir 229 } 230 return &LsoEnt{Name: relPath, Flags: apc.EntryIsDir}, nil 231 }