diff --git a/low/caching_paged_reader.go b/low/caching_paged_reader.go new file mode 100644 index 0000000..9c94aea --- /dev/null +++ b/low/caching_paged_reader.go @@ -0,0 +1,72 @@ +package squashfslow + +import ( + "errors" + "math" +) + +var errOutOfBounds = errors.New("out of bounds") +var errUnexpectedOutOfBounds = errors.New("unexpected out of bounds") +var errNilCollection = errors.New("nil collection") + +// readPagedItems calls readBLockOrPartial the correct number of times to cache +// requestedItemIndex in currentItems, and then returns currentItems[requestedItemIndex]. +// Parameters: +// - requestedItemIndex: The index of the item to be retrieved. +// - blockSize: The number of items per block. +// - currentItems: A slice of already-read items to manage in-memory storage. Must not be nil. +// - readBlockOrPartial: A callback function that reads the next block. It takes the index of the block +// to be read, and the number of items to read. It is normally passed block size, but if the last +// block is incomplete, it will be passed the number of items in the last block. +// Returns: +// - the T at requestedItemIndex +// - a non-nil error and the zero value of T if an error was encountered. +func readPagedItems[T any]( + requestedItemIndex int, + blockSize int, + currentItems *[]T, + totalItems int, + readBlockOrPartial func(idxBlock, numItems int) ([]T, error), +) (T, error) { + var zero T // Zero value for the item type, used for default return in error cases. + if currentItems == nil { + return zero, errNilCollection + } + + if requestedItemIndex < 0 || requestedItemIndex >= totalItems { + return zero, errOutOfBounds + } + + if len(*currentItems) > requestedItemIndex { + return (*currentItems)[requestedItemIndex], nil + } + + // Calculate which block contains the requested item + blockNum := int(math.Ceil(float64(requestedItemIndex+1)/float64(blockSize))) - 1 + + // Calculate blocks to read + blocksRead := len(*currentItems) / blockSize + blocksToRead := blockNum - blocksRead + 1 + + // Read and append new blocks + for i := 0; i < blocksToRead; i++ { + startBlock := blocksRead + i + itemsLeft := totalItems - len(*currentItems) + itemsToRead := blockSize + if itemsToRead > itemsLeft { + itemsToRead = itemsLeft + } + items, err := readBlockOrPartial(startBlock, itemsToRead) + if err != nil { + return zero, err + } + *currentItems = append(*currentItems, items...) + } + + // Ensure the slice contains the requested index after reading + if len(*currentItems) <= requestedItemIndex { + return zero, errUnexpectedOutOfBounds + } + + return (*currentItems)[requestedItemIndex], nil +} diff --git a/low/caching_paged_reader_test.go b/low/caching_paged_reader_test.go index 203f6f8..1ea6b3b 100644 --- a/low/caching_paged_reader_test.go +++ b/low/caching_paged_reader_test.go @@ -124,4 +124,4 @@ package squashfslow // assertEqual(t, 512, item) // assertLength(t, 612, currentItems) // }) -// } +// } \ No newline at end of file