This commit is contained in:
Dr. Michael Kübler
2026-11-07 23:24:14 +01:00
committed by Dr. Michael Kübler
parent 8e95cc3acd
commit c5e46946ac
16 changed files with 1073 additions and 197 deletions
+37 -4
View File
@@ -232,8 +232,39 @@ final class IMAPClient {
_ = try await command("UID STORE \(uid) \(read ? "+" : "-")FLAGS.SILENT (\\Seen)")
}
func move(uid: UInt64, to path: String) async throws {
_ = try await command("UID MOVE \(uid) \(Self.quote(path))")
struct AssignedUID: Equatable {
let validity: String
let uid: String
}
/// Single-message COPYUID/APPENDUID responses identify the local destination record.
static func assignedUID(_ lines: [String], sourceUID: UInt64? = nil) -> AssignedUID? {
let code = sourceUID == nil ? "APPENDUID" : "COPYUID"
for line in lines {
guard let start = line.range(of: "[" + code + " ", options: .caseInsensitive),
let end = line[start.upperBound...].firstIndex(of: "]") else { continue }
let parts = line[start.upperBound..<end].split(separator: " ").map(String.init)
guard parts.count == (sourceUID == nil ? 2 : 3),
let validity = UInt64(parts[0]), validity > 0,
let uid = UInt64(parts[parts.count - 1]), uid > 0 else { continue }
if let sourceUID, UInt64(parts[1]) != sourceUID { continue }
return AssignedUID(validity: String(validity), uid: String(uid))
}
return nil
}
/// Select and inspect only the requested UID, without enumerating the mailbox.
func messageInventory(folder: MailFolder, uid: UInt64) async throws -> MailboxInventory {
let selected = try await command("SELECT \(Self.quote(folder.path))")
let validity = try Self.uidValidity(from: selected.lines)
let fetched = try await command("UID FETCH \(uid) (UID FLAGS)")
return MailboxInventory(validity: validity, flags: try Self.parseFlags(fetched.lines))
}
@discardableResult
func move(uid: UInt64, to path: String) async throws -> AssignedUID? {
let result = try await command("UID MOVE \(uid) \(Self.quote(path))")
return Self.assignedUID(result.lines, sourceUID: uid)
}
/// UID EXPUNGE must never be replaced by EXPUNGE: other clients may have marked unrelated mail.
@@ -249,7 +280,8 @@ final class IMAPClient {
}
}
func append(_ message: Data, path: String, flags: [String]) async throws {
@discardableResult
func append(_ message: Data, path: String, flags: [String]) async throws -> AssignedUID? {
let allowed = ["\\Seen", "\\Answered", "\\Flagged", "\\Draft"]
let safeFlags = flags.filter { allowed.contains($0) }.joined(separator: " ")
sequence += 1
@@ -258,7 +290,8 @@ final class IMAPClient {
let ready = try await connection.readLine()
guard ready.hasPrefix("+") else { throw MailError.message("Nachricht konnte nicht im Ziel gespeichert werden: \(ready)") }
try await connection.send(message + Data("\r\n".utf8))
_ = try await response(tag: tag)
let result = try await response(tag: tag)
return Self.assignedUID(result.lines)
}
static func encodeMailbox(_ value: String) -> String {