From c808fa9ab35fde98d592acf74e74055f5f4a18c5 Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Sat, 16 Dec 2023 22:00:38 -0700 Subject: Refactor --- Sources/swiftGopherClient/gopherClient.swift | 50 ++++++--- .../gopherRequestResponseHandler.swift | 37 ++----- Sources/swiftGopherClient/gopherTypes.swift | 122 --------------------- 3 files changed, 46 insertions(+), 163 deletions(-) delete mode 100644 Sources/swiftGopherClient/gopherTypes.swift (limited to 'Sources/swiftGopherClient') diff --git a/Sources/swiftGopherClient/gopherClient.swift b/Sources/swiftGopherClient/gopherClient.swift index e508285..eabbbc4 100644 --- a/Sources/swiftGopherClient/gopherClient.swift +++ b/Sources/swiftGopherClient/gopherClient.swift @@ -7,12 +7,18 @@ import Foundation import NIO +import NIOTransportServices +import GopherHelpers public class GopherClient { private let group: EventLoopGroup public init() { - self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + self.group = NIOTSEventLoopGroup() + } else { + self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + } } deinit { @@ -20,22 +26,40 @@ public class GopherClient { } public func sendRequest(to host: String, port: Int = 70, message: String, completion: @escaping (Result<[gopherItem], Error>) -> Void) { - let bootstrap = ClientBootstrap(group: group) - .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .channelInitializer { channel in - channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion)) + if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + let bootstrap = NIOTSConnectionBootstrap(group: group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion)) + } + bootstrap.connect(host: host, port: port).whenComplete { result in + switch result { + case .success(let channel): + channel.closeFuture.whenComplete { _ in + print("Connection closed") + } + case .failure(let error): + completion(.failure(error)) + } } - - bootstrap.connect(host: host, port: port).whenComplete { result in - switch result { - case .success(let channel): - channel.closeFuture.whenComplete { _ in - print("Connection closed") + } else { + let bootstrap = ClientBootstrap(group: group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion)) + } + bootstrap.connect(host: host, port: port).whenComplete { result in + switch result { + case .success(let channel): + channel.closeFuture.whenComplete { _ in + print("Connection closed") + } + case .failure(let error): + completion(.failure(error)) } - case .failure(let error): - completion(.failure(error)) } } + } private func shutdownEventLoopGroup() { diff --git a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift b/Sources/swiftGopherClient/gopherRequestResponseHandler.swift index 3d15d66..d69c8c8 100644 --- a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift +++ b/Sources/swiftGopherClient/gopherRequestResponseHandler.swift @@ -7,6 +7,7 @@ import Foundation import NIO +import GopherHelpers final class GopherRequestResponseHandler: ChannelInboundHandler { typealias InboundIn = ByteBuffer @@ -31,17 +32,12 @@ final class GopherRequestResponseHandler: ChannelInboundHandler { func channelRead(context: ChannelHandlerContext, data: NIOAny) { var buffer = unwrapInboundIn(data) accumulatedData.writeBuffer(&buffer) - if let receivedString = buffer.getString(at: 0, length: buffer.readableBytes) { - print("Received from server: \(receivedString)") - } - //completion(.success(receivedString)) - //context.close(promise: nil) } func channelInactive(context: ChannelHandlerContext) { - // Parse GopherServerResponse - parseGopherServerResponse(response: accumulatedData.readString(length: accumulatedData.readableBytes) ?? "") - //completion(.success(accumulatedData.readString(length: accumulatedData.readableBytes) ?? "")) + if let dataCopy = accumulatedData.getSlice(at: 0, length: accumulatedData.readableBytes) { + parseGopherServerResponse(response: accumulatedData.readString(length: accumulatedData.readableBytes) ?? "", originalBytes: dataCopy) + } } func errorCaught(context: ChannelHandlerContext, error: Error) { @@ -49,9 +45,10 @@ final class GopherRequestResponseHandler: ChannelInboundHandler { context.close(promise: nil) } - func createGopherItem(rawLine: String, itemType: gopherItemType = .info) -> gopherItem { + func createGopherItem(rawLine: String, itemType: gopherItemType = .info, rawData: ByteBuffer) -> gopherItem { var item = gopherItem(rawLine: rawLine) item.parsedItemType = itemType + item.rawData = rawData if rawLine.isEmpty { item.valid = false @@ -77,7 +74,7 @@ final class GopherRequestResponseHandler: ChannelInboundHandler { return item } - func parseGopherServerResponse(response: String) { + func parseGopherServerResponse(response: String, originalBytes: ByteBuffer) { var gopherServerResponse: [gopherItem] = [] print("parsing") @@ -87,8 +84,7 @@ final class GopherRequestResponseHandler: ChannelInboundHandler { for line in response.split(separator: "\r\n") { let lineItemType = getGopherFileType(item: "\(line.first ?? " ")") - let item = createGopherItem(rawLine: String(line), itemType: lineItemType) - print(item.message) + let item = createGopherItem(rawLine: String(line), itemType: lineItemType, rawData: originalBytes) gopherServerResponse.append(item) } @@ -96,23 +92,8 @@ final class GopherRequestResponseHandler: ChannelInboundHandler { print("done parsing") completion(.success(gopherServerResponse)) - - //completion(.success(response)) } } -public struct gopherItem { - - public var rawLine: String - public var message: String = "" - public var parsedItemType: gopherItemType = .info - public var host: String = "error.host" - public var port: Int = 1 - public var selector: String = "" - public var valid: Bool = true - - public init(rawLine: String) { - self.rawLine = rawLine - } -} + diff --git a/Sources/swiftGopherClient/gopherTypes.swift b/Sources/swiftGopherClient/gopherTypes.swift deleted file mode 100644 index efec92e..0000000 --- a/Sources/swiftGopherClient/gopherTypes.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// gopherTypes.swift -// -// -// Created by Navan Chauhan on 12/12/23. -// - -import Foundation - -/* - - From Wikipedia - - Canonical types - 0 Text file - 1 Gopher submenu - 2 CCSO Nameserver - 3 Error code returned by a Gopher server to indicate failure - 4 BinHex-encoded file (primarily for Macintosh computers) - 5 DOS file - 6 uuencoded file - 7 Gopher full-text search - 8 Telnet - 9 Binary file - + Mirror or alternate server (for load balancing or in case of primary server downtime) - g GIF file - I Image file - T Telnet 3270 - gopher+ types - : Bitmap image - ; Movie file - < Sound file - Non-canonical types - d Doc. Seen used alongside PDF's and .DOC's - h HTML file - i Informational message, widely used.[25] - p image file "(especially the png format)" - r document rtf file "rich text Format") - s Sound file (especially the WAV format) - P document pdf file "Portable Document Format") - X document xml file "eXtensive Markup Language" ) - */ - -public enum gopherItemType { - case text - case directory - case nameserver - case error - case binhex - case bindos - case uuencoded - case search - case telnet - case binary - case mirror - case gif - case image - case tn3270Session - case bitmap - case movie - case sound - case doc - case html - case info -} - -func getGopherFileType(item: String) -> gopherItemType { - switch item { - case "0": - return .text - case "1": - return .directory - case "2": - return .nameserver - case "3": - return .error - case "4": - return .binhex - case "5": - return .bindos - case "6": - return .uuencoded - case "7": - return .search - case "8": - return .telnet - case "9": - return .binary - case "+": - return .mirror - case "g": - return .gif - case "I": - return .image - case "T": - return .tn3270Session - case ":": - return .bitmap - case ";": - return .movie - case "<": - return .sound - case "d": - return .doc - case "h": - return .html - case "i": - return .info - case "p": - return .image - case "r": - return .doc - case "s": - return .doc - case "P": - return .doc - case "X": - return .doc - default: - return .info - } -} -- cgit v1.2.3