Files
SwiftMail/Sources/SwiftIMAP/IMAP/Handler/CommandHandler.swift

168 lines
6.3 KiB
Swift

// CommandHandler.swift
// Protocol for command-specific IMAP handlers
import Foundation
import Logging
@preconcurrency import NIOIMAP
import NIOIMAPCore
import NIO
import NIOConcurrencyHelpers
/// Protocol for command-specific IMAP handlers
/// These handlers are added to the pipeline when a command is sent and removed when the response is received
public protocol CommandHandler: ChannelInboundHandler where InboundIn == Response {
/// The tag associated with this command (optional)
var commandTag: String? { get }
/// Whether this handler has completed processing
var isCompleted: Bool { get }
/// Handle the completion of this command
/// - Parameter context: The channel handler context
func handleCompletion(context: ChannelHandlerContext)
}
/// Base implementation of CommandHandler with common functionality
public class BaseIMAPCommandHandler<ResultType>: CommandHandler, RemovableChannelHandler {
public typealias InboundIn = Response
public typealias InboundOut = Response
/// The tag associated with this command (optional)
public let commandTag: String?
/// Whether this handler has completed processing
public private(set) var isCompleted: Bool = false
/// Lock for thread-safe access to mutable properties
internal let lock = NIOLock()
/// Buffer for logging during command processing
private var logBuffer: [String] = []
/// Promise for the command result
internal let promise: EventLoopPromise<ResultType>
/// Initialize a new command handler
/// - Parameters:
/// - commandTag: The tag associated with this command
/// - promise: The promise to fulfill when the command completes
public init(commandTag: String, promise: EventLoopPromise<ResultType>) {
self.commandTag = commandTag
self.promise = promise
}
/// Handle the completion of this command
/// - Parameter context: The channel handler context
public func handleCompletion(context: ChannelHandlerContext) {
lock.withLock {
isCompleted = true
}
// Remove this handler from the pipeline
context.pipeline.removeHandler(self, promise: nil)
}
/// Succeed the promise with a result
/// - Parameter result: The result to succeed with
internal func succeedWithResult(_ result: ResultType) {
promise.succeed(result)
}
/// Fail the promise with an error
/// - Parameter error: The error to fail with
internal func failWithError(_ error: Error) {
promise.fail(error)
}
/// Process an incoming response
/// - Parameter response: The response to process
/// - Returns: Whether the response was handled by this handler
public func processResponse(_ response: Response) -> Bool {
// If commandTag is nil, we're only interested in untagged responses
if commandTag == nil {
return handleUntaggedResponse(response)
}
// Check if this is a tagged response that matches our command tag
if case .tagged(let taggedResponse) = response, taggedResponse.tag == commandTag {
// Check the response status
if case .ok = taggedResponse.state {
// Subclasses should override handleTaggedOKResponse to handle the OK response
handleTaggedOKResponse(taggedResponse)
} else {
// Failed response, fail the promise with an error
handleTaggedErrorResponse(taggedResponse)
}
return true
}
// Not our tagged response, see if subclasses want to handle untagged responses
let handled = handleUntaggedResponse(response)
return handled
}
/// Handle a tagged OK response
/// Subclasses should override this method to handle successful responses
/// - Parameter response: The tagged response
open func handleTaggedOKResponse(_ response: TaggedResponse) {
// Default implementation succeeds with Void for handlers that don't need a result
// This only works for ResultType == Void, otherwise subclasses must override
if ResultType.self == Void.self {
succeedWithResult(() as! ResultType)
} else {
// If ResultType is not Void, subclasses must override this method
fatalError("Subclasses must override handleTaggedOKResponse for non-Void result types")
}
}
/// Handle a tagged error response
/// Subclasses can override this method to handle error responses differently
/// - Parameter response: The tagged response
open func handleTaggedErrorResponse(_ response: TaggedResponse) {
// Default implementation fails with a generic error
failWithError(IMAPError.commandFailed(String(describing: response.state)))
}
/// Handle an untagged response
/// Subclasses should override this method to handle untagged responses
/// - Parameter response: The untagged response
/// - Returns: Whether the response was handled by this handler
open func handleUntaggedResponse(_ response: Response) -> Bool {
// Default implementation doesn't handle untagged responses
return false
}
/// Channel read method from ChannelInboundHandler
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let response = unwrapInboundIn(data)
// Process the response (which will buffer it for logging)
let handled = processResponse(response)
// If this was our tagged response, handle completion
if handled {
handleCompletion(context: context)
}
// Always forward the response to the next handler
context.fireChannelRead(data)
}
/// Error caught method from ChannelInboundHandler
public func errorCaught(context: ChannelHandlerContext, error: Error) {
// Handle the error
handleError(error)
// Forward the error to the next handler
context.fireErrorCaught(error)
}
/// Handle an error
/// This method should be overridden by subclasses
public func handleError(_ error: Error) {
// Fail the promise with the error
failWithError(error)
}
}