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  }