From 06a9c1fcac27f7dafe0a3860bc264dcd9831b16a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 22 Jul 2020 18:25:56 -0400 Subject: [PATCH] Merge GeminiConnection into GeminiDataTask --- Gemini.xcodeproj/project.pbxproj | 4 - .../xcschemes/xcschememanagement.plist | 8 +- Gemini/BrowserWindowController.swift | 2 +- GeminiProtocol/GeminiConnection.swift | 79 -------------- GeminiProtocol/GeminiDataTask.swift | 100 ++++++++++-------- 5 files changed, 61 insertions(+), 132 deletions(-) delete mode 100644 GeminiProtocol/GeminiConnection.swift diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index 5b81506..15aeee5 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -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 = ""; }; D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiResponseHeader.swift; sourceTree = ""; }; D626649624BBF24100DF9B88 /* Message+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Message+Gemini.swift"; sourceTree = ""; }; - D626649724BBF24100DF9B88 /* GeminiConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiConnection.swift; sourceTree = ""; }; D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWParameters+Gemini.swift"; sourceTree = ""; }; D626649924BBF24100DF9B88 /* GeminiProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiProtocol.swift; sourceTree = ""; }; D626649A24BBF24100DF9B88 /* GeminiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiRequest.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist index 5eda572..a83b006 100644 --- a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ BrowserCore.xcscheme_^#shared#^_ orderHint - 5 + 2 Gemini-iOS.xcscheme_^#shared#^_ orderHint - 2 + 3 Gemini.xcscheme_^#shared#^_ @@ -27,12 +27,12 @@ GeminiProtocol.xcscheme_^#shared#^_ orderHint - 3 + 4 GeminiRenderer.xcscheme_^#shared#^_ orderHint - 4 + 5 SuppressBuildableAutocreation diff --git a/Gemini/BrowserWindowController.swift b/Gemini/BrowserWindowController.swift index c9cc140..9010993 100644 --- a/Gemini/BrowserWindowController.swift +++ b/Gemini/BrowserWindowController.swift @@ -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(_:)) } diff --git a/GeminiProtocol/GeminiConnection.swift b/GeminiProtocol/GeminiConnection.swift deleted file mode 100644 index e6b3c4e..0000000 --- a/GeminiProtocol/GeminiConnection.swift +++ /dev/null @@ -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() - } - } - -} diff --git a/GeminiProtocol/GeminiDataTask.swift b/GeminiProtocol/GeminiDataTask.swift index 27f8ea4..eb7c544 100644 --- a/GeminiProtocol/GeminiDataTask.swift +++ b/GeminiProtocol/GeminiDataTask.swift @@ -9,22 +9,35 @@ import Foundation import Network public class GeminiDataTask { + public typealias Completion = (Result) -> Void + + private static let queue = DispatchQueue(label: "GeminiDataTask", qos: .default) + public let request: GeminiRequest - private let completion: (Result) -> 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) -> 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) -> 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) { - } - -}