import Foundation enum NASProtocol: String, Codable, CaseIterable { case smb = "SMB" case sftp = "SFTP" var defaultPort: Int { switch self { case .smb: return 445 case .sftp: return 22 } } var localizedDescription: String { rawValue } } struct NASConnection: Codable, Identifiable, Equatable { var id: UUID = UUID() var host: String var port: Int var nasProtocol: NASProtocol var username: String var password: String // Runtime only — persisted in Keychain, not UserDefaults var remotePath: String var displayName: String // Excludes password so it is never written to UserDefaults. enum CodingKeys: String, CodingKey { case id, host, port, nasProtocol, username, remotePath, displayName } init( host: String, port: Int? = nil, nasProtocol: NASProtocol = .smb, username: String, password: String, remotePath: String = "/", displayName: String = "" ) { self.host = host self.port = port ?? nasProtocol.defaultPort self.nasProtocol = nasProtocol self.username = username self.password = password self.remotePath = remotePath self.displayName = displayName.isEmpty ? host : displayName } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) id = try c.decode(UUID.self, forKey: .id) host = try c.decode(String.self, forKey: .host) port = try c.decode(Int.self, forKey: .port) nasProtocol = try c.decode(NASProtocol.self, forKey: .nasProtocol) username = try c.decode(String.self, forKey: .username) remotePath = try c.decode(String.self, forKey: .remotePath) displayName = try c.decode(String.self, forKey: .displayName) password = "" // filled from Keychain by SavedConnections.load() } }