zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/pagination/image_pagination.go (about)

     1  package pagination
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"time"
     7  
     8  	zerr "zotregistry.dev/zot/errors"
     9  	zcommon "zotregistry.dev/zot/pkg/common"
    10  	gql_gen "zotregistry.dev/zot/pkg/extensions/search/gql_generated"
    11  )
    12  
    13  type ImageSummariesPageFinder struct {
    14  	limit      int
    15  	offset     int
    16  	sortBy     SortCriteria
    17  	pageBuffer []*gql_gen.ImageSummary
    18  }
    19  
    20  func NewImgSumPageFinder(limit, offset int, sortBy SortCriteria) (*ImageSummariesPageFinder, error) {
    21  	if sortBy == "" {
    22  		sortBy = AlphabeticAsc
    23  	}
    24  
    25  	if limit < 0 {
    26  		return nil, zerr.ErrLimitIsNegative
    27  	}
    28  
    29  	if offset < 0 {
    30  		return nil, zerr.ErrOffsetIsNegative
    31  	}
    32  
    33  	if _, found := ImgSumSortFuncs()[sortBy]; !found {
    34  		return nil, fmt.Errorf("sorting repos by '%s' is not supported %w",
    35  			sortBy, zerr.ErrSortCriteriaNotSupported)
    36  	}
    37  
    38  	return &ImageSummariesPageFinder{
    39  		limit:      limit,
    40  		offset:     offset,
    41  		sortBy:     sortBy,
    42  		pageBuffer: []*gql_gen.ImageSummary{},
    43  	}, nil
    44  }
    45  
    46  func (pf *ImageSummariesPageFinder) Add(imgSum *gql_gen.ImageSummary) {
    47  	pf.pageBuffer = append(pf.pageBuffer, imgSum)
    48  }
    49  
    50  func (pf *ImageSummariesPageFinder) Page() ([]*gql_gen.ImageSummary, zcommon.PageInfo) {
    51  	if len(pf.pageBuffer) == 0 {
    52  		return []*gql_gen.ImageSummary{}, zcommon.PageInfo{}
    53  	}
    54  
    55  	pageInfo := zcommon.PageInfo{}
    56  
    57  	sort.Slice(pf.pageBuffer, ImgSumSortFuncs()[pf.sortBy](pf.pageBuffer))
    58  
    59  	// the offset and limit are calculated in terms of repos counted
    60  	start := pf.offset
    61  	end := pf.offset + pf.limit
    62  
    63  	// we'll return an empty array when the offset is greater than the number of elements
    64  	if start >= len(pf.pageBuffer) {
    65  		start = len(pf.pageBuffer)
    66  		end = start
    67  	}
    68  
    69  	if end >= len(pf.pageBuffer) {
    70  		end = len(pf.pageBuffer)
    71  	}
    72  
    73  	page := pf.pageBuffer[start:end]
    74  
    75  	pageInfo.ItemCount = len(page)
    76  
    77  	if start == 0 && end == 0 {
    78  		page = pf.pageBuffer
    79  		pageInfo.ItemCount = len(page)
    80  	}
    81  
    82  	pageInfo.TotalCount = len(pf.pageBuffer)
    83  
    84  	return page, pageInfo
    85  }
    86  
    87  func ImgSumSortFuncs() map[SortCriteria]func(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
    88  	return map[SortCriteria]func(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool{
    89  		AlphabeticAsc: ImgSortByAlphabeticAsc,
    90  		AlphabeticDsc: ImgSortByAlphabeticDsc,
    91  		UpdateTime:    ImgSortByUpdateTime,
    92  		Relevance:     ImgSortByRelevance,
    93  		Downloads:     ImgSortByDownloads,
    94  	}
    95  }
    96  
    97  func ImgSortByAlphabeticAsc(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
    98  	return func(i, j int) bool { //nolint: varnamelen
    99  		if *pageBuffer[i].RepoName < *pageBuffer[j].RepoName {
   100  			return true
   101  		}
   102  
   103  		if *pageBuffer[i].RepoName == *pageBuffer[j].RepoName {
   104  			return *pageBuffer[i].Tag < *pageBuffer[j].Tag
   105  		}
   106  
   107  		return false
   108  	}
   109  }
   110  
   111  func ImgSortByAlphabeticDsc(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
   112  	return func(i, j int) bool { //nolint: varnamelen
   113  		if *pageBuffer[i].RepoName > *pageBuffer[j].RepoName {
   114  			return true
   115  		}
   116  
   117  		if *pageBuffer[i].RepoName == *pageBuffer[j].RepoName {
   118  			return *pageBuffer[i].Tag > *pageBuffer[j].Tag
   119  		}
   120  
   121  		return false
   122  	}
   123  }
   124  
   125  func ImgSortByRelevance(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
   126  	return func(i, j int) bool { //nolint: varnamelen
   127  		if *pageBuffer[i].RepoName < *pageBuffer[j].RepoName {
   128  			return true
   129  		}
   130  
   131  		if *pageBuffer[i].RepoName == *pageBuffer[j].RepoName {
   132  			return *pageBuffer[i].Tag < *pageBuffer[j].Tag
   133  		}
   134  
   135  		return false
   136  	}
   137  }
   138  
   139  // SortByUpdateTime sorting descending by time.
   140  func ImgSortByUpdateTime(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
   141  	repos2LastUpdated := map[string]time.Time{}
   142  
   143  	for _, img := range pageBuffer {
   144  		lastUpdated, ok := repos2LastUpdated[*img.RepoName]
   145  
   146  		if !ok || lastUpdated.Before(*img.LastUpdated) {
   147  			repos2LastUpdated[*img.RepoName] = *img.LastUpdated
   148  		}
   149  	}
   150  
   151  	return func(i, j int) bool {
   152  		iRepoTime, jRepoTime := repos2LastUpdated[*pageBuffer[i].RepoName], repos2LastUpdated[*pageBuffer[j].RepoName]
   153  
   154  		return (iRepoTime.After(jRepoTime) || iRepoTime.Equal(jRepoTime)) &&
   155  			pageBuffer[i].LastUpdated.After(*pageBuffer[j].LastUpdated)
   156  	}
   157  }
   158  
   159  // SortByDownloads returns a comparison function for descendant sorting by downloads.
   160  func ImgSortByDownloads(pageBuffer []*gql_gen.ImageSummary) func(i, j int) bool {
   161  	return func(i, j int) bool {
   162  		return *pageBuffer[i].DownloadCount > *pageBuffer[j].DownloadCount
   163  	}
   164  }