Skip to content

Commit

Permalink
Merge branch 'unselect'
Browse files Browse the repository at this point in the history
  • Loading branch information
odrobnik committed Mar 7, 2025
2 parents c8608b8 + 824ff93 commit d91bd6f
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<EnvironmentVariable
key = "ENABLE_DEBUG_OUTPUT"
value = "1"
isEnabled = "NO">
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
Expand Down
2 changes: 1 addition & 1 deletion Implemented.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
- [x] COPY - Copy messages to another mailbox.
- [x] MOVE - Move messages to another mailbox.
- [x] EXPUNGE - Expunge deleted messages from the selected mailbox.
- [ ] UNSELECT - Allows the client to unselect the current mailbox without selecting a new one.
- [x] UNSELECT - Allows the client to unselect the current mailbox without selecting a new one.

#### Message Commands

Expand Down
30 changes: 30 additions & 0 deletions Sources/SwiftIMAP/IMAP/Commands/UnselectCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation
import NIOIMAPCore

/**
Command to unselect the currently selected mailbox without expunging deleted messages

The UNSELECT command is an extension to the core IMAP protocol defined in RFC 3691.
It allows a client to deselect the current mailbox without implicitly causing an expunge
of messages marked for deletion, as the CLOSE command does.
*/
public struct UnselectCommand: IMAPCommand {
public typealias ResultType = Void
public typealias HandlerType = UnselectHandler

public var handlerType: HandlerType.Type {
return UnselectHandler.self
}

public init() {}

public func validate() throws {
// No validation needed for UNSELECT command
}

public func toTaggedCommand(tag: String) -> TaggedCommand {
// Using a raw string command since UNSELECT is not in the standard Command enum
// The UNSELECT command takes no parameters
return TaggedCommand(tag: tag, command: .unselect)
}
}
41 changes: 41 additions & 0 deletions Sources/SwiftIMAP/IMAP/Handler/UnselectHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation
import NIOIMAPCore
import NIO
import Logging

/**
Handler for the UNSELECT command

This handler processes responses from the IMAP server to the UNSELECT command.
The UNSELECT command is an extension to IMAP defined in RFC 3691 that allows
a client to deselect the current mailbox without expunging deleted messages.
*/
public final class UnselectHandler: BaseIMAPCommandHandler<Void>, IMAPCommandHandler {
public typealias ResultType = Void
public typealias InboundIn = Response
public typealias InboundOut = Never

override public func processResponse(_ response: Response) -> Bool {
// Call the base class implementation to buffer the response
let handled = super.processResponse(response)

// Process the response
if case .tagged(let tagged) = response, tagged.tag == commandTag {
// This is our tagged response, handle it
switch tagged.state {
case .ok:
// UNSELECT succeeded
succeedWithResult(())
case .no(let text):
// UNSELECT failed with NO response
failWithError(IMAPError.commandFailed("NO response: \(text)"))
case .bad(let text):
// UNSELECT failed with BAD response (likely not supported)
failWithError(IMAPError.commandFailed("BAD response: \(text)"))
}
return true
}

return handled
}
}
3 changes: 3 additions & 0 deletions Sources/SwiftIMAP/IMAPError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum IMAPError: Error {
case storeFailed(String)
case expungeFailed(String)
case moveFailed(String)
case commandNotSupported(String)
}

// Add CustomStringConvertible conformance for better error messages
Expand Down Expand Up @@ -53,6 +54,8 @@ extension IMAPError: CustomStringConvertible {
return "Expunge failed: \(reason)"
case .moveFailed(let reason):
return "Move failed: \(reason)"
case .commandNotSupported(let reason):
return "Command not supported: \(reason)"
}
}
}
18 changes: 18 additions & 0 deletions Sources/SwiftIMAP/IMAPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ public actor IMAPServer {
try await executeCommand(command)
}

/**
Unselect the currently selected mailbox without expunging deleted messages

This is an IMAP extension command (RFC 3691) that might not be supported by all servers.
If the server does not support UNSELECT, an IMAPError will be thrown.

- Throws: An error if the unselect operation fails or is not supported
*/
public func unselectMailbox() async throws {
// Check if the server supports UNSELECT capability
if !capabilities.contains(.unselect) {
throw IMAPError.commandNotSupported("UNSELECT command not supported by server")
}

let command = UnselectCommand()
try await executeCommand(command)
}

/**
Logout from the IMAP server
- Throws: An error if the logout fails
Expand Down

0 comments on commit d91bd6f

Please sign in to comment.