aboutsummaryrefslogtreecommitdiff
path: root/Sources/swiftGopherClient/gopherClient.swift
blob: 802d646ad0cc3689c6e8cc52b95863bf9499e2af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//
//  gopherClient.swift
//
//
//  Created by Navan Chauhan on 12/12/23.
//

import Foundation
import GopherHelpers
import NIO
import NIOTransportServices

/// `GopherClient` is a class for handling network connections and requests to Gopher servers.
///
/// It utilizes `NIOTSEventLoopGroup` on iOS/macOS (Not sure why you would run this on watchOS/tvOS but it supports that as well) for network operations, falling back to `MultiThreadedEventLoopGroup` otherwise.
public class GopherClient {
  private let group: EventLoopGroup

  /// Initializes a new instance of `GopherClient`.
  ///
  /// It automatically chooses the appropriate `EventLoopGroup` based on the running platform.
  public init() {
    #if os(Linux)
      self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
    #else
      if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, visionOS 1.0, *) {
        self.group = NIOTSEventLoopGroup()
      } else {
        self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
      }
    #endif
  }

  deinit {
    try? group.syncShutdownGracefully()
  }

  /// Sends a request to a Gopher server.
  ///
  /// - Parameters:
  ///   - host: The host address of the Gopher server.
  ///   - port: The port of the Gopher server. Defaults to 70.
  ///   - message: The message to be sent to the server.
  ///   - completion: A closure that handles the result of the request.
  ///
  /// The method asynchronously establishes a connection, sends the request, and calls the completion handler with the result.
  public func sendRequest(
    to host: String, port: Int = 70, message: String,
    completion: @escaping (Result<[gopherItem], Error>) -> Void
  ) {
    #if os(Linux)
      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))
        }
      }
    #else

      if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, visionOS 1.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))
          }
        }
      } 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))
          }
        }
      }
    #endif

  }

  /// Shuts down the event loop group, releasing any resources.
  ///
  /// This method is called during deinitialization to ensure clean shutdown of network resources.
  private func shutdownEventLoopGroup() {
    do {
      try group.syncShutdownGracefully()
    } catch {
      print("Error shutting down event loop group: \(error)")
    }
  }
}