Gemini/GeminiProtocol/GeminiConnection.swift

80 lines
2.7 KiB
Swift
Raw Normal View History

2020-07-13 03:09:37 +00:00
//
// GeminiConnection.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Foundation
import Network
2020-07-13 22:22:36 +00:00
public protocol GeminiConnectionDelegate: class {
2020-07-13 03:09:37 +00:00
func connectionReady(_ connection: GeminiConnection)
func connection(_ connection: GeminiConnection, receivedData data: Data?, header: GeminiResponseHeader)
func connection(_ connection: GeminiConnection, handleError error: GeminiConnection.Error)
2020-07-13 03:09:37 +00:00
func connectionCompleted(_ connection: GeminiConnection)
}
2020-07-13 22:22:36 +00:00
public class GeminiConnection {
2020-07-13 03:09:37 +00:00
public typealias Error = NWError
2020-07-13 22:22:36 +00:00
public weak var delegate: GeminiConnectionDelegate?
2020-07-13 03:09:37 +00:00
2020-07-15 02:39:38 +00:00
private let connection: NWConnection
2020-07-13 03:09:37 +00:00
2020-07-13 22:22:36 +00:00
public init(endpoint: NWEndpoint, delegate: GeminiConnectionDelegate? = nil) {
2020-07-13 03:09:37 +00:00
self.connection = NWConnection(to: endpoint, using: .gemini)
self.delegate = delegate
}
2020-07-15 02:39:38 +00:00
public func startConnection() {
2020-07-13 03:09:37 +00:00
connection.stateUpdateHandler = { (newState) in
switch newState {
case .ready:
2020-07-15 02:39:38 +00:00
print("\(self.connection) established")
2020-07-13 03:09:37 +00:00
self.delegate?.connectionReady(self)
case let .failed(error):
2020-07-15 02:39:38 +00:00
print("\(self.connection) failed: \(error)")
self.connection.cancel()
2020-07-13 03:09:37 +00:00
default:
break
}
}
// todo: should this be on a background queue?
connection.start(queue: .main)
}
2020-07-15 02:39:38 +00:00
public func cancelConnection() {
if connection.state != .cancelled {
connection.cancel()
}
}
2020-07-13 03:09:37 +00:00
2020-07-13 22:22:36 +00:00
public func sendRequest(_ request: GeminiRequest) {
2020-07-13 03:09:37 +00:00
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,
2020-07-13 03:09:37 +00:00
let header = message.geminiResponseHeader,
let delegate = self.delegate {
delegate.connection(self, receivedData: data, header: header)
}
2020-07-15 02:39:38 +00:00
guard isComplete else { fatalError("Connection should complete immediately") }
self.delegate?.connectionCompleted(self)
self.connection.cancel()
2020-07-13 03:09:37 +00:00
}
}
}