Files
SwiftMail/Sources/SwiftIMAP/IMAP/Handler/SearchHandler.swift
Oliver Drobnik ce58c2a1b8 Implement SORT command in IMAP
Related to #11

Implement the `SORT` command to allow clients to search and sort messages on the server.

* **SearchCommand.swift**
  - Update `SearchCommand` to accept sort criteria as an optional parameter.
  - Modify `toTaggedCommand` method to handle sort criteria.

* **IMAPServer.swift**
  - Add sort criteria parameter to the existing `search` function.
  - Add capability check for `SORT` before executing `SearchCommand`.
  - Implement fallback to `SEARCH` if `SORT` is not supported.

* **SearchHandler.swift**
  - Update `SearchHandler` to handle responses for both `SEARCH` and `SORT` commands.
  - Modify error handling to include `SORT` command failures.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Cocoanetics/SwiftMail/issues/11?shareId=XXXX-XXXX-XXXX-XXXX).
2025-03-12 15:05:30 +01:00

81 lines
3.3 KiB
Swift

import Foundation
import NIO
import NIOIMAP
import NIOIMAPCore
/**
* Handler for IMAP SEARCH and SORT commands.
*
* This handler processes search and sort responses from the IMAP server and collects
* the message identifiers that match the search or sort criteria.
*
* The generic parameter T specifies the exact MessageIdentifier type to be collected.
*/
public final class SearchHandler<T: MessageIdentifier>: BaseIMAPCommandHandler<MessageIdentifierSet<T>>, IMAPCommandHandler {
public typealias ResultType = MessageIdentifierSet<T>
public typealias InboundIn = Response
public typealias InboundOut = Never
private var searchResults: [T] = []
override public func processResponse(_ response: Response) -> Bool {
let handled = super.processResponse(response)
// Check for untagged status responses that indicate errors
if case let .untagged(untagged) = response,
case let .conditionalState(status) = untagged {
switch status {
case .bad(let responseText):
// Handle BAD response (protocol error)
failWithError(IMAPError.commandFailed("Search/Sort failed: BAD \(responseText.text)"))
return true
case .no(let responseText):
// Handle NO response (operational error)
failWithError(IMAPError.commandFailed("Search/Sort failed: NO \(responseText.text)"))
return true
default:
// Other status responses are handled by the base class
break
}
}
// Check for search or sort response data using proper pattern matching
if case let .untagged(untagged) = response,
case let .mailboxData(mailboxData) = untagged,
case let .search(ids, _) = mailboxData {
// Convert the IDs to the appropriate MessageIdentifier type
let results = ids.map { T.init(UInt32($0)) }
searchResults.append(contentsOf: results)
print("Extracted \(results.count) message identifiers from search/sort response")
}
return handled
}
override public func handleTaggedOKResponse(_ response: TaggedResponse) {
// When we receive an OK response, the search or sort is complete
// Return the collected search or sort results as a MessageIdentifierSet
print("Search/Sort complete, returning \(searchResults.count) results")
// Create a MessageIdentifierSet from the array of results
var resultSet = MessageIdentifierSet<T>()
for identifier in searchResults {
resultSet.insert(identifier)
}
succeedWithResult(resultSet)
}
override public func handleTaggedErrorResponse(_ response: TaggedResponse) {
// If the search or sort command fails, report the error with more specific information
switch response.state {
case .bad(let responseText):
failWithError(IMAPError.commandFailed("Search/Sort failed: BAD \(responseText.text)"))
case .no(let responseText):
failWithError(IMAPError.commandFailed("Search/Sort failed: NO \(responseText.text)"))
default:
failWithError(IMAPError.commandFailed("Search/Sort failed: \(String(describing: response.state))"))
}
}
}