123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- package pub
- import (
- "errors"
- "fmt"
- "golang.org/x/exp/slices"
- "servitor/client"
- "servitor/object"
- "net/url"
- "sync"
- )
- type Collection struct {
- kind string
- id *url.URL
- elements []any
- elementsErr error
- next any
- nextErr error
- size uint64
- sizeErr error
- }
- func NewCollection(input any, source *url.URL) (*Collection, error) {
- o, id, err := client.FetchUnknown(input, source)
- if err != nil {
- return nil, err
- }
- return NewCollectionFromObject(o, id)
- }
- func NewCollectionFromObject(o object.Object, id *url.URL) (*Collection, error) {
- c := &Collection{}
- c.id = id
- var err error
- if c.kind, err = o.GetString("type"); err != nil {
- return nil, err
- }
- if !slices.Contains([]string{
- "Collection", "OrderedCollection", "CollectionPage", "OrderedCollectionPage",
- }, c.kind) {
- return nil, fmt.Errorf("%w: %s is not a Collection", ErrWrongType, c.kind)
- }
- if c.kind == "Collection" || c.kind == "CollectionPage" {
- c.elements, c.elementsErr = o.GetList("items")
- } else {
- c.elements, c.elementsErr = o.GetList("orderedItems")
- }
- if c.kind == "Collection" || c.kind == "OrderedCollection" {
- c.next, c.nextErr = o.GetAny("first")
- } else {
- c.next, c.nextErr = o.GetAny("next")
- }
- c.size, c.sizeErr = o.GetNumber("totalItems")
- return c, nil
- }
- func (c *Collection) Size() (uint64, error) {
- return c.size, c.sizeErr
- }
- func (c *Collection) Harvest(amount uint, startingPoint uint) ([]Tangible, Container, uint) {
- return c.harvestWithEmptyCount(amount, startingPoint, 0)
- }
- func (c *Collection) harvestWithEmptyCount(amount uint, startingPoint uint, emptyCount int) ([]Tangible, Container, uint) {
- if c == nil {
- panic("can't harvest nil collection")
- }
- if c.elementsErr != nil && !errors.Is(c.elementsErr, object.ErrKeyNotPresent) {
- return []Tangible{NewFailure(c.elementsErr)}, nil, 0
- }
- var length uint
- if errors.Is(c.elementsErr, object.ErrKeyNotPresent) {
- length = 0
- } else {
- length = uint(len(c.elements))
- }
- if length == 0 {
- emptyCount += 1
- }
-
- if emptyCount > 3 {
- return []Tangible{NewFailure(errors.New("refusing to read the next collection because >3 consecutive empty collections have been encountered"))}, nil, 0
- }
- var amountFromThisPage uint
- if startingPoint >= length {
- amountFromThisPage = 0
- } else if length > amount+startingPoint {
- amountFromThisPage = amount
- } else {
- amountFromThisPage = length - startingPoint
- }
- fromThisPage := make([]Tangible, amountFromThisPage)
- var fromLaterPages []Tangible
- var nextCollection Container
- var nextStartingPoint uint
- var wg sync.WaitGroup
- for i := uint(0); i < amountFromThisPage; i++ {
- i := i
- wg.Add(1)
- go func() {
- fromThisPage[i] = NewTangible(c.elements[i+startingPoint], c.id)
- wg.Done()
- }()
- }
- wg.Add(1)
- go func() {
- if length > amount+startingPoint {
- fromLaterPages, nextCollection, nextStartingPoint = []Tangible{}, c, amount+startingPoint
- } else if errors.Is(c.nextErr, object.ErrKeyNotPresent) {
- fromLaterPages, nextCollection, nextStartingPoint = []Tangible{}, nil, 0
- } else if c.nextErr != nil {
- fromLaterPages, nextCollection, nextStartingPoint = []Tangible{NewFailure(c.nextErr)}, nil, 0
- } else if next, err := NewCollection(c.next, c.id); err != nil {
- fromLaterPages, nextCollection, nextStartingPoint = []Tangible{NewFailure(err)}, nil, 0
- } else {
- fromLaterPages, nextCollection, nextStartingPoint = next.harvestWithEmptyCount(amount-amountFromThisPage, 0, emptyCount)
- }
- wg.Done()
- }()
- wg.Wait()
- return append(fromThisPage, fromLaterPages...), nextCollection, nextStartingPoint
- }
|