import NIO import NIOIMAPCore @preconcurrency import NIOIMAP @preconcurrency import NIOSSL actor IMAPConnection { private let host: String private let port: Int private let group: EventLoopGroup private var channel: Channel? private let responseHandler: IMAPResponseHandler init(host: String, port: Int) { self.host = host self.port = port self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1) self.responseHandler = IMAPResponseHandler() } func connect() async throws { let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration()) let handler = responseHandler let hostname = host let bootstrap = ClientBootstrap(group: group) .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .channelInitializer { channel in let sslHandler = try! NIOSSLClientHandler(context: sslContext, serverHostname: hostname) return channel.pipeline.addHandlers([ sslHandler, IMAPClientHandler(), handler, ]) } channel = try await bootstrap.connect(host: host, port: port).get() try await handler.waitForGreeting() } func sendCommand(_ tag: String, command: CommandStreamPart) async throws -> [Response] { guard let channel else { throw IMAPError.notConnected } let handler = responseHandler return try await withCheckedThrowingContinuation { continuation in handler.sendCommand(tag: tag, continuation: continuation) channel.writeAndFlush(IMAPClientHandler.Message.part(command), promise: nil) } } /// Send multiple command parts as a single multi-part command (e.g. APPEND). /// The tag must match the tag embedded in the first part. func sendMultiPartCommand(_ tag: String, parts: [CommandStreamPart]) async throws -> [Response] { guard let channel else { throw IMAPError.notConnected } let handler = responseHandler return try await withCheckedThrowingContinuation { continuation in handler.sendCommand(tag: tag, continuation: continuation) for part in parts { channel.writeAndFlush(IMAPClientHandler.Message.part(part), promise: nil) } } } func disconnect() async throws { try await channel?.close() channel = nil } func shutdown() async throws { try await group.shutdownGracefully() } }