Gemini/GeminiProtocol/GeminiDataTask.swift

95 lines
2.8 KiB
Swift

//
// GeminiDataTask.swift
// GeminiProtocol
//
// Created by Shadowfacts on 7/14/20.
//
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: Completion
private var state = State.unstarted
private let connection: NWConnection
public init(request: GeminiRequest, completion: @escaping Completion) {
self.request = request
self.completion = completion
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 Completion) throws {
self.init(request: try GeminiRequest(url: url), completion: completion)
}
deinit {
self.cancel()
}
public func resume() {
guard state == .unstarted else { return }
connection.start(queue: GeminiDataTask.queue)
}
public func cancel() {
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
}
}
public extension GeminiDataTask {
enum Error: Swift.Error {
case noData
case connectionError(NWError)
}
}