Merge GeminiConnection into GeminiDataTask
This commit is contained in:
parent
70bbbc5583
commit
06a9c1fcac
|
@ -17,7 +17,6 @@
|
|||
D626648D24BBF22E00DF9B88 /* GeminiProtocol.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D626649C24BBF24100DF9B88 /* GeminiResponseHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */; };
|
||||
D626649D24BBF24100DF9B88 /* Message+Gemini.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649624BBF24100DF9B88 /* Message+Gemini.swift */; };
|
||||
D626649E24BBF24100DF9B88 /* GeminiConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649724BBF24100DF9B88 /* GeminiConnection.swift */; };
|
||||
D626649F24BBF24100DF9B88 /* NWParameters+Gemini.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */; };
|
||||
D62664A024BBF24100DF9B88 /* GeminiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649924BBF24100DF9B88 /* GeminiProtocol.swift */; };
|
||||
D62664A124BBF24100DF9B88 /* GeminiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649A24BBF24100DF9B88 /* GeminiRequest.swift */; };
|
||||
|
@ -250,7 +249,6 @@
|
|||
D626648824BBF22E00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiResponseHeader.swift; sourceTree = "<group>"; };
|
||||
D626649624BBF24100DF9B88 /* Message+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Message+Gemini.swift"; sourceTree = "<group>"; };
|
||||
D626649724BBF24100DF9B88 /* GeminiConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiConnection.swift; sourceTree = "<group>"; };
|
||||
D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWParameters+Gemini.swift"; sourceTree = "<group>"; };
|
||||
D626649924BBF24100DF9B88 /* GeminiProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiProtocol.swift; sourceTree = "<group>"; };
|
||||
D626649A24BBF24100DF9B88 /* GeminiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiRequest.swift; sourceTree = "<group>"; };
|
||||
|
@ -454,7 +452,6 @@
|
|||
D626649A24BBF24100DF9B88 /* GeminiRequest.swift */,
|
||||
D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */,
|
||||
D69F00AD24BEA29100E37622 /* GeminiResponse.swift */,
|
||||
D626649724BBF24100DF9B88 /* GeminiConnection.swift */,
|
||||
D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */,
|
||||
);
|
||||
path = GeminiProtocol;
|
||||
|
@ -965,7 +962,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D62664A024BBF24100DF9B88 /* GeminiProtocol.swift in Sources */,
|
||||
D626649E24BBF24100DF9B88 /* GeminiConnection.swift in Sources */,
|
||||
D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */,
|
||||
D62664A124BBF24100DF9B88 /* GeminiRequest.swift in Sources */,
|
||||
D626649F24BBF24100DF9B88 /* NWParameters+Gemini.swift in Sources */,
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
<key>BrowserCore.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>5</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>Gemini-iOS.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>Gemini.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -27,12 +27,12 @@
|
|||
<key>GeminiProtocol.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>GeminiRenderer.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -27,7 +27,7 @@ class BrowserWindowController: NSWindowController {
|
|||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
||||
contentViewController = NSHostingController(rootView: BrowserView(navigator: navigator))
|
||||
contentViewController = NSHostingController(rootView: ContentView(navigator: navigator))
|
||||
|
||||
urlUpdater = navigator.$currentURL.sink(receiveValue: self.currentURLChanged(_:))
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
//
|
||||
// GeminiConnection.swift
|
||||
// Gemini
|
||||
//
|
||||
// Created by Shadowfacts on 7/12/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public protocol GeminiConnectionDelegate: class {
|
||||
func connectionReady(_ connection: GeminiConnection)
|
||||
func connection(_ connection: GeminiConnection, receivedData data: Data?, header: GeminiResponseHeader)
|
||||
func connection(_ connection: GeminiConnection, handleError error: GeminiConnection.Error)
|
||||
func connectionCompleted(_ connection: GeminiConnection)
|
||||
}
|
||||
|
||||
public class GeminiConnection {
|
||||
|
||||
public typealias Error = NWError
|
||||
|
||||
public weak var delegate: GeminiConnectionDelegate?
|
||||
|
||||
private let connection: NWConnection
|
||||
|
||||
public init(endpoint: NWEndpoint, delegate: GeminiConnectionDelegate? = nil) {
|
||||
self.connection = NWConnection(to: endpoint, using: .gemini)
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func startConnection() {
|
||||
connection.stateUpdateHandler = { (newState) in
|
||||
switch newState {
|
||||
case .ready:
|
||||
print("\(self.connection) established")
|
||||
self.delegate?.connectionReady(self)
|
||||
case let .failed(error):
|
||||
print("\(self.connection) failed: \(error)")
|
||||
self.connection.cancel()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// todo: should this be on a background queue?
|
||||
connection.start(queue: .main)
|
||||
}
|
||||
|
||||
public func cancelConnection() {
|
||||
if connection.state != .cancelled {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
public func sendRequest(_ request: GeminiRequest) {
|
||||
let message = NWProtocolFramer.Message(geminiRequest: request)
|
||||
let context = NWConnection.ContentContext(identifier: "GeminiRequest", metadata: [message])
|
||||
// todo: should this really always be idempotent?
|
||||
connection.send(content: nil, contentContext: context, isComplete: true, completion: .contentProcessed({ (_) in }))
|
||||
receiveNextMessage()
|
||||
}
|
||||
|
||||
private func receiveNextMessage() {
|
||||
connection.receiveMessage { (data, context, isComplete, error) in
|
||||
if let error = error {
|
||||
self.delegate?.connection(self, handleError: error)
|
||||
} else if let message = context?.protocolMetadata(definition: GeminiProtocol.definition) as? NWProtocolFramer.Message,
|
||||
let header = message.geminiResponseHeader,
|
||||
let delegate = self.delegate {
|
||||
delegate.connection(self, receivedData: data, header: header)
|
||||
}
|
||||
|
||||
guard isComplete else { fatalError("Connection should complete immediately") }
|
||||
self.delegate?.connectionCompleted(self)
|
||||
self.connection.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,22 +9,35 @@ import Foundation
|
|||
import Network
|
||||
|
||||
public class GeminiDataTask {
|
||||
public typealias Completion = (Result<GeminiResponse, Error>) -> Void
|
||||
|
||||
private static let queue = DispatchQueue(label: "GeminiDataTask", qos: .default)
|
||||
|
||||
public let request: GeminiRequest
|
||||
private let completion: (Result<GeminiResponse, Error>) -> Void
|
||||
private let completion: Completion
|
||||
private var state = State.unstarted
|
||||
private let connection: NWConnection
|
||||
|
||||
private let connection: GeminiConnection
|
||||
|
||||
public private(set) var completed: Bool = false
|
||||
|
||||
public init(request: GeminiRequest, completion: @escaping (Result<GeminiResponse, Error>) -> Void) {
|
||||
public init(request: GeminiRequest, completion: @escaping Completion) {
|
||||
self.request = request
|
||||
self.completion = completion
|
||||
self.connection = GeminiConnection(endpoint: .url(request.url))
|
||||
self.connection.delegate = self
|
||||
self.connection = NWConnection(to: .url(request.url), using: .gemini)
|
||||
|
||||
self.connection.stateUpdateHandler = { (newState) in
|
||||
switch newState {
|
||||
case .ready:
|
||||
self.sendRequest()
|
||||
case let .failed(error):
|
||||
self.connection.cancel()
|
||||
self.completion(.failure(.connectionError(error)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public convenience init(url: URL, completion: @escaping (Result<GeminiResponse, Error>) -> Void) throws {
|
||||
public convenience init(url: URL, completion: @escaping Completion) throws {
|
||||
self.init(request: try GeminiRequest(url: url), completion: completion)
|
||||
}
|
||||
|
||||
|
@ -33,11 +46,43 @@ public class GeminiDataTask {
|
|||
}
|
||||
|
||||
public func resume() {
|
||||
self.connection.startConnection()
|
||||
guard state == .unstarted else { return }
|
||||
connection.start(queue: GeminiDataTask.queue)
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
connection.cancelConnection()
|
||||
guard state != .completed else { return }
|
||||
connection.cancel()
|
||||
state = .completed
|
||||
}
|
||||
|
||||
private func sendRequest() {
|
||||
let message = NWProtocolFramer.Message(geminiRequest: request)
|
||||
let context = NWConnection.ContentContext(identifier: "GeminiRequest", metadata: [message])
|
||||
connection.send(content: nil, contentContext: context, isComplete: true, completion: .contentProcessed({ (_) in }))
|
||||
state = .started
|
||||
receive()
|
||||
}
|
||||
|
||||
private func receive() {
|
||||
connection.receiveMessage { (data, context, isComplete, error) in
|
||||
if let error = error {
|
||||
self.completion(.failure(.connectionError(error)))
|
||||
} else if let message = context?.protocolMetadata(definition: GeminiProtocol.definition) as? NWProtocolFramer.Message,
|
||||
let header = message.geminiResponseHeader {
|
||||
let response = GeminiResponse(header: header, body: data)
|
||||
self.completion(.success(response))
|
||||
}
|
||||
|
||||
self.connection.cancel()
|
||||
self.state = .completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GeminiDataTask {
|
||||
enum State {
|
||||
case unstarted, started, completed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,36 +92,3 @@ public extension GeminiDataTask {
|
|||
case connectionError(NWError)
|
||||
}
|
||||
}
|
||||
|
||||
extension GeminiDataTask: GeminiConnectionDelegate {
|
||||
public func connectionReady(_ connection: GeminiConnection) {
|
||||
connection.sendRequest(self.request)
|
||||
}
|
||||
|
||||
public func connection(_ connection: GeminiConnection, receivedData data: Data?, header: GeminiResponseHeader) {
|
||||
guard !completed else {
|
||||
print("GeminiRequestTask(\(self)) has already completed, shouldn't be receiving any data")
|
||||
return
|
||||
}
|
||||
completed = true
|
||||
if let data = data {
|
||||
let response = GeminiResponse(header: header, body: data)
|
||||
self.completion(.success(response))
|
||||
} else {
|
||||
self.completion(.failure(.noData))
|
||||
}
|
||||
}
|
||||
|
||||
public func connection(_ connection: GeminiConnection, handleError error: GeminiConnection.Error) {
|
||||
guard !completed else {
|
||||
print("GeminiRequestTask(\(self)) has already completed, shouldn't be receiving any data")
|
||||
return
|
||||
}
|
||||
completed = true
|
||||
self.completion(.failure(.connectionError(error)))
|
||||
}
|
||||
|
||||
public func connectionCompleted(_ connection: GeminiConnection) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue