Skip to content

Commit

Permalink
Change BufferedReader.read(while:) signature (#2688)
Browse files Browse the repository at this point in the history
Return whether EOF read or predicate doesn't hold anymore in BufferedReader.read(while:)
  • Loading branch information
gjcairo committed Mar 20, 2024
1 parent f8c5e02 commit d15793c
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 11 deletions.
35 changes: 29 additions & 6 deletions Sources/NIOFileSystem/BufferedReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import NIOCore
///
/// You can create a reader from a ``ReadableFileHandleProtocol`` by calling
/// ``ReadableFileHandleProtocol/bufferedReader(startingAtAbsoluteOffset:capacity:)``. Call
/// ``read(_:)`` to read a fixed number of bytes from the file or ``read(while:)`` to read
/// ``read(_:)`` to read a fixed number of bytes from the file or ``read(while:)-8aukk`` to read
/// from the file while the bytes match a predicate.
///
/// You can also read bytes without returning them to caller by calling ``drop(_:)`` and
Expand Down Expand Up @@ -112,9 +112,25 @@ public struct BufferedReader<Handle: ReadableFileHandleProtocol> {
/// - Parameters:
/// - predicate: A predicate which evaluates to `true` for all bytes returned.
/// - Returns: The bytes read from the file.
/// - Important: This method has been deprecated: use ``read(while:)-8aukk`` instead.
@available(*, deprecated, message: "Use the read(while:) method returning a (ByteBuffer, Bool) tuple instead.")
public mutating func read(
while predicate: (UInt8) -> Bool
) async throws -> ByteBuffer {
try await self.read(while: predicate).bytes
}

/// Reads from the current position in the file until `predicate` returns `false` and returns
/// the read bytes.
///
/// - Parameters:
/// - predicate: A predicate which evaluates to `true` for all bytes returned.
/// - Returns: A tuple containing the bytes read from the file in its first component, and a boolean
/// indicating whether we've stopped reading because EOF has been reached, or because the predicate
/// condition doesn't hold true anymore.
public mutating func read(
while predicate: (UInt8) -> Bool
) async throws -> (bytes: ByteBuffer, readEOF: Bool) {
// Check if the required bytes are in the buffer already.
let view = self.buffer.readableBytesView

Expand All @@ -123,7 +139,11 @@ public struct BufferedReader<Handle: ReadableFileHandleProtocol> {
let prefix = view[..<index]
let buffer = ByteBuffer(prefix)
self.buffer.moveReaderIndex(forwardBy: buffer.readableBytes)
return buffer

// If we reached this codepath, it's because at least one element
// in the buffer makes the predicate false. This means that we have
// stopped reading because the condition doesn't hold true anymore.
return (buffer, false)
}

// The predicate holds true for all bytes in the buffer, start consuming chunks from the
Expand All @@ -142,18 +162,21 @@ public struct BufferedReader<Handle: ReadableFileHandleProtocol> {
let buffer = self.buffer
self.buffer = chunk

// Store the rest of the chunk.
return buffer
// If we reached this codepath, it's because at least one element
// in the buffer makes the predicate false. This means that we have
// stopped reading because the condition doesn't hold true anymore.
return (buffer, false)
} else {
// Predicate holds for all bytes. Continue reading.
self.buffer.writeBuffer(&chunk)
}
}

// Read end-of-file without hitting the predicate: clear the buffer and return all bytes.
// Read end-of-file and the predicate still holds for all bytes:
// clear the buffer and return all bytes.
let buffer = self.buffer
self.buffer = ByteBuffer()
return buffer
return (buffer, true)
}

/// Reads and discards the given number of bytes.
Expand Down
14 changes: 9 additions & 5 deletions Tests/NIOFileSystemIntegrationTests/BufferedReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,25 +119,29 @@ final class BufferedReaderTests: XCTestCase {

try await fs.withFileHandle(forReadingAt: path) { handle in
var reader = handle.bufferedReader()
let zeros = try await reader.read { $0 == 0 }
let (zeros, isEOFZeros) = try await reader.read { $0 == 0 }
XCTAssertEqual(zeros, ByteBuffer(bytes: Array(repeating: 0, count: 1024)))
XCTAssertFalse(isEOFZeros)

let onesAndTwos = try await reader.read { $0 < 3 }
let (onesAndTwos, isEOFOnesAndTwos) = try await reader.read { $0 < 3 }
var expectedOnesAndTwos = ByteBuffer()
expectedOnesAndTwos.writeRepeatingByte(1, count: 1024)
expectedOnesAndTwos.writeRepeatingByte(2, count: 1024)

XCTAssertEqual(onesAndTwos, expectedOnesAndTwos)
XCTAssertFalse(isEOFOnesAndTwos)

let threesThroughNines = try await reader.read { $0 < 10 }
let (threesThroughNines, isEOFThreesThroughNines) = try await reader.read { $0 < 10 }
var expectedThreesThroughNines = ByteBuffer()
for byte in UInt8(3)...9 {
expectedThreesThroughNines.writeRepeatingByte(byte, count: 1024)
}
XCTAssertEqual(threesThroughNines, expectedThreesThroughNines)
XCTAssertFalse(isEOFThreesThroughNines)

let theRest = try await reader.read { _ in true }
let (theRest, isEOFTheRest) = try await reader.read { _ in true }
XCTAssertEqual(theRest.readableBytes, 246 * 1024)
XCTAssertTrue(isEOFTheRest)
}
}

Expand Down Expand Up @@ -236,7 +240,7 @@ final class BufferedReaderTests: XCTestCase {
// Gobble up whitespace etc..
try await reader.drop(while: { !isWordIsh($0) })
// Read the next word.
var characters = try await reader.read(while: isWordIsh(_:))
var (characters, _) = try await reader.read(while: isWordIsh(_:))

if characters.readableBytes == 0 {
break // Done.
Expand Down

0 comments on commit d15793c

Please sign in to comment.