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