// GoToSocial // Copyright (C) GoToSocial Authors admin@gotosocial.org // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package paging import "golang.org/x/exp/slices" // Pager provides a means of paging serialized IDs, // using the terminology of our API endpoint queries. type Pager struct { // SinceID will limit the returned // page of IDs to contain newer than // since ID (excluding it). Result // will be returned DESCENDING. SinceID string // MinID will limit the returned // page of IDs to contain newer than // min ID (excluding it). Result // will be returned ASCENDING. MinID string // MaxID will limit the returned // page of IDs to contain older // than (excluding) this max ID. MaxID string // Limit will limit the returned // page of IDs to at most 'limit'. Limit int } // Page will page the given slice of GoToSocial IDs according // to the receiving Pager's SinceID, MinID, MaxID and Limits. // NOTE THE INPUT SLICE MUST BE SORTED IN ASCENDING ORDER // (I.E. OLDEST ITEMS AT LOWEST INDICES, NEWER AT HIGHER). func (p *Pager) PageAsc(ids []string) []string { if p == nil { // no paging. return ids } var asc bool if p.SinceID != "" { // If a sinceID is given, we // page down i.e. descending. asc = false for i := 0; i < len(ids); i++ { if ids[i] == p.SinceID { // Hit the boundary. // Reslice to be: // "from here" ids = ids[i+1:] break } } } else if p.MinID != "" { // We only support minID if // no sinceID is provided. // // If a minID is given, we // page up, i.e. ascending. asc = true for i := 0; i < len(ids); i++ { if ids[i] == p.MinID { // Hit the boundary. // Reslice to be: // "from here" ids = ids[i+1:] break } } } if p.MaxID != "" { for i := 0; i < len(ids); i++ { if ids[i] == p.MaxID { // Hit the boundary. // Reslice to be: // "up to here" ids = ids[:i] break } } } if !asc && len(ids) > 1 { var ( // Start at front. i = 0 // Start at back. j = len(ids) - 1 ) // Clone input IDs before // we perform modifications. ids = slices.Clone(ids) for i < j { // Swap i,j index values in slice. ids[i], ids[j] = ids[j], ids[i] // incr + decr, // looping until // they meet in // the middle. i++ j-- } } if p.Limit > 0 && p.Limit < len(ids) { // Reslice IDs to given limit. ids = ids[:p.Limit] } return ids } // Page will page the given slice of GoToSocial IDs according // to the receiving Pager's SinceID, MinID, MaxID and Limits. // NOTE THE INPUT SLICE MUST BE SORTED IN ASCENDING ORDER. // (I.E. NEWEST ITEMS AT LOWEST INDICES, OLDER AT HIGHER). func (p *Pager) PageDesc(ids []string) []string { if p == nil { // no paging. return ids } var asc bool if p.MaxID != "" { for i := 0; i < len(ids); i++ { if ids[i] == p.MaxID { // Hit the boundary. // Reslice to be: // "from here" ids = ids[i+1:] break } } } if p.SinceID != "" { // If a sinceID is given, we // page down i.e. descending. asc = false for i := 0; i < len(ids); i++ { if ids[i] == p.SinceID { // Hit the boundary. // Reslice to be: // "up to here" ids = ids[:i] break } } } else if p.MinID != "" { // We only support minID if // no sinceID is provided. // // If a minID is given, we // page up, i.e. ascending. asc = true for i := 0; i < len(ids); i++ { if ids[i] == p.MinID { // Hit the boundary. // Reslice to be: // "up to here" ids = ids[:i] break } } } if asc && len(ids) > 1 { var ( // Start at front. i = 0 // Start at back. j = len(ids) - 1 ) // Clone input IDs before // we perform modifications. ids = slices.Clone(ids) for i < j { // Swap i,j index values in slice. ids[i], ids[j] = ids[j], ids[i] // incr + decr, // looping until // they meet in // the middle. i++ j-- } } if p.Limit > 0 && p.Limit < len(ids) { // Reslice IDs to given limit. ids = ids[:p.Limit] } return ids }