|
| 1 | +package cache |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | +) |
| 7 | + |
| 8 | +var ErrRangeNotSatisfiable = errors.New("requested byte range is not satisfiable") |
| 9 | + |
| 10 | +type byteRangeKind uint8 |
| 11 | + |
| 12 | +const ( |
| 13 | + closedByteRange byteRangeKind = iota |
| 14 | + openEndedByteRange |
| 15 | + suffixByteRange |
| 16 | +) |
| 17 | + |
| 18 | +// ByteRange is a parsed, representation-independent byte range. Constructors |
| 19 | +// keep HTTP syntax out of the cache package while preserving the distinction |
| 20 | +// between closed, open-ended, and suffix ranges until the total size is known. |
| 21 | +type ByteRange struct { |
| 22 | + kind byteRangeKind |
| 23 | + first int64 |
| 24 | + last int64 |
| 25 | + suffix int64 |
| 26 | +} |
| 27 | + |
| 28 | +func ClosedByteRange(first, last int64) ByteRange { |
| 29 | + return ByteRange{kind: closedByteRange, first: first, last: last} |
| 30 | +} |
| 31 | + |
| 32 | +func OpenEndedByteRange(first int64) ByteRange { |
| 33 | + return ByteRange{kind: openEndedByteRange, first: first} |
| 34 | +} |
| 35 | + |
| 36 | +func SuffixByteRange(length int64) ByteRange { |
| 37 | + return ByteRange{kind: suffixByteRange, suffix: length} |
| 38 | +} |
| 39 | + |
| 40 | +type DownloadRange struct { |
| 41 | + Offset int64 |
| 42 | + Count int64 |
| 43 | +} |
| 44 | + |
| 45 | +type RangeNotSatisfiableError struct { |
| 46 | + SizeBytes int64 |
| 47 | +} |
| 48 | + |
| 49 | +func (e *RangeNotSatisfiableError) Error() string { |
| 50 | + return fmt.Sprintf("%s: size %d", ErrRangeNotSatisfiable, e.SizeBytes) |
| 51 | +} |
| 52 | + |
| 53 | +func (e *RangeNotSatisfiableError) Unwrap() error { |
| 54 | + return ErrRangeNotSatisfiable |
| 55 | +} |
| 56 | + |
| 57 | +func resolveByteRanges(ranges []ByteRange, size int64) (DownloadRange, error) { |
| 58 | + if size < 0 { |
| 59 | + return DownloadRange{}, fmt.Errorf("invalid cache size %d", size) |
| 60 | + } |
| 61 | + for _, candidate := range ranges { |
| 62 | + switch candidate.kind { |
| 63 | + case closedByteRange: |
| 64 | + if candidate.first < 0 || candidate.last < candidate.first { |
| 65 | + return DownloadRange{}, fmt.Errorf("invalid closed byte range %d-%d", candidate.first, candidate.last) |
| 66 | + } |
| 67 | + if candidate.first >= size { |
| 68 | + continue |
| 69 | + } |
| 70 | + last := min(candidate.last, size-1) |
| 71 | + return DownloadRange{Offset: candidate.first, Count: last - candidate.first + 1}, nil |
| 72 | + case openEndedByteRange: |
| 73 | + if candidate.first < 0 { |
| 74 | + return DownloadRange{}, fmt.Errorf("invalid open-ended byte range %d-", candidate.first) |
| 75 | + } |
| 76 | + if candidate.first >= size { |
| 77 | + continue |
| 78 | + } |
| 79 | + return DownloadRange{Offset: candidate.first, Count: size - candidate.first}, nil |
| 80 | + case suffixByteRange: |
| 81 | + if candidate.suffix < 0 { |
| 82 | + return DownloadRange{}, fmt.Errorf("invalid suffix byte range -%d", candidate.suffix) |
| 83 | + } |
| 84 | + if candidate.suffix == 0 || size == 0 { |
| 85 | + continue |
| 86 | + } |
| 87 | + count := min(candidate.suffix, size) |
| 88 | + return DownloadRange{Offset: size - count, Count: count}, nil |
| 89 | + default: |
| 90 | + return DownloadRange{}, fmt.Errorf("invalid byte range kind %d", candidate.kind) |
| 91 | + } |
| 92 | + } |
| 93 | + return DownloadRange{}, &RangeNotSatisfiableError{SizeBytes: size} |
| 94 | +} |
0 commit comments