55//
66
77import Foundation
8+ import ReadiumInternal
89
910/// Wraps an existing `Resource` and buffers its content.
1011///
@@ -18,15 +19,23 @@ import Foundation
1819/// apparent when reading forward and consecutively – e.g. when downloading the
1920/// resource by chunks. The buffer is ignored when reading backward or far
2021/// ahead.
21- public actor BufferingResource : Resource {
22+ public actor BufferingResource : Resource , Loggable {
2223 private nonisolated let resource : Resource
23- private let bufferSize : UInt64
24+
25+ /// The buffer containing the current bytes read from the wrapped
26+ /// `Resource`, with the range it covers.
27+ private var buffer : Buffer
2428
2529 /// - Parameter bufferSize: Size of the buffer chunks to read.
26- public init ( resource: Resource , bufferSize: UInt64 = 8192 ) {
27- assert ( bufferSize > 0 )
30+ public init ( resource: Resource , bufferSize: Int = 8192 ) {
31+ precondition ( bufferSize > 0 )
2832 self . resource = resource
29- self . bufferSize = bufferSize
33+ buffer = Buffer ( maxSize: bufferSize)
34+ }
35+
36+ @available ( * , deprecated, message: " Use an Int bufferSize instead. " )
37+ public init ( resource: Resource , bufferSize: UInt64 ) {
38+ self . init ( resource: resource, bufferSize: Int ( bufferSize) )
3039 }
3140
3241 public nonisolated var sourceURL : AbsoluteURL ? { resource. sourceURL }
@@ -39,9 +48,6 @@ public actor BufferingResource: Resource {
3948 resource. close ( )
4049 }
4150
42- /// The buffer containing the current bytes read from the wrapped `Resource`, with the range it covers.
43- private var buffer : ( data: Data , range: Range < UInt64 > ) ? = nil
44-
4551 private var cachedLength : ReadResult < UInt64 ? > ?
4652
4753 public func estimatedLength( ) async -> ReadResult < UInt64 ? > {
@@ -69,74 +75,78 @@ public actor BufferingResource: Resource {
6975 consume ( Data ( ) )
7076 return . success( ( ) )
7177 }
78+ if let data = buffer. get ( range: requestedRange) {
79+ log ( . trace, " Used buffer for \( requestedRange) ( \( requestedRange. count) bytes) " )
80+ consume ( data)
81+ return . success( ( ) )
82+ }
7283
73- // Round up the range to be read to the next `bufferSize`, because we
74- // will buffer the excess.
75- let readUpperBound = min ( requestedRange. upperBound. ceilMultiple ( of: bufferSize) , length)
76- var readRange : Range < UInt64 > = requestedRange. lowerBound ..< readUpperBound
84+ // Calculate the readRange to cover at least buffer.maxSize bytes.
85+ // Adjust the start if near the end of the resource.
86+ var readStart = requestedRange. lowerBound
87+ var readEnd = requestedRange. upperBound
88+ let missingBytesToMatchBufferSize = buffer. maxSize - requestedRange. count
89+ if missingBytesToMatchBufferSize > 0 {
90+ readEnd = min ( readEnd + UInt64( missingBytesToMatchBufferSize) , length)
91+ }
92+ if readEnd - readStart < buffer. maxSize {
93+ readStart = UInt64 ( max ( 0 , Int ( readEnd) - buffer. maxSize) )
94+ }
95+ let readRange = readStart ..< readEnd
96+ log ( . trace, " Requested \( requestedRange) ( \( requestedRange. count) bytes), will read range \( readRange) ( \( readRange. count) bytes) of resource with length \( length) " )
97+
98+ // Fallback on reading the requested range from the original resource.
99+ return await resource. read ( range: readRange)
100+ . flatMap { data in
101+ buffer. set ( data, at: readRange. lowerBound)
102+
103+ guard let data = data [ requestedRange, offsetBy: readRange. lowerBound] else {
104+ return . failure( . decoding( " Cannot extract the requested range from the read range " ) )
105+ }
77106
78- // Attempt to serve parts or all of the request using the buffer.
79- if let buffer = buffer {
80- // Everything already buffered?
81- if buffer. range. contains ( requestedRange) {
82- let data = extractRange ( requestedRange, in: buffer. data, startingAt: buffer. range. lowerBound)
83107 consume ( data)
84108 return . success( ( ) )
85-
86- // Beginning of requested data is buffered?
87- } else if buffer. range. contains ( requestedRange. lowerBound) {
88- var data = buffer. data
89- let bufferStart = buffer. range. lowerBound
90- readRange = buffer. range. upperBound ..< readRange. upperBound
91-
92- return await resource. read ( range: readRange)
93- . map { readData in
94- data += readData
95- // Shift the current buffer to the tail of the read data.
96- saveBuffer ( from: data, range: readRange)
97- consume ( extractRange ( requestedRange, in: data, startingAt: bufferStart) )
98- return ( )
99- }
100109 }
110+ }
111+
112+ private struct Buffer {
113+ let maxSize : Int
114+ private var data : Data = . init( )
115+ private var startOffset : UInt64 = 0
116+
117+ init ( maxSize: Int ) {
118+ self . maxSize = maxSize
101119 }
102120
103- // Fallback on reading the requested range from the original resource.
104- return await resource. read ( range: readRange)
105- . map { data in
106- saveBuffer ( from: data, range: readRange)
107- consume ( data [ 0 ..< requestedRange. count] )
108- return ( )
121+ mutating func set( _ data: Data , at offset: UInt64 ) {
122+ var data = data
123+ var offset = offset
124+
125+ // Truncates the beginning of the data to maxSize.
126+ if data. count > maxSize {
127+ offset += UInt64 ( data. count - maxSize)
128+ data = Data ( data. suffix ( maxSize) )
109129 }
110- }
111130
112- /// Keeps the last chunk of the given `data` as the buffer for next reads.
113- ///
114- /// - Parameters:
115- /// - data: Data read from the original resource.
116- /// - range: Range of the read data in the resource.
117- private func saveBuffer( from data: Data , range: Range < UInt64 > ) {
118- let lastChunk = Data ( data. suffix ( Int ( bufferSize) ) )
119- buffer = (
120- data: lastChunk,
121- range: ( range. upperBound - UInt64( lastChunk. count) ) ..< range. upperBound
122- )
123- }
131+ self . data = data
132+ startOffset = offset
133+ }
124134
125- /// Reads a sub-range of the given `data` after shifting the given absolute (to the resource) ranges to be relative
126- /// to `data`.
127- private func extractRange( _ requestedRange: Range < UInt64 > , in data: Data , startingAt dataStartOffset: UInt64 ) -> Data {
128- let lower = ( requestedRange. lowerBound - dataStartOffset)
129- let upper = lower + ( requestedRange. upperBound - requestedRange. lowerBound)
130- assert ( lower >= 0 )
131- assert ( upper <= data. count)
132- return data [ lower ..< upper]
135+ func get( range: Range < UInt64 > ) -> Data ? {
136+ data [ range, offsetBy: startOffset]
137+ }
133138 }
134139}
135140
136141public extension Resource {
137142 /// Wraps this resource in a `BufferingResource` to improve reading
138143 /// performances.
139- func buffered( size: UInt64 = 8192 ) -> BufferingResource {
144+ func buffered( size: Int = 8192 ) -> BufferingResource {
140145 BufferingResource ( resource: self , bufferSize: size)
141146 }
147+
148+ @available ( * , deprecated, message: " Use an Int bufferSize instead. " )
149+ func buffered( size: UInt64 ) -> BufferingResource {
150+ buffered ( size: Int ( size) )
151+ }
142152}
0 commit comments