Merge GeminiConnection into GeminiDataTask

This commit is contained in:
Shadowfacts 2020-07-22 18:25:56 -04:00
parent 70bbbc5583
commit 06a9c1fcac
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 61 additions and 132 deletions

View File

@ -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 */,

View File

@ -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>

View File

@ -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(_:))
}

View File

@ -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()
}
}
}

View File

@ -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) {
}
}