Files
MikeMail/MikeMail/MailModels.swift
T
2026-11-07 23:24:14 +01:00

116 lines
3.9 KiB
Swift

import Foundation
import Security
struct MailAccount: Identifiable, Codable, Hashable, Sendable {
var id = UUID()
var name = ""
var email = ""
var username = ""
var host = ""
var smtpPort: UInt16 = 587
var imapPort: UInt16 = 993
static let testAccount = MailAccount(name: "Testkonto", email: "test@kuebler-sulzbach.de",
username: "test@kuebler-sulzbach.de", host: "w0099985.kasserver.com")
}
struct MailFolder: Identifiable, Hashable, Codable, Sendable {
var id: String { accountID.uuidString + ":" + path }
let accountID: UUID
let path: String
let name: String
var selectable = true
var children: [MailFolder]?
var delimiter: String?
var specialUse: [String]?
var isInbox: Bool { path.uppercased() == "INBOX" }
var isTrash: Bool {
specialUse?.contains("\\trash") == true || ["trash", "papierkorb", "deleted messages", "deleted items"].contains(name.lowercased())
}
var icon: String {
switch path.lowercased() {
case "inbox": "tray"
case let value where value.contains("sent"): "paperplane"
case let value where value.contains("trash"): "trash"
case let value where value.contains("draft"): "doc"
default: "folder"
}
}
}
struct MailAttachment: Identifiable, Sendable {
var id = UUID()
let name: String
let mimeType: String
let data: Data
var contentID: String? = nil
}
struct MailMessage: Identifiable, Sendable {
let id: String
let sender: String
let recipient: String
let subject: String
let date: Date
let body: String
let attachments: [MailAttachment]
var isRead = false
var cc = ""
var replyTo = ""
var htmlBody: String? = nil
var displaySender: String { MailDisplay.sender(sender) }
var attachmentCount: Int { attachments.count }
}
enum MailError: LocalizedError {
case message(String)
case authentication(String)
var errorDescription: String? {
switch self { case .message(let text), .authentication(let text): text }
}
}
enum PasswordStore {
private static func query(_ id: UUID) -> [String: Any] {
[kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "de.mikemail.account",
kSecAttrAccount as String: id.uuidString]
}
static func save(_ password: String, for id: UUID) throws {
let attributes = [kSecValueData as String: Data(password.utf8)]
let status = SecItemUpdate(query(id) as CFDictionary, attributes as CFDictionary)
if status == errSecItemNotFound {
var entry = query(id)
entry.merge(attributes) { _, new in new }
entry[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
let result = SecItemAdd(entry as CFDictionary, nil)
guard result == errSecSuccess else { throw MailError.message("Passwort konnte nicht im Schlüsselbund gespeichert werden (\(result)).") }
} else if status != errSecSuccess {
throw MailError.message("Schlüsselbundfehler (\(status)).")
}
}
static func delete(for id: UUID) throws {
let status = SecItemDelete(query(id) as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw MailError.message("Passwort konnte nicht entfernt werden (\(status)).")
}
}
static func password(for id: UUID) throws -> String {
var entry = query(id)
entry[kSecReturnData as String] = true
entry[kSecMatchLimit as String] = kSecMatchLimitOne
var result: CFTypeRef?
let status = SecItemCopyMatching(entry as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data,
let value = String(data: data, encoding: .utf8) else {
throw MailError.message("Bitte unter „Konten“ das Passwort für dieses Konto speichern.")
}
return value
}
}