Skip to content

Commit d5adb0f

Browse files
Implement seek InputStream
1 parent f14cf81 commit d5adb0f

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

Foundation/Stream.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ open class Stream: NSObject {
114114
// InputStream is an abstract class representing the base functionality of a read stream.
115115
// Subclassers are required to implement these methods.
116116
open class InputStream: Stream {
117-
117+
enum _Error: Error {
118+
case cantSeekInputStream
119+
}
120+
118121
internal let _stream: CFReadStream!
119122

120123
// reads up to length bytes into the supplied buffer, which must be at least of size len. Returns the actual number of bytes read.
@@ -225,6 +228,43 @@ open class OutputStream : Stream {
225228
}
226229
}
227230

231+
extension InputStream {
232+
func seek(to position: UInt64) throws {
233+
guard position > 0 else {
234+
return
235+
}
236+
237+
guard position < Int.max else { throw _Error.cantSeekInputStream }
238+
239+
let bufferSize = 1024
240+
var remainingBytes = Int(position)
241+
242+
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufferSize, alignment: MemoryLayout<UInt8>.alignment)
243+
244+
guard let pointer = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
245+
buffer.deallocate()
246+
throw _Error.cantSeekInputStream
247+
}
248+
249+
if self.streamStatus == .notOpen {
250+
self.open()
251+
}
252+
253+
while remainingBytes > 0 && self.hasBytesAvailable {
254+
let read = self.read(pointer, maxLength: min(bufferSize, remainingBytes))
255+
if read == -1 {
256+
throw _Error.cantSeekInputStream
257+
}
258+
remainingBytes -= read
259+
}
260+
261+
buffer.deallocate()
262+
if remainingBytes != 0 {
263+
throw _Error.cantSeekInputStream
264+
}
265+
}
266+
}
267+
228268
// Discussion of this API is ongoing for its usage of AutoreleasingUnsafeMutablePointer
229269
#if false
230270
extension Stream {

Foundation/URLSession/NativeProtocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
269269
case .transferInProgress(let currentTransferState):
270270
switch currentTransferState.requestBodySource {
271271
case is _BodyStreamSource:
272+
try inputStream.seek(to: position)
272273
let drain = strongSelf.createTransferBodyDataDrain()
273274
let source = _BodyStreamSource(inputStream: inputStream)
274275
let transferState = _TransferState(url: url, bodyDataDrain: drain, bodySource: source)

TestFoundation/TestStream.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10+
private extension Data {
11+
init(reading input: InputStream) {
12+
self.init()
13+
input.open()
14+
15+
let bufferSize = 1024
16+
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
17+
while input.hasBytesAvailable {
18+
let read = input.read(buffer, maxLength: bufferSize)
19+
self.append(buffer, count: read)
20+
}
21+
buffer.deallocate(capacity: bufferSize)
22+
23+
input.close()
24+
}
25+
}
26+
1027
class TestStream : XCTestCase {
1128
static var allTests: [(String, (TestStream) -> () throws -> Void)] {
1229
return [
@@ -15,6 +32,7 @@ class TestStream : XCTestCase {
1532
("test_InputStreamWithFile", test_InputStreamWithFile),
1633
("test_InputStreamHasBytesAvailable", test_InputStreamHasBytesAvailable),
1734
("test_InputStreamInvalidPath", test_InputStreamInvalidPath),
35+
("test_InputStreamSeekToPosition", test_InputStreamSeekToPosition),
1836
("test_outputStreamCreationToFile", test_outputStreamCreationToFile),
1937
("test_outputStreamCreationToBuffer", test_outputStreamCreationToBuffer),
2038
("test_outputStreamCreationWithUrl", test_outputStreamCreationWithUrl),
@@ -116,6 +134,50 @@ class TestStream : XCTestCase {
116134
XCTAssertEqual(.error, fileStream.streamStatus)
117135
}
118136

137+
func test_InputStreamSeekToPosition() {
138+
let str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras congue laoreet facilisis. Sed porta tristique orci. Fusce ut nisl dignissim, tempor tortor id, molestie neque. Nam non tincidunt mi. Integer ac diam quis leo aliquam congue et non magna. In porta mauris suscipit erat pulvinar, sed fringilla quam ornare. Nulla vulputate et ligula vitae sollicitudin. Nulla vel vehicula risus. Quisque eu urna ullamcorper, tincidunt ante vitae, aliquet sem. Suspendisse nec turpis placerat, porttitor ex vel, tristique orci. Maecenas pretium, augue non elementum imperdiet, diam ex vestibulum tortor, non ultrices ante enim iaculis ex. Fusce ut nisl dignissim, tempor tortor id, molestie neque. Nam non tincidunt mi. Integer ac diam quis leo aliquam congue et non magna. In porta mauris suscipit erat pulvinar, sed fringilla quam ornare. Nulla vulputate et ligula vitae sollicitudin. Nulla vel vehicula risus. Quisque eu urna ullamcorper, tincidunt ante vitae, aliquet sem. Suspendisse nec turpis placerat, porttitor ex vel, tristique orci. Maecenas pretium, augue non elementum imperdiet, diam ex vestibulum tortor, non ultrices ante enim iaculis ex.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras congue laoreet facilisis. Sed porta tristique orci. Fusce ut nisl dignissim, tempor tortor id, molestie neque. Nam non tincidunt mi. Integer ac diam quis leo aliquam congue et non magna. In porta mauris suscipit erat pulvinar, sed fringilla quam ornare. Nulla vulputate et ligula vitae sollicitudin. Nulla vel vehicula risus. Quisque eu urna ullamcorper, tincidunt ante vitae, aliquet sem. Suspendisse nec turpis placerat, porttitor ex vel."
139+
XCTAssert(str.count > 1024) // str.count must be bigger than buffersize inside InputStream.seek func.
140+
141+
func testSubdata(_ pos: UInt64) throws -> Data? {
142+
guard let data = str.data(using: .utf8) else {
143+
XCTFail()
144+
return nil
145+
}
146+
147+
let stream = InputStream(data: data)
148+
stream.open()
149+
150+
try stream.seek(to: pos)
151+
let streamData = Data(reading: stream)
152+
153+
let subdata = data.subdata(in: Range(Int(pos)..<data.count))
154+
XCTAssertEqual(streamData, subdata)
155+
156+
return subdata
157+
}
158+
159+
var sum = 0
160+
for i in 0...str.count {
161+
do {
162+
sum += try testSubdata(UInt64(i))!.count
163+
} catch _ {
164+
XCTFail()
165+
}
166+
}
167+
168+
XCTAssertEqual(((1 + str.count) * str.count)/2, sum) // Test on sum of arithmetic sequence :)
169+
XCTAssertEqual(try testSubdata(UInt64(str.count))!.count, 0) // It shouldbe end
170+
171+
do {
172+
try testSubdata(UInt64(str.count + 1)) // out of boundaries
173+
XCTFail()
174+
} catch let error as InputStream._Error {
175+
XCTAssertEqual(error, .cantSeekInputStream)
176+
} catch {
177+
XCTFail()
178+
}
179+
}
180+
119181
func test_outputStreamCreationToFile() {
120182
guard let filePath = createTestFile("TestFileOut.txt", _contents: Data(capacity: 256)) else {
121183
XCTFail("Unable to create temp file");

0 commit comments

Comments
 (0)