add IMAPIdleHandler: NIO channel handler for IDLE event streaming
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import NIO
|
||||
import NIOIMAPCore
|
||||
import NIOIMAP
|
||||
|
||||
/// Events emitted by the IDLE handler
|
||||
public enum IMAPIdleEvent: Sendable {
|
||||
case exists(Int)
|
||||
case expunge(Int)
|
||||
case idleTerminated
|
||||
}
|
||||
|
||||
/// NIO ChannelInboundHandler that processes untagged responses during IMAP IDLE.
|
||||
/// Unlike the standard IMAPResponseHandler (which uses CheckedContinuation for tagged responses),
|
||||
/// this handler uses AsyncStream to deliver a continuous stream of events.
|
||||
final class IMAPIdleHandler: ChannelInboundHandler, @unchecked Sendable {
|
||||
typealias InboundIn = Response
|
||||
|
||||
private let continuation: AsyncStream<IMAPIdleEvent>.Continuation
|
||||
private var idleTag: String?
|
||||
|
||||
init(continuation: AsyncStream<IMAPIdleEvent>.Continuation) {
|
||||
self.continuation = continuation
|
||||
}
|
||||
|
||||
func setIdleTag(_ tag: String) {
|
||||
idleTag = tag
|
||||
}
|
||||
|
||||
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||
let response = unwrapInboundIn(data)
|
||||
|
||||
switch response {
|
||||
case .untagged(let payload):
|
||||
switch payload {
|
||||
case .mailboxData(let data):
|
||||
switch data {
|
||||
case .exists(let count):
|
||||
continuation.yield(.exists(count))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .messageData(let data):
|
||||
switch data {
|
||||
case .expunge(let seqNum):
|
||||
continuation.yield(.expunge(Int(seqNum.rawValue)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .tagged(let tagged):
|
||||
if tagged.tag == idleTag {
|
||||
continuation.yield(.idleTerminated)
|
||||
}
|
||||
case .idleStarted:
|
||||
// Server acknowledged IDLE — we're now idling
|
||||
break
|
||||
case .fetch, .authenticationChallenge, .fatal:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
||||
continuation.finish()
|
||||
context.close(promise: nil)
|
||||
}
|
||||
|
||||
func channelInactive(context: ChannelHandlerContext) {
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user