From c07b57e395f83d78661af6311dc5dcf6765bee4a Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:53:08 +0200 Subject: [PATCH] Move extensions for models from APIActor.swift to respective files Port api load to DrawerView --- .../OpenHABCore/Model/CGFloatExtension.swift | 24 ++ .../Model/OpenHABCommandDescription.swift | 10 + .../Model/OpenHABCommandOptions.swift | 10 + .../OpenHABCore/Model/OpenHABItem.swift | 13 +- .../OpenHABCore/Model/OpenHABOptions.swift | 10 + .../OpenHABCore/Model/OpenHABPage.swift | 17 + .../OpenHABCore/Model/OpenHABSitemap.swift | 44 +-- .../Model/OpenHABSitemapWidgetEvent.swift | 93 ++++++ .../Model/OpenHABStateDescription.swift | 10 + .../OpenHABCore/Model/OpenHABUiTile.swift | 10 +- .../OpenHABCore/Model/OpenHABWidget.swift | 53 +++ .../Model/OpenHABWidgetMapping.swift | 6 + .../Sources/OpenHABCore/Util/APIActor.swift | 301 +----------------- .../OpenHABCore/Util/APIActorDelegate.swift | 76 +++++ openHAB/DrawerView.swift | 98 ++---- 15 files changed, 353 insertions(+), 422 deletions(-) create mode 100644 OpenHABCore/Sources/OpenHABCore/Model/CGFloatExtension.swift create mode 100644 OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemapWidgetEvent.swift create mode 100644 OpenHABCore/Sources/OpenHABCore/Util/APIActorDelegate.swift diff --git a/OpenHABCore/Sources/OpenHABCore/Model/CGFloatExtension.swift b/OpenHABCore/Sources/OpenHABCore/Model/CGFloatExtension.swift new file mode 100644 index 00000000..97d44787 --- /dev/null +++ b/OpenHABCore/Sources/OpenHABCore/Model/CGFloatExtension.swift @@ -0,0 +1,24 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Foundation + +extension CGFloat { + init(state string: String, divisor: Float) { + let numberFormatter = NumberFormatter() + numberFormatter.locale = Locale(identifier: "US") + if let number = numberFormatter.number(from: string) { + self.init(number.floatValue / divisor) + } else { + self.init(0) + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandDescription.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandDescription.swift index ce8eff96..1b96cb5f 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandDescription.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandDescription.swift @@ -30,3 +30,13 @@ extension OpenHABCommandDescription.CodingData { OpenHABCommandDescription(commandOptions: commandOptions) } } + +extension OpenHABCommandDescription { + convenience init?(_ commands: Components.Schemas.CommandDescription?) { + if let commands { + self.init(commandOptions: commands.commandOptions?.compactMap { OpenHABCommandOptions($0) }) + } else { + return nil + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandOptions.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandOptions.swift index 3ace621a..383a1a75 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandOptions.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABCommandOptions.swift @@ -20,3 +20,13 @@ public class OpenHABCommandOptions: Decodable { self.label = label } } + +extension OpenHABCommandOptions { + convenience init?(_ options: Components.Schemas.CommandOption?) { + if let options { + self.init(command: options.command.orEmpty, label: options.label.orEmpty) + } else { + return nil + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift index 175f4ef6..7da0e102 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift @@ -148,14 +148,13 @@ public extension OpenHABItem.CodingData { } } -extension CGFloat { - init(state string: String, divisor: Float) { - let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: "US") - if let number = numberFormatter.number(from: string) { - self.init(number.floatValue / divisor) +extension OpenHABItem { + convenience init?(_ item: Components.Schemas.EnrichedItemDTO?) { + if let item { + // swiftlint:disable:next line_length + self.init(name: item.name.orEmpty, type: item._type.orEmpty, state: item.state.orEmpty, link: item.link.orEmpty, label: item.label.orEmpty, groupType: nil, stateDescription: OpenHABStateDescription(item.stateDescription), commandDescription: OpenHABCommandDescription(item.commandDescription), members: [], category: item.category, options: []) } else { - self.init(0) + return nil } } } diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABOptions.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABOptions.swift index d9a9983e..620a8458 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABOptions.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABOptions.swift @@ -20,3 +20,13 @@ public class OpenHABOptions: Decodable { self.label = label } } + +extension OpenHABOptions { + convenience init?(_ options: Components.Schemas.StateOption?) { + if let options { + self.init(value: options.value.orEmpty, label: options.label.orEmpty) + } else { + return nil + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABPage.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABPage.swift index 0c9632df..27b2c8fd 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABPage.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABPage.swift @@ -86,3 +86,20 @@ public extension OpenHABPage.CodingData { return OpenHABPage(pageId: pageId.orEmpty, title: title.orEmpty, link: link.orEmpty, leaf: leaf ?? false, widgets: mappedWidgets, icon: icon.orEmpty) } } + +extension OpenHABPage { + convenience init?(_ page: Components.Schemas.PageDTO?) { + if let page { + self.init( + pageId: page.id.orEmpty, + title: page.title.orEmpty, + link: page.link.orEmpty, + leaf: page.leaf ?? false, + widgets: page.widgets?.compactMap { OpenHABWidget($0) } ?? [], + icon: page.icon.orEmpty + ) + } else { + return nil + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemap.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemap.swift index 00a03e86..8f654bcd 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemap.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemap.swift @@ -51,42 +51,14 @@ public final class OpenHABSitemap: NSObject { } } -public extension OpenHABSitemap { - struct CodingData: Decodable { - public let name: String - public let label: String - public let page: OpenHABPage.CodingData? - public let link: String - public let icon: String? - - private enum CodingKeys: String, CodingKey { - case page = "homepage" - case name - case label - case link - case icon - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - name = try container.decode(forKey: .name) - label = try container.decode(forKey: .label, default: name) - page = try container.decode(forKey: .page) - link = try container.decode(forKey: .link) - icon = try container.decodeIfPresent(forKey: .icon) - } - } -} - -public extension OpenHABSitemap.CodingData { - var openHABSitemap: OpenHABSitemap { - OpenHABSitemap( - name: name, - icon: icon.orEmpty, - label: label, - link: link, - page: page?.openHABSitemapPage +extension OpenHABSitemap { + convenience init(_ sitemap: Components.Schemas.SitemapDTO) { + self.init( + name: sitemap.name.orEmpty, + icon: sitemap.icon.orEmpty, + label: sitemap.label.orEmpty, + link: sitemap.link.orEmpty, + page: OpenHABPage(sitemap.homepage) ) } } diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemapWidgetEvent.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemapWidgetEvent.swift new file mode 100644 index 00000000..22447103 --- /dev/null +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABSitemapWidgetEvent.swift @@ -0,0 +1,93 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Foundation + +public class OpenHABSitemapWidgetEvent { + var sitemapName: String? + var pageId: String? + var widgetId: String? + var label: String? + var labelSource: String? + var icon: String? + var reloadIcon: Bool? + var labelcolor: String? + var valuecolor: String? + var iconcolor: String? + var visibility: Bool? + var state: String? + var enrichedItem: OpenHABItem? + var descriptionChanged: Bool? + + init(sitemapName: String? = nil, pageId: String? = nil, widgetId: String? = nil, label: String? = nil, labelSource: String? = nil, icon: String? = nil, reloadIcon: Bool? = nil, labelcolor: String? = nil, valuecolor: String? = nil, iconcolor: String? = nil, visibility: Bool? = nil, state: String? = nil, enrichedItem: OpenHABItem? = nil, descriptionChanged: Bool? = nil) { + self.sitemapName = sitemapName + self.pageId = pageId + self.widgetId = widgetId + self.label = label + self.labelSource = labelSource + self.icon = icon + self.reloadIcon = reloadIcon + self.labelcolor = labelcolor + self.valuecolor = valuecolor + self.iconcolor = iconcolor + self.visibility = visibility + self.state = state + self.enrichedItem = enrichedItem + self.descriptionChanged = descriptionChanged + } + + convenience init?(_ event: Components.Schemas.SitemapWidgetEvent?) { + guard let event else { return nil } + // swiftlint:disable:next line_length + self.init(sitemapName: event.sitemapName, pageId: event.pageId, widgetId: event.widgetId, label: event.label, labelSource: event.labelSource, icon: event.icon, reloadIcon: event.reloadIcon, labelcolor: event.labelcolor, valuecolor: event.valuecolor, iconcolor: event.iconcolor, visibility: event.visibility, state: event.state, enrichedItem: OpenHABItem(event.item), descriptionChanged: event.descriptionChanged) + } +} + +extension OpenHABSitemapWidgetEvent: CustomStringConvertible { + public var description: String { + "\(widgetId ?? "") \(label ?? "") \(enrichedItem?.state ?? "")" + } +} + +public extension OpenHABSitemapWidgetEvent { + struct CodingData: Decodable, Hashable, Equatable { + public static func == (lhs: OpenHABSitemapWidgetEvent.CodingData, rhs: OpenHABSitemapWidgetEvent.CodingData) -> Bool { + lhs.widgetId == rhs.widgetId + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(widgetId) + } + + var sitemapName: String? + var pageId: String? + var widgetId: String? + var label: String? + var labelSource: String? + var icon: String? + var reloadIcon: Bool? + var labelcolor: String? + var valuecolor: String? + var iconcolor: String? + var visibility: Bool? +// var state: String? + var item: OpenHABItem.CodingData? + var descriptionChanged: Bool? + var link: String? + } +} + +extension OpenHABSitemapWidgetEvent.CodingData { + var openHABSitemapWidgetEvent: OpenHABSitemapWidgetEvent { + // swiftlint:disable:next line_length + OpenHABSitemapWidgetEvent(sitemapName: sitemapName, pageId: pageId, widgetId: widgetId, label: label, labelSource: labelSource, icon: icon, reloadIcon: reloadIcon, labelcolor: labelcolor, valuecolor: valuecolor, iconcolor: iconcolor, visibility: visibility, enrichedItem: item?.openHABItem, descriptionChanged: descriptionChanged) + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift index 439812c8..9e17b3c9 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift @@ -59,3 +59,13 @@ extension OpenHABStateDescription.CodingData { OpenHABStateDescription(minimum: minimum, maximum: maximum, step: step, readOnly: readOnly, options: options, pattern: pattern) } } + +extension OpenHABStateDescription { + convenience init?(_ state: Components.Schemas.StateDescription?) { + if let state { + self.init(minimum: state.minimum, maximum: state.maximum, step: state.step, readOnly: state.readOnly, options: state.options?.compactMap { OpenHABOptions($0) }, pattern: state.pattern) + } else { + return nil + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABUiTile.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABUiTile.swift index ffac41ee..06705597 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABUiTile.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABUiTile.swift @@ -11,7 +11,7 @@ import Foundation -public class OpenHABUiTile: Decodable { +public class OpenHABUiTile { public var name = "" public var url = "" public var imageUrl = "" @@ -23,10 +23,8 @@ public class OpenHABUiTile: Decodable { } } -public extension OpenHABUiTile { - struct CodingData: Decodable { - public let name: String - public let url: String - public let imageUrl: String +extension OpenHABUiTile { + convenience init(_ tile: Components.Schemas.TileDTO) { + self.init(name: tile.name.orEmpty, url: tile.url.orEmpty, imageUrl: tile.imageUrl.orEmpty) } } diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift index a7c4dc84..8e5afd05 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift @@ -305,3 +305,56 @@ extension [OpenHABWidget] { } } } + +extension OpenHABWidget { + convenience init(_ widget: Components.Schemas.WidgetDTO) { + self.init( + widgetId: widget.widgetId.orEmpty, + label: widget.label.orEmpty, + icon: widget.icon.orEmpty, + type: OpenHABWidget.WidgetType(rawValue: widget._type!), + url: widget.url, + period: widget.period, + minValue: widget.minValue, + maxValue: widget.maxValue, + step: widget.step, + refresh: widget.refresh.map(Int.init), + height: 50, // TODO: + isLeaf: true, + iconColor: widget.iconcolor, + labelColor: widget.labelcolor, + valueColor: widget.valuecolor, + service: widget.service, + state: widget.state, + text: "", + legend: widget.legend, + encoding: widget.encoding, + item: OpenHABItem(widget.item), + linkedPage: OpenHABPage(widget.linkedPage), + mappings: widget.mappings?.compactMap(OpenHABWidgetMapping.init) ?? [], + widgets: widget.widgets?.compactMap { OpenHABWidget($0) } ?? [], + visibility: widget.visibility, + switchSupport: widget.switchSupport, + forceAsItem: widget.forceAsItem + ) + } +} + +extension OpenHABWidget { + func update(with event: OpenHABSitemapWidgetEvent) { + state = event.state ?? state + icon = event.icon ?? icon + label = event.label ?? label + iconColor = event.iconcolor ?? "" + labelcolor = event.labelcolor ?? "" + valuecolor = event.valuecolor ?? "" + visibility = event.visibility ?? visibility + + if let enrichedItem = event.enrichedItem { + if let link = item?.link { + enrichedItem.link = link + } + item = enrichedItem + } + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidgetMapping.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidgetMapping.swift index 6d2d1499..81775c25 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidgetMapping.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidgetMapping.swift @@ -20,3 +20,9 @@ public class OpenHABWidgetMapping: NSObject, Decodable { self.label = label.orEmpty } } + +extension OpenHABWidgetMapping { + convenience init(_ mapping: Components.Schemas.MappingDTO) { + self.init(command: mapping.command, label: mapping.label) + } +} diff --git a/OpenHABCore/Sources/OpenHABCore/Util/APIActor.swift b/OpenHABCore/Sources/OpenHABCore/Util/APIActor.swift index 84134177..e2d1b4ea 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/APIActor.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/APIActor.swift @@ -23,7 +23,9 @@ public protocol OpenHABUiTileService { func openHABTiles() async throws -> [OpenHABUiTile] } -// swiftlint:disable file_types_order +public enum APIActorError: Error { + case undocumented +} public actor APIActor { var api: APIProtocol @@ -99,10 +101,6 @@ public actor APIActor { } } -public enum APIActorError: Error { - case undocumented -} - extension APIActor: OpenHABSitemapsService { public func openHABSitemaps() async throws -> [OpenHABSitemap] { // swiftformat:disable:next redundantSelf @@ -209,296 +207,3 @@ public extension APIActor { _ = try response.ok } } - -// MARK: - URLSessionDelegate for Client Certificates and Basic Auth - -class APIActorDelegate: NSObject, URLSessionDelegate, URLSessionTaskDelegate { - private let username: String - private let password: String - - init(username: String, password: String) { - self.username = username - self.password = password - } - - public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - await urlSessionInternal(session, task: nil, didReceive: challenge) - } - - public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - await urlSessionInternal(session, task: task, didReceive: challenge) - } - - private func urlSessionInternal(_ session: URLSession, task: URLSessionTask?, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - os_log("URLAuthenticationChallenge: %{public}@", log: .networking, type: .info, challenge.protectionSpace.authenticationMethod) - let authenticationMethod = challenge.protectionSpace.authenticationMethod - switch authenticationMethod { - case NSURLAuthenticationMethodServerTrust: - return await handleServerTrust(challenge: challenge) - case NSURLAuthenticationMethodDefault, NSURLAuthenticationMethodHTTPBasic: - if let task { - task.authAttemptCount += 1 - if task.authAttemptCount > 1 { - return (.cancelAuthenticationChallenge, nil) - } else { - return await handleBasicAuth(challenge: challenge) - } - } else { - return await handleBasicAuth(challenge: challenge) - } - case NSURLAuthenticationMethodClientCertificate: - return await handleClientCertificateAuth(challenge: challenge) - default: - return (.performDefaultHandling, nil) - } - } - - private func handleServerTrust(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - guard let serverTrust = challenge.protectionSpace.serverTrust else { - return (.performDefaultHandling, nil) - } - let credential = URLCredential(trust: serverTrust) - return (.useCredential, credential) - } - - private func handleBasicAuth(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - let credential = URLCredential(user: username, password: password, persistence: .forSession) - return (.useCredential, credential) - } - - private func handleClientCertificateAuth(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - let certificateManager = ClientCertificateManager() - let (disposition, credential) = certificateManager.evaluateTrust(with: challenge) - return (disposition, credential) - } -} - -extension OpenHABWidget { - func update(with event: OpenHABSitemapWidgetEvent) { - state = event.state ?? state - icon = event.icon ?? icon - label = event.label ?? label - iconColor = event.iconcolor ?? "" - labelcolor = event.labelcolor ?? "" - valuecolor = event.valuecolor ?? "" - visibility = event.visibility ?? visibility - - if let enrichedItem = event.enrichedItem { - if let link = item?.link { - enrichedItem.link = link - } - item = enrichedItem - } - } -} - -public class OpenHABSitemapWidgetEvent { - var sitemapName: String? - var pageId: String? - var widgetId: String? - var label: String? - var labelSource: String? - var icon: String? - var reloadIcon: Bool? - var labelcolor: String? - var valuecolor: String? - var iconcolor: String? - var visibility: Bool? - var state: String? - var enrichedItem: OpenHABItem? - var descriptionChanged: Bool? - - init(sitemapName: String? = nil, pageId: String? = nil, widgetId: String? = nil, label: String? = nil, labelSource: String? = nil, icon: String? = nil, reloadIcon: Bool? = nil, labelcolor: String? = nil, valuecolor: String? = nil, iconcolor: String? = nil, visibility: Bool? = nil, state: String? = nil, enrichedItem: OpenHABItem? = nil, descriptionChanged: Bool? = nil) { - self.sitemapName = sitemapName - self.pageId = pageId - self.widgetId = widgetId - self.label = label - self.labelSource = labelSource - self.icon = icon - self.reloadIcon = reloadIcon - self.labelcolor = labelcolor - self.valuecolor = valuecolor - self.iconcolor = iconcolor - self.visibility = visibility - self.state = state - self.enrichedItem = enrichedItem - self.descriptionChanged = descriptionChanged - } - - convenience init?(_ event: Components.Schemas.SitemapWidgetEvent?) { - guard let event else { return nil } - // swiftlint:disable:next line_length - self.init(sitemapName: event.sitemapName, pageId: event.pageId, widgetId: event.widgetId, label: event.label, labelSource: event.labelSource, icon: event.icon, reloadIcon: event.reloadIcon, labelcolor: event.labelcolor, valuecolor: event.valuecolor, iconcolor: event.iconcolor, visibility: event.visibility, state: event.state, enrichedItem: OpenHABItem(event.item), descriptionChanged: event.descriptionChanged) - } -} - -extension OpenHABSitemapWidgetEvent: CustomStringConvertible { - public var description: String { - "\(widgetId ?? "") \(label ?? "") \(enrichedItem?.state ?? "")" - } -} - -public extension OpenHABSitemapWidgetEvent { - struct CodingData: Decodable, Hashable, Equatable { - public static func == (lhs: OpenHABSitemapWidgetEvent.CodingData, rhs: OpenHABSitemapWidgetEvent.CodingData) -> Bool { - lhs.widgetId == rhs.widgetId - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(widgetId) - } - - var sitemapName: String? - var pageId: String? - var widgetId: String? - var label: String? - var labelSource: String? - var icon: String? - var reloadIcon: Bool? - var labelcolor: String? - var valuecolor: String? - var iconcolor: String? - var visibility: Bool? -// var state: String? - var item: OpenHABItem.CodingData? - var descriptionChanged: Bool? - var link: String? - } -} - -extension OpenHABSitemapWidgetEvent.CodingData { - var openHABSitemapWidgetEvent: OpenHABSitemapWidgetEvent { - // swiftlint:disable:next line_length - OpenHABSitemapWidgetEvent(sitemapName: sitemapName, pageId: pageId, widgetId: widgetId, label: label, labelSource: labelSource, icon: icon, reloadIcon: reloadIcon, labelcolor: labelcolor, valuecolor: valuecolor, iconcolor: iconcolor, visibility: visibility, enrichedItem: item?.openHABItem, descriptionChanged: descriptionChanged) - } -} - -extension OpenHABUiTile { - convenience init(_ tile: Components.Schemas.TileDTO) { - self.init(name: tile.name.orEmpty, url: tile.url.orEmpty, imageUrl: tile.imageUrl.orEmpty) - } -} - -extension OpenHABSitemap { - convenience init(_ sitemap: Components.Schemas.SitemapDTO) { - self.init( - name: sitemap.name.orEmpty, - icon: sitemap.icon.orEmpty, - label: sitemap.label.orEmpty, - link: sitemap.link.orEmpty, - page: OpenHABPage(sitemap.homepage) - ) - } -} - -extension OpenHABPage { - convenience init?(_ page: Components.Schemas.PageDTO?) { - if let page { - self.init( - pageId: page.id.orEmpty, - title: page.title.orEmpty, - link: page.link.orEmpty, - leaf: page.leaf ?? false, - widgets: page.widgets?.compactMap { OpenHABWidget($0) } ?? [], - icon: page.icon.orEmpty - ) - } else { - return nil - } - } -} - -extension OpenHABWidgetMapping { - convenience init(_ mapping: Components.Schemas.MappingDTO) { - self.init(command: mapping.command, label: mapping.label) - } -} - -extension OpenHABCommandOptions { - convenience init?(_ options: Components.Schemas.CommandOption?) { - if let options { - self.init(command: options.command.orEmpty, label: options.label.orEmpty) - } else { - return nil - } - } -} - -extension OpenHABOptions { - convenience init?(_ options: Components.Schemas.StateOption?) { - if let options { - self.init(value: options.value.orEmpty, label: options.label.orEmpty) - } else { - return nil - } - } -} - -extension OpenHABStateDescription { - convenience init?(_ state: Components.Schemas.StateDescription?) { - if let state { - self.init(minimum: state.minimum, maximum: state.maximum, step: state.step, readOnly: state.readOnly, options: state.options?.compactMap { OpenHABOptions($0) }, pattern: state.pattern) - } else { - return nil - } - } -} - -extension OpenHABCommandDescription { - convenience init?(_ commands: Components.Schemas.CommandDescription?) { - if let commands { - self.init(commandOptions: commands.commandOptions?.compactMap { OpenHABCommandOptions($0) }) - } else { - return nil - } - } -} - -// swiftlint:disable line_length -extension OpenHABItem { - convenience init?(_ item: Components.Schemas.EnrichedItemDTO?) { - if let item { - self.init(name: item.name.orEmpty, type: item._type.orEmpty, state: item.state.orEmpty, link: item.link.orEmpty, label: item.label.orEmpty, groupType: nil, stateDescription: OpenHABStateDescription(item.stateDescription), commandDescription: OpenHABCommandDescription(item.commandDescription), members: [], category: item.category, options: []) - } else { - return nil - } - } -} - -// swiftlint:enable line_length - -extension OpenHABWidget { - convenience init(_ widget: Components.Schemas.WidgetDTO) { - self.init( - widgetId: widget.widgetId.orEmpty, - label: widget.label.orEmpty, - icon: widget.icon.orEmpty, - type: OpenHABWidget.WidgetType(rawValue: widget._type!), - url: widget.url, - period: widget.period, - minValue: widget.minValue, - maxValue: widget.maxValue, - step: widget.step, - refresh: widget.refresh.map(Int.init), - height: 50, // TODO: - isLeaf: true, - iconColor: widget.iconcolor, - labelColor: widget.labelcolor, - valueColor: widget.valuecolor, - service: widget.service, - state: widget.state, - text: "", - legend: widget.legend, - encoding: widget.encoding, - item: OpenHABItem(widget.item), - linkedPage: OpenHABPage(widget.linkedPage), - mappings: widget.mappings?.compactMap(OpenHABWidgetMapping.init) ?? [], - widgets: widget.widgets?.compactMap { OpenHABWidget($0) } ?? [], - visibility: widget.visibility, - switchSupport: widget.switchSupport, - forceAsItem: widget.forceAsItem - ) - } -} - -// swiftlint:enable file_types_order diff --git a/OpenHABCore/Sources/OpenHABCore/Util/APIActorDelegate.swift b/OpenHABCore/Sources/OpenHABCore/Util/APIActorDelegate.swift new file mode 100644 index 00000000..f5648225 --- /dev/null +++ b/OpenHABCore/Sources/OpenHABCore/Util/APIActorDelegate.swift @@ -0,0 +1,76 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Foundation +import os + +// MARK: - URLSessionDelegate for Client Certificates and Basic Auth + +class APIActorDelegate: NSObject, URLSessionDelegate, URLSessionTaskDelegate { + private let username: String + private let password: String + + init(username: String, password: String) { + self.username = username + self.password = password + } + + public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + await urlSessionInternal(session, task: nil, didReceive: challenge) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + await urlSessionInternal(session, task: task, didReceive: challenge) + } + + private func urlSessionInternal(_ session: URLSession, task: URLSessionTask?, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + os_log("URLAuthenticationChallenge: %{public}@", log: .networking, type: .info, challenge.protectionSpace.authenticationMethod) + let authenticationMethod = challenge.protectionSpace.authenticationMethod + switch authenticationMethod { + case NSURLAuthenticationMethodServerTrust: + return await handleServerTrust(challenge: challenge) + case NSURLAuthenticationMethodDefault, NSURLAuthenticationMethodHTTPBasic: + if let task { + task.authAttemptCount += 1 + if task.authAttemptCount > 1 { + return (.cancelAuthenticationChallenge, nil) + } else { + return await handleBasicAuth(challenge: challenge) + } + } else { + return await handleBasicAuth(challenge: challenge) + } + case NSURLAuthenticationMethodClientCertificate: + return await handleClientCertificateAuth(challenge: challenge) + default: + return (.performDefaultHandling, nil) + } + } + + private func handleServerTrust(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + guard let serverTrust = challenge.protectionSpace.serverTrust else { + return (.performDefaultHandling, nil) + } + let credential = URLCredential(trust: serverTrust) + return (.useCredential, credential) + } + + private func handleBasicAuth(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + let credential = URLCredential(user: username, password: password, persistence: .forSession) + return (.useCredential, credential) + } + + private func handleClientCertificateAuth(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + let certificateManager = ClientCertificateManager() + let (disposition, credential) = certificateManager.evaluateTrust(with: challenge) + return (disposition, credential) + } +} diff --git a/openHAB/DrawerView.swift b/openHAB/DrawerView.swift index 291428a1..2faf8440 100644 --- a/openHAB/DrawerView.swift +++ b/openHAB/DrawerView.swift @@ -16,31 +16,6 @@ import SafariServices import SFSafeSymbols import SwiftUI -func deriveSitemaps(_ response: Data?) -> [OpenHABSitemap] { - var sitemaps = [OpenHABSitemap]() - - if let response { - do { - os_log("Response will be decoded by JSON", log: .remoteAccess, type: .info) - let sitemapsCodingData = try response.decoded(as: [OpenHABSitemap.CodingData].self) - for sitemapCodingDatum in sitemapsCodingData { - os_log("Sitemap %{PUBLIC}@", log: .remoteAccess, type: .info, sitemapCodingDatum.label) - sitemaps.append(sitemapCodingDatum.openHABSitemap) - } - } catch { - os_log("Should not throw %{PUBLIC}@", log: .notifications, type: .error, error.localizedDescription) - } - } - - return sitemaps -} - -struct UiTile: Decodable { - var name: String - var url: String - var imageUrl: String -} - struct ImageView: View { let url: String @@ -167,67 +142,40 @@ struct DrawerView: View { } } .listStyle(.inset) - .onAppear(perform: loadData) - } - - private func loadData() { - // TODO: Replace network calls with appropriate @EnvironmentObject or other state management - loadSitemaps() - loadUiTiles() - } - - private func loadSitemaps() { - // Perform network call to load sitemaps and decode - // Update the sitemaps state - - NetworkConnection.sitemaps(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in - switch response.result { - case let .success(data): - os_log("Sitemap response", log: .viewCycle, type: .info) - - sitemaps = deriveSitemaps(data) + .task { + let apiactor = await APIActor() + Task { + do { + await apiactor.updateBaseURL(with: URL(string: appData?.openHABRootUrl ?? "")!) - if sitemaps.last?.name == "_default", sitemaps.count > 1 { - sitemaps = Array(sitemaps.dropLast()) - } + sitemaps = try await apiactor.openHABSitemaps() + if sitemaps.last?.name == "_default", sitemaps.count > 1 { + sitemaps = Array(sitemaps.dropLast()) + } + // Sort the sitemaps according to Settings selection. + switch SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label { + case .label: sitemaps.sort { $0.label < $1.label } + case .name: sitemaps.sort { $0.name < $1.name } + } - // Sort the sitemaps according to Settings selection. - switch SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label { - case .label: sitemaps.sort { $0.label < $1.label } - case .name: sitemaps.sort { $0.name < $1.name } + } catch { + os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + sitemaps = [] } - case let .failure(error): - os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) } - } - } - private func loadUiTiles() { - // Perform network call to load UI Tiles and decode - // Update the uiTiles state - NetworkConnection.uiTiles(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in - switch response.result { - case .success: - os_log("ui tiles response", log: .viewCycle, type: .info) - guard let responseData = response.data else { - os_log("Error: did not receive data", log: OSLog.remoteAccess, type: .info) - return - } + Task { do { - uiTiles = try JSONDecoder().decode([OpenHABUiTile].self, from: responseData) + await apiactor.updateBaseURL(with: URL(string: appData?.openHABRootUrl ?? "")!) + uiTiles = try await apiactor.openHABTiles() + os_log("ui tiles response", log: .viewCycle, type: .info) } catch { - os_log("Error: did not receive data %{PUBLIC}@", log: OSLog.remoteAccess, type: .info, error.localizedDescription) + os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + uiTiles = [] } - case let .failure(error): - os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) } } } - - mutating func loadSettings() { - openHABUsername = Preferences.username - openHABPassword = Preferences.password - } } #Preview {