Skip to content

Commit 533fd5c

Browse files
committed
feat: implement request using InpuStream on iOS
1 parent 2af8078 commit 533fd5c

File tree

6 files changed

+223
-19
lines changed

6 files changed

+223
-19
lines changed

ios/Bridge/GomobileIPFS.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
1AB96057288589CB00AC7579 /* InputStreamFromGo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB96055288589CB00AC7579 /* InputStreamFromGo.swift */; };
11+
1AB96058288589CB00AC7579 /* InputStreamToGo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB96056288589CB00AC7579 /* InputStreamToGo.swift */; };
1012
8AF76E4826EF532E007DF24A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AF76E4626EA36F9007DF24A /* CoreBluetooth.framework */; };
1113
8AF76E4926EF532E007DF24A /* CoreBluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8AF76E4626EA36F9007DF24A /* CoreBluetooth.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1214
8D1B639D24487FC900CE188F /* ConfigIPFSTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D1B639C24487FC900CE188F /* ConfigIPFSTest.swift */; };
@@ -51,6 +53,8 @@
5153
/* End PBXCopyFilesBuildPhase section */
5254

5355
/* Begin PBXFileReference section */
56+
1AB96055288589CB00AC7579 /* InputStreamFromGo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputStreamFromGo.swift; sourceTree = "<group>"; };
57+
1AB96056288589CB00AC7579 /* InputStreamToGo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputStreamToGo.swift; sourceTree = "<group>"; };
5458
8AF76E4626EA36F9007DF24A /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; };
5559
8D1B639C24487FC900CE188F /* ConfigIPFSTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigIPFSTest.swift; sourceTree = "<group>"; };
5660
8D2C38A324470D470005C2DD /* RequestIPFSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestIPFSTests.swift; sourceTree = "<group>"; };
@@ -134,6 +138,8 @@
134138
8DE4ECE72402BFE500B70884 /* Sources */ = {
135139
isa = PBXGroup;
136140
children = (
141+
1AB96055288589CB00AC7579 /* InputStreamFromGo.swift */,
142+
1AB96056288589CB00AC7579 /* InputStreamToGo.swift */,
137143
8DE4ECF92402C0ED00B70884 /* Config.swift */,
138144
8DE4ECFA2402C0ED00B70884 /* Error.swift */,
139145
8DE4ECFB2402C0ED00B70884 /* IPFS.swift */,
@@ -265,7 +271,9 @@
265271
isa = PBXSourcesBuildPhase;
266272
buildActionMask = 2147483647;
267273
files = (
274+
1AB96057288589CB00AC7579 /* InputStreamFromGo.swift in Sources */,
268275
8DE4ED032402C0ED00B70884 /* Repo.swift in Sources */,
276+
1AB96058288589CB00AC7579 /* InputStreamToGo.swift in Sources */,
269277
8DE4ECFF2402C0ED00B70884 /* SockManager.swift in Sources */,
270278
8DE4ED022402C0ED00B70884 /* IPFS.swift in Sources */,
271279
8DE4ED002402C0ED00B70884 /* Config.swift in Sources */,
@@ -432,7 +440,7 @@
432440
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../packages/build/ios/intermediates/core/**";
433441
INFOPLIST_FILE = GomobileIPFS/Info.plist;
434442
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
435-
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
443+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
436444
LD_RUNPATH_SEARCH_PATHS = (
437445
"$(inherited)",
438446
"@executable_path/Frameworks",
@@ -467,7 +475,7 @@
467475
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../packages/build/ios/intermediates/core/**";
468476
INFOPLIST_FILE = GomobileIPFS/Info.plist;
469477
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
470-
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
478+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
471479
LD_RUNPATH_SEARCH_PATHS = (
472480
"$(inherited)",
473481
"@executable_path/Frameworks",
@@ -495,7 +503,7 @@
495503
CODE_SIGN_STYLE = Automatic;
496504
DEVELOPMENT_TEAM = 9F2792GBSY;
497505
INFOPLIST_FILE = GomobileIPFSTests/Info.plist;
498-
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
506+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
499507
LD_RUNPATH_SEARCH_PATHS = (
500508
"$(inherited)",
501509
"@executable_path/Frameworks",
@@ -520,7 +528,7 @@
520528
CODE_SIGN_STYLE = Automatic;
521529
DEVELOPMENT_TEAM = 9F2792GBSY;
522530
INFOPLIST_FILE = GomobileIPFSTests/Info.plist;
523-
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
531+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
524532
LD_RUNPATH_SEARCH_PATHS = (
525533
"$(inherited)",
526534
"@executable_path/Frameworks",
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// InputStreamFromGo.swift
3+
// GomobileIPFS
4+
//
5+
// Created by Antoine Eddi on 19/01/2020.
6+
//
7+
8+
import Foundation
9+
import Core
10+
11+
public class InputStreamFromGoError: IPFSError {
12+
private static var code: Int = 6
13+
private static var subdomain: String = "InputStreamFromGo"
14+
15+
required init(_ description: String, _ optCause: NSError? = nil) {
16+
super.init(description, optCause, InputStreamFromGoError.subdomain, InputStreamFromGoError.code)
17+
}
18+
19+
required init?(coder: NSCoder) {
20+
super.init(coder: coder)
21+
}
22+
}
23+
24+
public class InputStreamFromGo: InputStream {
25+
private var _streamStatus: Stream.Status
26+
private var _streamError: Error?
27+
private weak var _delegate: StreamDelegate?
28+
private var readCloser: CoreReadCloser
29+
30+
init(_ readCloser: CoreReadCloser) {
31+
self._streamStatus = .notOpen
32+
self._streamError = nil
33+
self.readCloser = readCloser
34+
super.init(data: Data())
35+
}
36+
37+
override public var hasBytesAvailable: Bool {
38+
if self.streamStatus == .atEnd || self.streamStatus == .closed {
39+
return false
40+
}
41+
return true
42+
}
43+
44+
override public var streamStatus: Stream.Status {
45+
return _streamStatus
46+
}
47+
48+
override public var streamError: Error? {
49+
return _streamError
50+
}
51+
52+
override public var delegate: StreamDelegate? {
53+
get {
54+
return _delegate
55+
}
56+
set {
57+
_delegate = newValue
58+
}
59+
}
60+
61+
override public func open() {
62+
if self._streamStatus == .notOpen {
63+
self._streamStatus = .open
64+
}
65+
}
66+
67+
override public func close() {
68+
if self._streamStatus != .closed {
69+
self._streamStatus = .closed
70+
try? self.readCloser.close()
71+
}
72+
}
73+
74+
override public func read(_ buffer: UnsafeMutablePointer<UInt8>, maxLength len: Int) -> Int {
75+
if self._streamStatus != .open {
76+
self._streamError = InputStreamFromGoError("stream not opened (\(self._streamStatus))")
77+
return -1
78+
}
79+
80+
self._streamStatus = .reading
81+
82+
// swiftlint:disable todo
83+
// TODO: Use the buffer argument directly without making a copy.
84+
// swiftlint:enable todo
85+
let tmp: NSData? = NSMutableData(length: len)
86+
var read: Int = 0
87+
88+
do {
89+
try self.readCloser.read(tmp as Data?, n: &read)
90+
} catch let err {
91+
if err.localizedDescription == "EOF" {
92+
self._streamStatus = .atEnd
93+
return 0
94+
}
95+
96+
self._streamStatus = .error
97+
self._streamError = err
98+
return -1
99+
}
100+
101+
tmp!.getBytes(buffer, length: read)
102+
self._streamStatus = .open
103+
104+
return read
105+
}
106+
107+
override public func getBuffer(
108+
_ buffer: UnsafeMutablePointer<UnsafeMutablePointer<UInt8>?>,
109+
length len: UnsafeMutablePointer<Int>) -> Bool {
110+
return false
111+
}
112+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// InputStreamToGo.swift
3+
// GomobileIPFS
4+
//
5+
// Created by CI Agent on 20/01/2020.
6+
//
7+
8+
import Foundation
9+
import Core
10+
11+
public class InputStreamToGo: NSObject, CoreReaderProtocol {
12+
private var inputStream: InputStream
13+
14+
init(_ inputStream: InputStream) {
15+
self.inputStream = inputStream
16+
self.inputStream.open()
17+
}
18+
19+
public func read(_ buffer: Data?, n len: UnsafeMutablePointer<Int>?) throws {
20+
var read: Int
21+
22+
let bytes = UnsafeMutablePointer<UInt8>(OpaquePointer((buffer! as NSData).bytes))
23+
read = self.inputStream.read(bytes, maxLength: buffer!.count)
24+
len?.initialize(to: read)
25+
26+
if read == 0 && self.inputStream.streamStatus == .atEnd {
27+
self.inputStream.close()
28+
throw NSError(domain: "", code: 0, userInfo: ["NSLocalizedDescription": "EOF"])
29+
}
30+
}
31+
}

ios/Bridge/GomobileIPFS/Sources/RequestBuilder.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class RequestBuilder {
6565
case .string(let string):
6666
self.requestBuilder.stringOptions(option, value: string)
6767
case .bytes(let data):
68-
self.requestBuilder.byteOptions(option, value: data)
68+
self.requestBuilder.bytesOptions(option, value: data)
6969
}
7070

7171
return self
@@ -97,13 +97,25 @@ public class RequestBuilder {
9797
return self
9898
}
9999

100-
/// Sends the request to the underlying go-ipfs node
100+
/// Sends the request to the underlying go-ipfs node and returns an InputStream
101+
/// - Throws: `RequestBuilderError`: If sending the request failed
102+
/// - Returns: An InputStream from which to read the response
103+
/// - seealso: [IPFS API Doc](https://docs.ipfs.io/reference/api/http/)
104+
public func send() throws -> InputStream {
105+
do {
106+
return try InputStreamFromGo(self.requestBuilder.send())
107+
} catch let error as NSError {
108+
throw RequestBuilderError("sending request failed", error)
109+
}
110+
}
111+
112+
/// Sends the request to the underlying go-ipfs node and returns a byte array
101113
/// - Throws: `RequestBuilderError`: If sending the request failed
102114
/// - Returns: A Data object containing the response
103115
/// - seealso: [IPFS API Doc](https://docs.ipfs.io/reference/api/http/)
104-
public func send() throws -> Data? {
116+
public func sendToBytes() throws -> Data? {
105117
do {
106-
return try self.requestBuilder.send()
118+
return try self.requestBuilder.sendToBytes()
107119
} catch let error as NSError {
108120
throw RequestBuilderError("sending request failed", error)
109121
}
@@ -114,7 +126,7 @@ public class RequestBuilder {
114126
/// - Returns: A dict containing the response
115127
/// - seealso: [IPFS API Doc](https://docs.ipfs.io/reference/api/http/)
116128
public func sendToDict() throws -> [String: Any]? {
117-
guard let res = try self.requestBuilder.send() else {
129+
guard let res = try self.requestBuilder.sendToBytes() else {
118130
return nil
119131
}
120132

ios/Bridge/GomobileIPFSTests/RequestIPFSTests.swift

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77
//
88

99
import XCTest
10+
import CryptoKit
1011
@testable import GomobileIPFS
1112

1213
class RequestIPFSTests: XCTestCase {
1314

1415
private var ipfs: IPFS!
16+
// This CID is the IPFS logo in the Wikipedia mirror. It should exist for a long time.
17+
private var fileUri: String = "/ipfs/bafkreifxaqwd63x4bhjj33sfm3pmny2codycx27jo77it33hkexzrawyma"
18+
private var expectedFileLength: Int = 2940
19+
private var expectedFileSha256: String =
20+
"SHA256 digest: b7042c3f6efc09d29dee4566dec6e34270f02bebe977fe89ef67512f9882d860"
1521

1622
override func setUp() {
1723
do {
@@ -27,7 +33,7 @@ class RequestIPFSTests: XCTestCase {
2733

2834
guard let resolveResp = try ipfs.newRequest("resolve")
2935
.with(argument: "/ipns/\(domain)")
30-
.sendToDict() else {
36+
.sendToDict() else {
3137
XCTFail("error while casting dict for \"resolve\"")
3238
return
3339
}
@@ -62,14 +68,49 @@ class RequestIPFSTests: XCTestCase {
6268
}
6369

6470
func testCatFile() throws {
65-
let latestRaw = try ipfs.newRequest("cat")
66-
.with(argument: "/ipns/xkcd.hacdias.com/latest/info.json")
67-
.send()
71+
let response = try ipfs.newRequest("cat")
72+
.with(argument: fileUri)
73+
.sendToBytes()
6874

69-
do {
70-
try JSONSerialization.jsonObject(with: latestRaw!, options: [])
71-
} catch _ {
72-
XCTFail("error while parsing fetched JSON: \(String(decoding: latestRaw! , as: UTF8.self))")
75+
XCTAssertEqual(
76+
expectedFileLength,
77+
response!.count,
78+
"response should have the correct length"
79+
)
80+
XCTAssertEqual(
81+
expectedFileSha256,
82+
SHA256.hash(data: response!).description,
83+
"response should have the correct SHA256"
84+
)
85+
}
86+
87+
func testCatFileStream() throws {
88+
var count = 0
89+
var hasher = SHA256()
90+
91+
if let stream = try? ipfs.newRequest("cat")
92+
.with(argument: fileUri)
93+
.send() {
94+
var buf: [UInt8] = [UInt8](repeating: 0, count: 1000)
95+
stream.open()
96+
while case let len = stream.read(&buf, maxLength: buf.count), len > 0 {
97+
count += len
98+
hasher.update(data: buf[..<len])
99+
}
100+
stream.close()
101+
} else {
102+
XCTFail("error calling ipfs.newRequest")
73103
}
104+
105+
XCTAssertEqual(
106+
expectedFileLength,
107+
count,
108+
"response should have the correct length"
109+
)
110+
XCTAssertEqual(
111+
expectedFileSha256,
112+
hasher.finalize().description,
113+
"response should have the correct SHA256"
114+
)
74115
}
75116
}

ios/Example/Example/ViewController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class ViewController: UIViewController {
107107
do {
108108
let fetchedData = try ViewController.ipfs!.newRequest("cat")
109109
.with(argument: code)
110-
.send()
110+
.sendToBytes()
111111

112112
title = "IPFS File"
113113
image = UIImage(data: fetchedData!)!
@@ -254,7 +254,7 @@ class ViewController: UIViewController {
254254

255255
let fetchedData = try ViewController.ipfs!.newRequest("cat")
256256
.with(argument: "\(ViewController.XKCDIPNS)/\(formattedIndex)/image.\(imgExt)")
257-
.send()
257+
.sendToBytes()
258258

259259
title = "\(randomIndex). \(fetchedInfo["title"] as! String)"
260260
image = UIImage(data: fetchedData!)!

0 commit comments

Comments
 (0)