Skip to content

Commit 89b33e4

Browse files
committed
Merge branch 'dev.better_stream_control' into develop
2 parents 38d28cb + 0e3d079 commit 89b33e4

File tree

7 files changed

+264
-66
lines changed

7 files changed

+264
-66
lines changed

Sources/StreamReader/Core/StreamReader.swift

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ import Foundation
1212

1313
public protocol StreamReader : class {
1414

15+
/**
16+
Whether _the underlying stream_’s `EOF` has been reached, either because of
17+
`readSizeLimit` constraint, or because the actual end of the stream has been
18+
reached. */
19+
var streamHasReachedEOF: Bool {get}
20+
/**
21+
The number of bytes read _from the underlying stream_. Can be greater than
22+
`readSizeLimit` iif `readSizeLimit` has been changed to a lower value than
23+
`currentStreamReadPosition`. */
24+
var currentStreamReadPosition: Int {get}
25+
26+
/**
27+
Force set `streamHasReachedEOF` to `false`. Call this before calling the read
28+
methods to force a read when EOF has been reached. */
29+
func clearStreamHasReachedEOF()
30+
1531
/**
1632
The index of the first byte returned from the stream at the next read, where
1733
0 is the first byte of the stream.
@@ -21,8 +37,8 @@ public protocol StreamReader : class {
2137
var currentReadPosition: Int {get}
2238

2339
/**
24-
The maximum total number of bytes allowed to be read _from the underlying
25-
stream_, and the maximum value possible for `currentReadPosition`.
40+
The maximum value possible for `currentReadPosition` **and** the maximum
41+
total number of bytes allowed to be read _from the underlying stream_.
2642

2743
If set to `nil`, there are no limits.
2844

@@ -36,8 +52,9 @@ public protocol StreamReader : class {
3652
possible for more than `readSizeLimit` to _have been read_ from the stream,
3753
or for `currentReadPosition` to be greater than `readSizeLimit`.
3854

39-
If this `currentReadPosition` is greater than `readSizeLimit`, the
40-
`.notEnoughData` error would be thrown if trying to read more data.
55+
When `currentReadPosition` is greater than `readSizeLimit`, trying to read
56+
more data will throw the `.notEnoughData` error (or return an empty data if
57+
reading less than asked is allowed).
4158

4259
This property can be useful because it is usually not possible to add data
4360
back to a stream once it has been read from. If you know your stream can have
@@ -54,6 +71,9 @@ public protocol StreamReader : class {
5471
You should not assume the memory you get is bound to a particular type. Use
5572
the memory rebinding methods if you need them.
5673

74+
Reading a data of size 0 must never fail and always return an empty Data
75+
object.
76+
5777
- Important: For the memory to stay valid and immutable in the handler, do
5878
**NOT** do any stream operation inside the handler.
5979

@@ -151,13 +171,21 @@ public extension StreamReader {
151171
public extension StreamReader {
152172

153173
/**
154-
Has EOF been reached?
174+
Returns `true` if the underlying stream has reached EOF _and_ the current
175+
stream read position is the same (or lower than) the current read position. */
176+
var hasReachedEOF: Bool {
177+
return streamHasReachedEOF && currentReadPosition >= currentStreamReadPosition
178+
}
155179

156-
- Important: If the read size limit has not been reached, one byte might be
157-
read from the underlying stream when using this method though the
158-
`currentReadPosition` of the stream reader will never change. */
159-
func hasReachedEOF() throws -> Bool {
160-
return try peekData(size: 1, allowReadingLess: true).isEmpty
180+
/**
181+
Actively check for `EOF` in the stream.
182+
183+
- Important: If the read size limit has not been reached and EOF has not been
184+
definitely reached, one byte might be read from the underlying stream when
185+
using this method, though the `currentReadPosition` of the stream reader will
186+
never change. */
187+
func checkForEOF() throws -> Bool {
188+
return try hasReachedEOF || peekData(size: 1, allowReadingLess: true).isEmpty
161189
}
162190

163191
func readData(size: Int, allowReadingLess: Bool = false) throws -> Data {

Sources/StreamReader/Core/StreamReaderError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ public enum StreamReaderError : Error {
2323
`readData(upToDelimiters:...)` method. */
2424
case delimitersNotFound
2525

26+
/**
27+
When a read operation is done in a `GenericStreamReader` that would require
28+
reading from the underlying stream (not enough data in the buffer), but the
29+
`underlyingStreamReadSizeLimit` is 0, this error is thrown. */
30+
case streamReadForbidden
31+
2632
/** An error occurred reading the stream. */
2733
case streamReadError(streamError: Error?)
2834

Sources/StreamReader/Implementations/DataReader.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,38 @@ public final class DataReader : StreamReader {
1313

1414
public let sourceData: Data
1515
public let sourceDataSize: Int
16+
1617
public private(set) var currentReadPosition = 0
1718

19+
/**
20+
The whole data is in memory: the “underlying stream” (the `sourceData`) is
21+
_always_ read to `EOF`. */
22+
public let streamHasReachedEOF = true
23+
/** Always the size of the source data. */
24+
public let currentStreamReadPosition: Int
25+
1826
public var readSizeLimit: Int?
1927

2028
public init(data: Data, readSizeLimit limit: Int? = nil) {
2129
sourceData = data
2230
sourceDataSize = sourceData.count
31+
currentStreamReadPosition = sourceDataSize
2332
readSizeLimit = limit
2433
}
2534

35+
public func clearStreamHasReachedEOF() {
36+
/* nop: the data reader has always reached EOF because all of the data is
37+
 * in memory at all time. */
38+
}
39+
2640
public func readData<T>(size: Int, allowReadingLess: Bool, updateReadPosition: Bool, _ handler: (UnsafeRawBufferPointer) throws -> T) throws -> T {
2741
assert(size >= 0, "Cannot read a negative number of bytes!")
2842
assert(currentReadPosition <= sourceDataSize, "INTERNAL ERROR")
2943

44+
guard size > 0 else {
45+
return try handler(UnsafeRawBufferPointer(start: nil, count: 0))
46+
}
47+
3048
if !allowReadingLess {
3149
if let maxRead = readSizeLimit {
3250
guard currentReadPosition + size <= maxRead else {throw StreamReaderError.notEnoughData(wouldReachReadSizeLimit: true)}

Sources/StreamReader/Implementations/GenericStreamReader.swift

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,30 @@ public final class GenericStreamReader : StreamReader {
3737
reading up to given delimiters and there is no space left in the buffer. */
3838
public var bufferSizeIncrement: Int
3939

40+
/**
41+
Whether `EOF` has been reached, either because of `readSizeLimit` constraint,
42+
or because end of underlying stream has been reached. See doc of
43+
`readSizeLimit` for a few more info. */
44+
public private(set) var streamHasReachedEOF = false
45+
public private(set) var currentStreamReadPosition: Int
4046
public private(set) var currentReadPosition = 0
4147

42-
public var readSizeLimit: Int?
48+
/**
49+
The maximum number of bytes that can be returned by the read methods (when
50+
updating read position), and also the number of bytes that can be read from
51+
the underlying stream.
52+
53+
Changing this to a greater value will force `streamHasReachedEOF` to `false`,
54+
but next read might reach `EOF` directly regardless.
55+
56+
Changing this to a lower value will not change `streamHasReachedEOF` at all. */
57+
public var readSizeLimit: Int? {
58+
didSet {
59+
if readSizeLimit ?? Int.max > oldValue ?? Int.max {
60+
streamHasReachedEOF = false
61+
}
62+
}
63+
}
4364
/**
4465
The max size to read from the stream with a single read. Set to `nil` for no
4566
max.
@@ -66,12 +87,15 @@ public final class GenericStreamReader : StreamReader {
6687
- Parameter readSizeLimit: The maximum number of bytes allowed to be read
6788
from the stream. Cannot be negative.
6889
- Parameter underlyingStreamReadSizeLimit: The max size to read from the
69-
stream with a single read. Cannot be negative or 0. */
90+
stream with a single read. Cannot be negative. If 0, the reader is
91+
effectively forbidden from reading the underlying stream. If a read operation
92+
is done that would require reading from the stream (not enough data in the
93+
buffer), the `streamReadForbidden` error is thrown. */
7094
public init(stream: GenericReadStream, bufferSize size: Int, bufferSizeIncrement sizeIncrement: Int, readSizeLimit limit: Int? = nil, underlyingStreamReadSizeLimit streamReadSizeLimit: Int? = nil) {
7195
assert(size > 0)
7296
assert(sizeIncrement > 0)
7397
assert(limit == nil || limit! >= 0)
74-
assert(streamReadSizeLimit == nil || streamReadSizeLimit! > 0)
98+
assert(streamReadSizeLimit == nil || streamReadSizeLimit! >= 0)
7599

76100
sourceStream = stream
77101

@@ -83,7 +107,7 @@ public final class GenericStreamReader : StreamReader {
83107
bufferStartPos = 0
84108
bufferValidLength = 0
85109

86-
totalReadBytesCount = 0
110+
currentStreamReadPosition = 0
87111

88112
readSizeLimit = limit
89113
underlyingStreamReadSizeLimit = streamReadSizeLimit
@@ -94,14 +118,43 @@ public final class GenericStreamReader : StreamReader {
94118
bufferSize = 0
95119
}
96120

121+
public func clearStreamHasReachedEOF() {
122+
streamHasReachedEOF = false
123+
}
124+
125+
/**
126+
Reads `size` bytes from the underlying stream into the internal buffer and
127+
returns the number of bytes read.
128+
129+
The `readSizeLimit` and `underlyingStreamReadSizeLimit` variables are
130+
respected when using this method.
131+
132+
- Important: If the buffer is big enough, might read _more_ bytes than asked.
133+
Can also read less (if the end of the stream is reached, or if only one read
134+
is allowed and read returned less than asked).
135+
136+
- Parameter size: The number of bytes you want the buffer to be filled with
137+
from the stream.
138+
- Parameter allowMoreThanOneRead: If `true`, the method will read from the
139+
stream until the asked size is read or the end of stream is reached.
140+
- Returns: The number of bytes acutally read from the stream. */
141+
public func readStreamInBuffer(size: Int, allowMoreThanOneRead: Bool = false) throws -> Int {
142+
let previousBufferValidLenth = bufferValidLength
143+
_ = try readDataNoCurrentPosIncrement(size: (bufferValidLength + size), readContraints: allowMoreThanOneRead ? .readUntilSizeOrStreamEnd : .readFromStreamMaxOnce)
144+
let ret = (bufferValidLength - previousBufferValidLenth)
145+
assert(ret >= 0, "INTERNAL LOGIC ERROR")
146+
return ret
147+
}
148+
97149
public func readData<T>(size: Int, allowReadingLess: Bool, updateReadPosition: Bool, _ handler: (UnsafeRawBufferPointer) throws -> T) throws -> T {
98150
let ret = try readDataNoCurrentPosIncrement(size: size, readContraints: allowReadingLess ? .readUntilSizeOrStreamEnd : .getExactSize)
151+
assert(ret.count <= bufferValidLength, "INTERNAL LOGIC ERROR")
152+
assert(ret.count <= size, "INTERNAL LOGIC ERROR")
99153
if updateReadPosition {
100154
currentReadPosition += ret.count
101155
bufferValidLength -= ret.count
102156
bufferStartPos += ret.count
103157
}
104-
assert(ret.count <= size, "INTERNAL ERROR")
105158
return try handler(ret)
106159
}
107160

@@ -178,9 +231,6 @@ public final class GenericStreamReader : StreamReader {
178231
private var bufferStartPos: Int
179232
private var bufferValidLength: Int
180233

181-
/** The total number of bytes read from the source stream. */
182-
private var totalReadBytesCount = 0
183-
184234
private enum ReadContraints {
185235
case getExactSize
186236
case readUntilSizeOrStreamEnd
@@ -192,15 +242,26 @@ public final class GenericStreamReader : StreamReader {
192242
}
193243

194244
private func readDataNoCurrentPosIncrement(size: Int, readContraints: ReadContraints) throws -> UnsafeRawBufferPointer {
245+
assert(size >= 0)
246+
/* If we want to read 0 bytes, whether we’ve reached EOF or we are allowed
247+
 * to read more bytes, we can return directly an empty buffer. */
248+
guard size > 0 else {return UnsafeRawBufferPointer(start: nil, count: 0)}
249+
/* We check reading the given size is allowed. */
195250
let allowedToBeRead = readSizeLimit.flatMap{ $0 - currentReadPosition }
196251
if let allowedToBeRead = allowedToBeRead, allowedToBeRead < size {
197252
guard readContraints.allowReadingLess else {
198253
throw StreamReaderError.notEnoughData(wouldReachReadSizeLimit: true)
199254
}
200255
if allowedToBeRead <= 0 {
201-
return UnsafeRawBufferPointer(start: nil, count: 0)
256+
streamHasReachedEOF = true
202257
}
203258
}
259+
/* If we have reached EOF (not of the stream, the one of the reader), we
260+
 * know there is nothing more to return. */
261+
guard !hasReachedEOF else {
262+
if readContraints.allowReadingLess {return UnsafeRawBufferPointer(start: nil, count: 0)}
263+
else {throw StreamReaderError.notEnoughData(wouldReachReadSizeLimit: true)}
264+
}
204265
assert(allowedToBeRead == nil || allowedToBeRead! >= 0)
205266

206267
/* We constrain the size to the maximum allowed to be read. */
@@ -287,20 +348,31 @@ public final class GenericStreamReader : StreamReader {
287348

288349
let bufferStart = buffer + bufferStartPos
289350
if bufferValidLength < size {
290-
/* The buffer does not contain enough: we read from the stream.
291-
 * As per the specs of the function, we know there is enough space in
292-
 * the buffer to hold the required size, and reading the given size
293-
 * won’t break the readSizeLimit contract. */
351+
/* The buffer does not contain enough: we read from the stream. */
294352
repeat {
353+
assert(size - bufferValidLength > 0, "INTERNAL LOGIC ERROR")
354+
/* We try and read as much bytes as possible from the stream */
355+
let unconstrainedSizeToRead = bufferSize - (bufferStartPos + bufferValidLength)
356+
/* But we have to constrain to the size allowed to be read */
357+
let sizeAllowedToBeRead: Int
358+
if let readSizeLimit = readSizeLimit {sizeAllowedToBeRead = min(readSizeLimit - currentStreamReadPosition, unconstrainedSizeToRead)}
359+
else {sizeAllowedToBeRead = unconstrainedSizeToRead}
360+
/* And to the underlying stream read size limit */
295361
let sizeToRead: Int
296-
if let readLimit = underlyingStreamReadSizeLimit {sizeToRead = min(readLimit, size - bufferValidLength)}
297-
else {sizeToRead = size - bufferValidLength}
298-
assert(sizeToRead > 0, "INTERNAL LOGIC ERROR")
362+
if let readLimit = underlyingStreamReadSizeLimit {sizeToRead = min(readLimit, sizeAllowedToBeRead)}
363+
else {sizeToRead = sizeAllowedToBeRead}
299364
assert(sizeToRead <= bufferSize - (bufferStartPos + bufferValidLength), "INTERNAL LOGIC ERROR")
365+
366+
guard sizeToRead > 0 else {
367+
assert(underlyingStreamReadSizeLimit! == 0, "INTERNAL LOGIC ERROR")
368+
throw StreamReaderError.streamReadForbidden
369+
}
370+
300371
let sizeRead = try sourceStream.read(bufferStart + bufferValidLength, maxLength: sizeToRead)
301372
bufferValidLength += sizeRead
302-
totalReadBytesCount += sizeRead
303-
assert(readSizeLimit == nil || totalReadBytesCount <= readSizeLimit!)
373+
currentStreamReadPosition += sizeRead
374+
if sizeRead == 0 {streamHasReachedEOF = true}
375+
assert(readSizeLimit == nil || currentStreamReadPosition <= readSizeLimit!)
304376

305377
if readContraints == .readFromStreamMaxOnce {break}
306378
guard sizeRead > 0 else {

0 commit comments

Comments
 (0)