diff --git a/Docs/README.md b/Docs/README.md index 14681931..1a90e078 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -10,21 +10,28 @@ subscribe to a topic. It is written in Swift using SwiftUI. This App is open source, contributions are welcome. Features: -- authentication with username/password or client certificates -- connect using MQTT or Websocket -- support for SSL/TLS -- support for self signed certificates -- create multiple broker settings -- messages are grouped by topic -- search/filter/focus-on for topics -- json highlighting and pretty printing -- publish messages -- publish json messages with a form -- sync settings using private iCloud database -- pause the connection -- connect to multiple brokers at once -- totally free and no ADs -- open source +- Authentication with username/password or client certificates +- Connect using MQTT or Websocket +- Support for SSL/TLS +- Support for self-signed certificates +- MQTT 3.1.1 and MQTT 5 +- Siri shortcuts for publish and receive messages +- Create multiple broker settings +- Subscribe to multiple topics +- Folder and flat view +- Messages are grouped by topic +- Fulltext search for topics and payload +- JSON highlighting and pretty-printing +- Publish messages +- Publish JSON messages with a form +- Sync settings using a private iCloud database +- Pause the connection +- Connect to multiple brokers at once +- Hex view for binary payload +- Predefined settings for AWS IoT +- Free and without any ADs +- Open source + # TestFlight diff --git a/src/Common/Intents.intentdefinition b/src/Common/Intents.intentdefinition new file mode 100644 index 00000000..7174a52d --- /dev/null +++ b/src/Common/Intents.intentdefinition @@ -0,0 +1,583 @@ + + + + + INEnums + + + INEnumDisplayName + Qos + INEnumDisplayNameID + iH1f8j + INEnumGeneratesHeader + + INEnumName + Qos + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + QoedXM + INEnumValueName + unknown + + + INEnumValueDisplayName + qos 0 + INEnumValueDisplayNameID + OpoFfw + INEnumValueIndex + 1 + INEnumValueName + qos0 + + + INEnumValueDisplayName + qos 1 + INEnumValueDisplayNameID + JjmyXl + INEnumValueIndex + 2 + INEnumValueName + qos1 + + + INEnumValueDisplayName + qos 2 + INEnumValueDisplayNameID + v3y4Xg + INEnumValueIndex + 3 + INEnumValueName + qos2 + + + + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + WAoQSI + INIntentDefinitionSystemVersion + 21E258 + INIntentDefinitionToolsBuildVersion + 13E500a + INIntentDefinitionToolsVersion + 13.3.1 + INIntents + + + INIntentCategory + generic + INIntentConfigurable + + INIntentDescription + Publish a MQTT message + INIntentDescriptionID + cVQ1cO + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 9 + INIntentManagedParameterCombinations + + broker,topic,message,retainMessage,qos + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Publish MQTT message + INIntentParameterCombinationTitleID + McC9za + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + PublishMQTTMessage + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Broker + INIntentParameterDisplayNameID + xOwXwS + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + n2hhqx + + INIntentParameterName + broker + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 7 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Topic + INIntentParameterDisplayNameID + 9JzFQW + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + None + INIntentParameterMetadataDefaultValueID + buWaqg + INIntentParameterMetadataDisableAutocorrect + + INIntentParameterMetadataDisableSmartDashes + + INIntentParameterMetadataDisableSmartQuotes + + + INIntentParameterName + topic + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 2 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Message + INIntentParameterDisplayNameID + hF35Yt + INIntentParameterDisplayPriority + 3 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + None + INIntentParameterMetadataDefaultValueID + cOi2FX + INIntentParameterMetadataDisableAutocorrect + + INIntentParameterMetadataDisableSmartDashes + + INIntentParameterMetadataDisableSmartQuotes + + INIntentParameterMetadataMultiline + + + INIntentParameterName + message + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 3 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Retain + INIntentParameterDisplayNameID + bLvdSj + INIntentParameterDisplayPriority + 4 + INIntentParameterMetadata + + INIntentParameterMetadataFalseDisplayName + false + INIntentParameterMetadataFalseDisplayNameID + eQ8upA + INIntentParameterMetadataTrueDisplayName + true + INIntentParameterMetadataTrueDisplayNameID + kSIj6G + + INIntentParameterName + retainMessage + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 5 + INIntentParameterType + Boolean + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + QoS + INIntentParameterDisplayNameID + 88fmBQ + INIntentParameterDisplayPriority + 5 + INIntentParameterEnumType + Qos + INIntentParameterEnumTypeNamespace + WAoQSI + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + qos0 + + INIntentParameterName + qos + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${qos}’. + INIntentParameterPromptDialogFormatStringID + j7Z5NT + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${qos}’? + INIntentParameterPromptDialogFormatStringID + Blqlxv + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterTag + 9 + INIntentParameterType + Integer + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Error + INIntentResponseParameterDisplayNameID + Ce8ycL + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + error + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + String + + + + INIntentTitle + Publish MQTT Message + INIntentTitleID + rviDew + INIntentType + Custom + INIntentVerb + Do + + + INIntentCategory + generic + INIntentConfigurable + + INIntentDescription + Receive a MQTT Message + INIntentDescriptionID + bxxMAY + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 5 + INIntentManagedParameterCombinations + + broker,topic,timeoutSeconds + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Receive MQTT Message + INIntentParameterCombinationTitleID + ACxSZg + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + ReceiveMQTTMessage + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Broker + INIntentParameterDisplayNameID + kDH26T + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + BUCkyE + + INIntentParameterName + broker + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 1 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Topic + INIntentParameterDisplayNameID + szaiWS + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + 0xju9V + + INIntentParameterName + topic + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 2 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Timeout Seconds + INIntentParameterDisplayNameID + hQNGnL + INIntentParameterDisplayPriority + 3 + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + 10 + INIntentParameterMetadataMaximumValue + 300 + INIntentParameterMetadataMinimumValue + 2 + INIntentParameterMetadataType + Stepper + + INIntentParameterName + timeoutSeconds + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 5 + INIntentParameterType + Integer + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseOutput + message + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Message + INIntentResponseParameterDisplayNameID + z0Su4D + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + message + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + String + + + + INIntentTitle + Receive MQTT Message + INIntentTitleID + mA2VlX + INIntentType + Custom + INIntentVerb + Do + + + INTypes + + + diff --git a/src/MQTTAnalyzer/extensions/Date+Iso.swift b/src/Common/extensions/Date+Iso.swift similarity index 100% rename from src/MQTTAnalyzer/extensions/Date+Iso.swift rename to src/Common/extensions/Date+Iso.swift diff --git a/src/MQTTAnalyzer/extensions/DispatchQueue+Background.swift b/src/Common/extensions/DispatchQueue+Background.swift similarity index 100% rename from src/MQTTAnalyzer/extensions/DispatchQueue+Background.swift rename to src/Common/extensions/DispatchQueue+Background.swift diff --git a/src/MQTTAnalyzer/extensions/String+RegExp.swift b/src/Common/extensions/String+RegExp.swift similarity index 100% rename from src/MQTTAnalyzer/extensions/String+RegExp.swift rename to src/Common/extensions/String+RegExp.swift diff --git a/src/MQTTAnalyzer/extensions/String+Utils.swift b/src/Common/extensions/String+Utils.swift similarity index 100% rename from src/MQTTAnalyzer/extensions/String+Utils.swift rename to src/Common/extensions/String+Utils.swift diff --git a/src/Common/icloud/Certificate.swift b/src/Common/icloud/Certificate.swift new file mode 100644 index 00000000..c74facb7 --- /dev/null +++ b/src/Common/icloud/Certificate.swift @@ -0,0 +1,36 @@ +// +// Certificate.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation + +func getCertificate(_ host: Host, type: CertificateFileType) -> CertificateFile? { + return host.certificates.filter { $0.type == type }.first +} + +extension CertificateFile { + func getBaseUrl(certificate: CertificateFile) throws -> URL { + if certificate.location == .cloud { + if let url = CloudDataManager.instance.getCloudDocumentDiretoryURL() { + return url + } + else { + CloudDataManager.logger.error("No cloud URL found (Cloud disbled?)") + throw CertificateError.noCloud + } + } + else { + if let url = CloudDataManager.instance.getLocalDocumentDiretoryURL() { + return url + } + else { + CloudDataManager.logger.error("No local URL found") + throw CertificateError.noLocalURL + } + } + } +} diff --git a/src/MQTTAnalyzer/icloud/CertificateFileModel.swift b/src/Common/icloud/CertificateFileModel.swift similarity index 100% rename from src/MQTTAnalyzer/icloud/CertificateFileModel.swift rename to src/Common/icloud/CertificateFileModel.swift diff --git a/src/MQTTAnalyzer/icloud/CloudDataManager.swift b/src/Common/icloud/CloudDataManager.swift similarity index 85% rename from src/MQTTAnalyzer/icloud/CloudDataManager.swift rename to src/Common/icloud/CloudDataManager.swift index 911ab07b..76ffdf25 100644 --- a/src/MQTTAnalyzer/icloud/CloudDataManager.swift +++ b/src/Common/icloud/CloudDataManager.swift @@ -114,26 +114,3 @@ class CloudDataManager { } } } - -extension CertificateFile { - func getBaseUrl(certificate: CertificateFile) throws -> URL { - if certificate.location == .cloud { - if let url = CloudDataManager.instance.getCloudDocumentDiretoryURL() { - return url - } - else { - CloudDataManager.logger.error("No cloud URL found (Cloud disbled?)") - throw CertificateError.noCloud - } - } - else { - if let url = CloudDataManager.instance.getLocalDocumentDiretoryURL() { - return url - } - else { - CloudDataManager.logger.error("No local URL found") - throw CertificateError.noLocalURL - } - } - } -} diff --git a/src/MQTTAnalyzer/icloud/FileLister.swift b/src/Common/icloud/FileLister.swift similarity index 100% rename from src/MQTTAnalyzer/icloud/FileLister.swift rename to src/Common/icloud/FileLister.swift diff --git a/src/MQTTAnalyzer/json/JSONUtils.swift b/src/Common/json/JSONUtils.swift similarity index 100% rename from src/MQTTAnalyzer/json/JSONUtils.swift rename to src/Common/json/JSONUtils.swift diff --git a/src/MQTTAnalyzer/logger/Logger.swift b/src/Common/logger/Logger.swift similarity index 100% rename from src/MQTTAnalyzer/logger/Logger.swift rename to src/Common/logger/Logger.swift diff --git a/src/Common/model/Handlers.swift b/src/Common/model/Handlers.swift new file mode 100644 index 00000000..d10b621e --- /dev/null +++ b/src/Common/model/Handlers.swift @@ -0,0 +1,21 @@ +// +// Handlers.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation + +protocol InitHost: AnyObject { + func initHost(host: Host) +} + +protocol ReconnectDelegate: AnyObject { + func reconnect(host: Host) +} + +protocol DisconnectDelegate: AnyObject { + func disconnect(host: Host) +} diff --git a/src/MQTTAnalyzer/model/HostModel.swift b/src/Common/model/HostModel.swift similarity index 93% rename from src/MQTTAnalyzer/model/HostModel.swift rename to src/Common/model/HostModel.swift index 88039441..f27eb60b 100644 --- a/src/MQTTAnalyzer/model/HostModel.swift +++ b/src/Common/model/HostModel.swift @@ -204,25 +204,16 @@ class HostsModel: ObservableObject { self.hosts = hosts } - func delete(at offsets: IndexSet, persistence: Persistence) { - let original = hostsSorted - - for idx in offsets { - persistence.delete(original[idx]) - } - - persistence.load() - } - func getBroker(at offsets: IndexSet) -> Host? { if let first = offsets.first { return hostsSorted[first] } return nil } - - func delete(_ host: Host, persistence: Persistence) { - persistence.delete(host) - persistence.load() +} + +extension Host { + static func randomClientId() -> String { + return "mqtt-analyzer-\(String.random(length: 8))" } } diff --git a/src/MQTTAnalyzer/model/v2/MsgMessage.swift b/src/Common/model/MsgMessage.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/MsgMessage.swift rename to src/Common/model/MsgMessage.swift diff --git a/src/MQTTAnalyzer/model/v2/MsgMetadata.swift b/src/Common/model/MsgMetadata.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/MsgMetadata.swift rename to src/Common/model/MsgMetadata.swift diff --git a/src/MQTTAnalyzer/model/v2/MsgPayload.swift b/src/Common/model/MsgPayload.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/MsgPayload.swift rename to src/Common/model/MsgPayload.swift diff --git a/src/MQTTAnalyzer/model/Multimap.swift b/src/Common/model/Multimap.swift similarity index 100% rename from src/MQTTAnalyzer/model/Multimap.swift rename to src/Common/model/Multimap.swift diff --git a/src/MQTTAnalyzer/model/Readstate.swift b/src/Common/model/Readstate.swift similarity index 100% rename from src/MQTTAnalyzer/model/Readstate.swift rename to src/Common/model/Readstate.swift diff --git a/src/MQTTAnalyzer/model/HostSetting.swift b/src/Common/model/persistence/HostSetting.swift similarity index 94% rename from src/MQTTAnalyzer/model/HostSetting.swift rename to src/Common/model/persistence/HostSetting.swift index 201000d7..0983c4b3 100644 --- a/src/MQTTAnalyzer/model/HostSetting.swift +++ b/src/Common/model/persistence/HostSetting.swift @@ -71,12 +71,6 @@ class HostSetting: Object { } } -extension Host { - static func randomClientId() -> String { - return "mqtt-analyzer-\(String.random(length: 8))" - } -} - extension HostSetting: CKRecordConvertible { } diff --git a/src/MQTTAnalyzer/model/persistence/HostSettingExamples.swift b/src/Common/model/persistence/HostSettingExamples.swift similarity index 90% rename from src/MQTTAnalyzer/model/persistence/HostSettingExamples.swift rename to src/Common/model/persistence/HostSettingExamples.swift index 8fa243e9..7243a594 100644 --- a/src/MQTTAnalyzer/model/persistence/HostSettingExamples.swift +++ b/src/Common/model/persistence/HostSettingExamples.swift @@ -21,8 +21,8 @@ class HostSettingExamples { return } - createIfNotPresent(setting: RealmPersistenceTransformer.transform(example1()), realm: realm) - createIfNotPresent(setting: RealmPersistenceTransformer.transform(example2()), realm: realm) + createIfNotPresent(setting: PersistenceTransformer.transformToRealm(from: example1()), realm: realm) + createIfNotPresent(setting: PersistenceTransformer.transformToRealm(from: example2()), realm: realm) setWritten() } diff --git a/src/MQTTAnalyzer/model/persistence/Persistence.swift b/src/Common/model/persistence/Persistence.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/Persistence.swift rename to src/Common/model/persistence/Persistence.swift diff --git a/src/MQTTAnalyzer/model/persistence/RealmPersistence.swift b/src/Common/model/persistence/PersistenceTransformer.swift similarity index 64% rename from src/MQTTAnalyzer/model/persistence/RealmPersistence.swift rename to src/Common/model/persistence/PersistenceTransformer.swift index 67051c9d..2625d904 100644 --- a/src/MQTTAnalyzer/model/persistence/RealmPersistence.swift +++ b/src/Common/model/persistence/PersistenceTransformer.swift @@ -1,119 +1,14 @@ // -// HostModelPersistence.swift +// PersistenceTransformer.swift // MQTTAnalyzer // -// Created by Philipp Arndt on 2019-11-15. -// Copyright © 2019 Philipp Arndt. All rights reserved. +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. // import Foundation -import RealmSwift -import SwiftUI -public class RealmPersistence: Persistence { - let model: HostsModel - let realm: Realm - var token: NotificationToken? - - init?(model: HostsModel) { - self.model = model - - if let realm = RealmPersistence.initRealm() { - self.realm = realm - } - else { - return nil - } - } - - class func initRealm() -> Realm? { - do { - return try Realm() - } - catch { - NSLog("Unable to initialize persistence, using stub persistence. \(error)") - return nil - } - - } - - func create(_ host: Host) { - if CommandLine.arguments.contains("--ui-testing") { - return - } - - let setting = RealmPersistenceTransformer.transform(host) - - do { - try realm.write { - realm.add(setting) - } - } - catch { - NSLog("Error creating entry in database: \(error.localizedDescription)") - } - } - - func update(_ host: Host) { - let settings = realm.objects(HostSetting.self) - .filter("id = %@", host.ID) - - if let setting = settings.first { - do { - try realm.write { - RealmPersistenceTransformer.copy(from: host, to: setting) - } - } - catch { - NSLog("Error updating database: \(error.localizedDescription)") - } - - } - } - - func delete(_ host: Host) { - let settings = realm.objects(HostSetting.self) - .filter("id = %@", host.ID) - - if let setting = settings.first { - do { - try realm.write { - setting.isDeleted = true - } - } - catch { - NSLog("Error deleting entry from database: \(error.localizedDescription)") - } - } - - load() - } - - func load() { - HostSettingExamples.inititalize(realm: realm) - - let settings = realm.objects(HostSetting.self) - - token?.invalidate() - - token = settings.observe { (_: RealmCollectionChange) in - self.pushModel(settings: settings) - } - } - - private func pushModel(settings: Results) { - let hosts: [Host] = settings - .filter { !$0.isDeleted } - .map { RealmPersistenceTransformer.transform($0) } - - DispatchQueue.main.async { - self.model.hosts = hosts - } - } - -} - -class RealmPersistenceTransformer { +class PersistenceTransformer { private class func transformAuth(_ type: HostAuthenticationType) -> Int8 { switch type { case .usernamePassword: @@ -196,7 +91,7 @@ class RealmPersistenceTransformer { } } - class func transform(_ host: HostSetting) -> Host { + class func transform(from host: HostSetting) -> Host { let result = Host(id: host.id) result.deleted = host.isDeleted result.alias = host.alias @@ -221,7 +116,7 @@ class RealmPersistenceTransformer { return result } - class func transform(_ host: Host) -> HostSetting { + class func transformToRealm(from host: Host) -> HostSetting { let result = HostSetting() copy(from: host, to: result) return result @@ -249,4 +144,51 @@ class RealmPersistenceTransformer { result.navigationMode = transformNavigationMode(host.navigationMode) result.maxMessagesOfSubFolders = host.maxMessagesOfSubFolders } + + class func transformToSQLite(from host: Host) -> SQLiteBrokerSetting { + return SQLiteBrokerSetting( + id: host.ID, + alias: host.alias, + hostname: host.hostname, + port: Int(host.port), + subscriptions: PersistenceEncoder.encode(subscriptions: host.subscriptions), + protocolMethod: Int(transformConnectionMethod(host.protocolMethod)), + basePath: host.basePath, + ssl: host.ssl, + untrustedSSL: host.untrustedSSL, + protocolVersion: Int(transformProtocolVersion(host.protocolVersion)), + authType: Int(transformAuth(host.auth)), + username: host.username, + password: host.password, + certificates: PersistenceEncoder.encode(certificates: host.certificates), + certClientKeyPassword: host.certClientKeyPassword, + clientID: host.clientID, + limitTopic: host.limitTopic, + limitMessagesBatch: host.limitMessagesBatch, + deleted: host.deleted + ) + } + + class func transform(from host: SQLiteBrokerSetting) -> Host { + let result = Host(id: host.id) + result.deleted = host.deleted + result.alias = host.alias + result.hostname = host.hostname + result.port = UInt16(host.port) + result.subscriptions = PersistenceEncoder.decode(subscriptions: host.subscriptions) + result.auth = transformAuth(Int8(host.authType)) + result.username = host.username + result.password = host.password + result.certificates = PersistenceEncoder.decode(certificates: host.certificates) + result.certClientKeyPassword = host.certClientKeyPassword + result.clientID = host.clientID + result.limitTopic = host.limitTopic + result.limitMessagesBatch = host.limitMessagesBatch + result.protocolMethod = transformConnectionMethod(Int8(host.protocolMethod)) + result.protocolVersion = transformProtocolVersion(Int8(host.protocolVersion)) + result.basePath = host.basePath + result.ssl = host.ssl + result.untrustedSSL = host.untrustedSSL + return result + } } diff --git a/src/Common/model/persistence/RealmPersistence.swift b/src/Common/model/persistence/RealmPersistence.swift new file mode 100644 index 00000000..7ebf7a48 --- /dev/null +++ b/src/Common/model/persistence/RealmPersistence.swift @@ -0,0 +1,138 @@ +// +// HostModelPersistence.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2019-11-15. +// Copyright © 2019 Philipp Arndt. All rights reserved. +// + +import Foundation +import RealmSwift +import SwiftUI + +public class RealmPersistence: Persistence { + let model: HostsModel + let realm: Realm + var token: NotificationToken? + + static var realmPath: URL? { + let directoryUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.de.rnd7.mqttanalyzer") + return directoryUrl?.appendingPathComponent("default.realm") + } + + static var realmConfig: Realm.Configuration { + var config = Realm.Configuration(fileURL: realmPath) + config.deleteRealmIfMigrationNeeded = true + return config + } + + init?(model: HostsModel) { + self.model = model + + if let realm = RealmPersistence.initRealm() { + self.realm = realm + } + else { + return nil + } + } + + class func initRealm() -> Realm? { + do { + return try Realm() + } + catch { + NSLog("Unable to initialize persistence, using stub persistence. \(error)") + return nil + } + + } + + func create(_ host: Host) { + if CommandLine.arguments.contains("--ui-testing") { + return + } + + let setting = PersistenceTransformer.transformToRealm(from: host) + + do { + try realm.write { + realm.add(setting) + } + } + catch { + NSLog("Error creating entry in database: \(error.localizedDescription)") + } + } + + func update(_ host: Host) { + let settings = realm.objects(HostSetting.self) + .filter("id = %@", host.ID) + + if let setting = settings.first { + do { + try realm.write { + PersistenceTransformer.copy(from: host, to: setting) + } + } + catch { + NSLog("Error updating database: \(error.localizedDescription)") + } + + } + } + + func delete(_ host: Host) { + let settings = realm.objects(HostSetting.self) + .filter("id = %@", host.ID) + + if let setting = settings.first { + do { + try realm.write { + setting.isDeleted = true + } + } + catch { + NSLog("Error deleting entry from database: \(error.localizedDescription)") + } + } + + load() + } + + func load() { + HostSettingExamples.inititalize(realm: realm) + + let settings = realm.objects(HostSetting.self) + + token?.invalidate() + + token = settings.observe { (_: RealmCollectionChange) in + self.pushModel(settings: settings) + } + } + + func first(name: String) -> Host? { + return filter(settings: realm.objects(HostSetting.self)) + .filter { $0.aliasOrHost.lowercased() == name.lowercased() } + .first + } + + private func filter(settings: Results) -> [Host] { + return settings + .filter { !$0.isDeleted } + .map { PersistenceTransformer.transform(from: $0) } + } + + private func pushModel(settings: Results) { + let hosts = filter(settings: settings) + + DispatchQueue.main.async { + self.model.hosts = hosts + + let sqlite = SQLitePersistence() + sqlite.insert(hosts: hosts) + sqlite.close() + } + } +} diff --git a/src/Common/model/persistence/SQLitePersistence.swift b/src/Common/model/persistence/SQLitePersistence.swift new file mode 100644 index 00000000..5d6aa850 --- /dev/null +++ b/src/Common/model/persistence/SQLitePersistence.swift @@ -0,0 +1,198 @@ +// +// SQLitePersistence.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation +import GRDB + +struct SQLiteBrokerSetting: Codable, FetchableRecord, PersistableRecord { + var id: String + var alias: String + var hostname: String + var port: Int + var subscriptions: Data + var protocolMethod: Int + + var basePath: String + var ssl: Bool + var untrustedSSL: Bool + var protocolVersion: Int + var authType: Int + + var username: String + var password: String + var certificates: Data + var certClientKeyPassword: String + var clientID: String + var limitTopic: Int + var limitMessagesBatch: Int + var deleted: Bool +} + +class SQLitePersistence { + let availabe: Bool + let queue: DatabaseQueue + + static let table = "SQLiteBrokerSetting" + + static var path: URL? { + let directoryUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.de.rnd7.mqttanalyzer") + return directoryUrl?.appendingPathComponent("brokers.sqlite") + } + + class func createQueue() -> DatabaseQueue { + do { + if let dbPath = path { + return try DatabaseQueue(path: dbPath.path) + } + } + catch { + NSLog("Unable to create database file. Using in memory database.") + } + + return DatabaseQueue() + } + + init() { + self.queue = SQLitePersistence.createQueue() + + do { + // 2. Define the database schema + try queue.write { db in + if !(try db.tableExists(SQLitePersistence.table)) { + try db.create(table: SQLitePersistence.table) { t in + t.column("id", .text).notNull() + t.column("alias", .text) + t.column("hostname", .text).notNull() + t.column("port", .integer).notNull() + + t.column("subscriptions", .blob).notNull() + t.column("protocolMethod", .integer).notNull() + t.column("basePath", .text).notNull() + + t.column("ssl", .boolean).notNull() + t.column("untrustedSSL", .boolean).notNull() + t.column("protocolVersion", .integer).notNull() + + t.column("authType", .integer).notNull() + + t.column("username", .text).notNull() + t.column("password", .text).notNull() + t.column("certificates", .blob).notNull() + t.column("certClientKeyPassword", .text).notNull() + + t.column("clientID", .text).notNull() + + t.column("limitTopic", .integer).notNull() + t.column("limitMessagesBatch", .integer).notNull() + t.column("deleted", .boolean).notNull() + + t.primaryKey(["id"]) + } + } + } + + availabe = true + } + catch { + NSLog("Error creating full text search table") + availabe = false + } + } + + func deleteAll() { + if !availabe { + return + } + + do { + _ = try queue.write { db in + try db.execute(sql: "DELETE FROM \(SQLitePersistence.table)") + } + } + catch { + NSLog("Error deleting all records") + } + } + + func add(setting: SQLiteBrokerSetting) { + if !availabe { + return + } + + do { + try queue.write { db in + try setting.insert(db) + } + } + catch { + NSLog("Error inserting setting") + } + } + + func close() { + if !availabe { + return + } + + do { + try queue.close() + } + catch { + NSLog("Error closing database") + } + } +} + +extension SQLitePersistence { + func insert(hosts: [Host]) { + deleteAll() + + hosts + .map { PersistenceTransformer.transformToSQLite(from: $0)} + .forEach { add(setting: $0) } + } + + func first(by name: String) -> Host? { + if !availabe { + return nil + } + do { + let settings: [SQLiteBrokerSetting] = try queue.read { db in + try SQLiteBrokerSetting.fetchAll(db) + } + + return settings + .map { PersistenceTransformer.transform(from: $0) } + .filter { $0.aliasOrHost.lowercased() == name.lowercased() } + .first + } + catch { + NSLog("Error reading settings") + return nil + } + } + + func allNames() -> [String] { + if !availabe { + return [] + } + do { + let settings: [SQLiteBrokerSetting] = try queue.read { db in + try SQLiteBrokerSetting.fetchAll(db) + } + + return settings + .map { PersistenceTransformer.transform(from: $0) } + .map { $0.aliasOrHost } + } + catch { + NSLog("Error reading settings") + return [] + } + } +} diff --git a/src/MQTTAnalyzer/model/persistence/StubPersistence.swift b/src/Common/model/persistence/StubPersistence.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/StubPersistence.swift rename to src/Common/model/persistence/StubPersistence.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift b/src/Common/model/persistence/migration/DataMigration.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift rename to src/Common/model/persistence/migration/DataMigration.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationAuth.swift b/src/Common/model/persistence/migration/DataMigrationAuth.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationAuth.swift rename to src/Common/model/persistence/migration/DataMigrationAuth.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationCertificateFiles.swift b/src/Common/model/persistence/migration/DataMigrationCertificateFiles.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationCertificateFiles.swift rename to src/Common/model/persistence/migration/DataMigrationCertificateFiles.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationClientImpl.swift b/src/Common/model/persistence/migration/DataMigrationClientImpl.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationClientImpl.swift rename to src/Common/model/persistence/migration/DataMigrationClientImpl.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationEmptyTopic.swift b/src/Common/model/persistence/migration/DataMigrationEmptyTopic.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationEmptyTopic.swift rename to src/Common/model/persistence/migration/DataMigrationEmptyTopic.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationLimits.swift b/src/Common/model/persistence/migration/DataMigrationLimits.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationLimits.swift rename to src/Common/model/persistence/migration/DataMigrationLimits.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationMoscapsuleDeprecation.swift b/src/Common/model/persistence/migration/DataMigrationMoscapsuleDeprecation.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationMoscapsuleDeprecation.swift rename to src/Common/model/persistence/migration/DataMigrationMoscapsuleDeprecation.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationMultipleTopics.swift b/src/Common/model/persistence/migration/DataMigrationMultipleTopics.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationMultipleTopics.swift rename to src/Common/model/persistence/migration/DataMigrationMultipleTopics.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationNavigationMode.swift b/src/Common/model/persistence/migration/DataMigrationNavigationMode.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationNavigationMode.swift rename to src/Common/model/persistence/migration/DataMigrationNavigationMode.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigrationProtocolVersion.swift b/src/Common/model/persistence/migration/DataMigrationProtocolVersion.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/DataMigrationProtocolVersion.swift rename to src/Common/model/persistence/migration/DataMigrationProtocolVersion.swift diff --git a/src/MQTTAnalyzer/model/persistence/migration/MigrationHelper.swift b/src/Common/model/persistence/migration/MigrationHelper.swift similarity index 100% rename from src/MQTTAnalyzer/model/persistence/migration/MigrationHelper.swift rename to src/Common/model/persistence/migration/MigrationHelper.swift diff --git a/src/MQTTAnalyzer/model/v2/search/SearchIndex.swift b/src/Common/model/search/SearchIndex.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/search/SearchIndex.swift rename to src/Common/model/search/SearchIndex.swift diff --git a/src/MQTTAnalyzer/model/timeseries/DiagramPath.swift b/src/Common/model/timeseries/DiagramPath.swift similarity index 100% rename from src/MQTTAnalyzer/model/timeseries/DiagramPath.swift rename to src/Common/model/timeseries/DiagramPath.swift diff --git a/src/MQTTAnalyzer/model/timeseries/TimeSeriesModel.swift b/src/Common/model/timeseries/TimeSeriesModel.swift similarity index 100% rename from src/MQTTAnalyzer/model/timeseries/TimeSeriesModel.swift rename to src/Common/model/timeseries/TimeSeriesModel.swift diff --git a/src/MQTTAnalyzer/model/timeseries/TimeSeriesValue.swift b/src/Common/model/timeseries/TimeSeriesValue.swift similarity index 100% rename from src/MQTTAnalyzer/model/timeseries/TimeSeriesValue.swift rename to src/Common/model/timeseries/TimeSeriesValue.swift diff --git a/src/MQTTAnalyzer/model/timeseries/TimeSeriesValueUtil.swift b/src/Common/model/timeseries/TimeSeriesValueUtil.swift similarity index 100% rename from src/MQTTAnalyzer/model/timeseries/TimeSeriesValueUtil.swift rename to src/Common/model/timeseries/TimeSeriesValueUtil.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+Counter.swift b/src/Common/model/tree/TopicTree+Counter.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+Counter.swift rename to src/Common/model/tree/TopicTree+Counter.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+Deletion.swift b/src/Common/model/tree/TopicTree+Deletion.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+Deletion.swift rename to src/Common/model/tree/TopicTree+Deletion.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+Filter.swift b/src/Common/model/tree/TopicTree+Filter.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+Filter.swift rename to src/Common/model/tree/TopicTree+Filter.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+Flatten.swift b/src/Common/model/tree/TopicTree+Flatten.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+Flatten.swift rename to src/Common/model/tree/TopicTree+Flatten.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+ReadState.swift b/src/Common/model/tree/TopicTree+ReadState.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+ReadState.swift rename to src/Common/model/tree/TopicTree+ReadState.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree+Search.swift b/src/Common/model/tree/TopicTree+Search.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree+Search.swift rename to src/Common/model/tree/TopicTree+Search.swift diff --git a/src/MQTTAnalyzer/model/v2/TopicTree.swift b/src/Common/model/tree/TopicTree.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TopicTree.swift rename to src/Common/model/tree/TopicTree.swift diff --git a/src/MQTTAnalyzer/model/v2/TreeUtils.swift b/src/Common/model/tree/TreeUtils.swift similarity index 100% rename from src/MQTTAnalyzer/model/v2/TreeUtils.swift rename to src/Common/model/tree/TreeUtils.swift diff --git a/src/MQTTAnalyzer/mqtt/MqttClient.swift b/src/Common/mqtt/MqttClient.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/MqttClient.swift rename to src/Common/mqtt/MqttClient.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/ClientUtils.swift b/src/Common/mqtt/cocoamqtt/ClientUtils.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/ClientUtils.swift rename to src/Common/mqtt/cocoamqtt/ClientUtils.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift b/src/Common/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift rename to src/Common/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/Host+ActualUserPassword.swift b/src/Common/mqtt/cocoamqtt/Host+ActualUserPassword.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/Host+ActualUserPassword.swift rename to src/Common/mqtt/cocoamqtt/Host+ActualUserPassword.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+Client.swift b/src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+Client.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+Client.swift rename to src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+Client.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+OnMessage.swift b/src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+OnMessage.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+OnMessage.swift rename to src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT+OnMessage.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift b/src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift similarity index 98% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift rename to src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift index be8642ad..ace0b472 100644 --- a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift +++ b/src/Common/mqtt/cocoamqtt/MQTT5ClientCocoaMQTT.swift @@ -12,7 +12,7 @@ import Combine import Network class MQTT5ClientCocoaMQTT: MqttClient { - let delgate = MQTT5Delegate() + let delegate = MQTT5Delegate() let utils: ClientUtils var connectionState: ConnectionState { utils.connectionState } @@ -51,7 +51,7 @@ class MQTT5ClientCocoaMQTT: MqttClient { mqtt.keepAlive = 60 mqtt.autoReconnect = false - mqtt.delegate = self.delgate + mqtt.delegate = self.delegate mqtt.didReceiveMessage = self.didReceiveMessage mqtt.didDisconnect = utils.didDisconnect mqtt.didConnectAck = self.didConnect diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5Delegate.swift b/src/Common/mqtt/cocoamqtt/MQTT5Delegate.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTT5Delegate.swift rename to src/Common/mqtt/cocoamqtt/MQTT5Delegate.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT+Client.swift b/src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT+Client.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT+Client.swift rename to src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT+Client.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT+OnMessage.swift b/src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT+OnMessage.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT+OnMessage.swift rename to src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT+OnMessage.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift b/src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift similarity index 87% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift rename to src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift index 3dccf617..75b1bcea 100644 --- a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift +++ b/src/Common/mqtt/cocoamqtt/MQTTClientCocoaMQTT.swift @@ -12,7 +12,7 @@ import Combine import Network class MQTTClientCocoaMQTT: MqttClient { - let delgate = MQTTDelegate() + let delegate = MQTTDelegate() let utils: ClientUtils var connectionState: ConnectionState { utils.connectionState } @@ -27,31 +27,19 @@ class MQTTClientCocoaMQTT: MqttClient { utils.initConnect() let mqtt = createClient(host: host) - mqtt.enableSSL = host.ssl - mqtt.allowUntrustCACertificate = host.untrustedSSL - - if host.auth == .usernamePassword { - mqtt.username = host.actualUsername - mqtt.password = host.actualPassword + do { + try configureClient(client: mqtt) } - else if host.auth == .certificate { - do { - try mqtt.sslSettings = createSSLSettings(host: host) - } - catch let error as CertificateError { - utils.failConnection(reason: "\(error.rawValue)") - return - } - catch { - utils.failConnection(reason: "\(error)") - return - } + catch let error as CertificateError { + utils.failConnection(reason: "\(error.rawValue)") + return + } + catch { + utils.failConnection(reason: "\(error)") + return } - mqtt.keepAlive = 60 - mqtt.autoReconnect = false - - mqtt.delegate = self.delgate + mqtt.delegate = self.delegate mqtt.didReceiveMessage = self.didReceiveMessage mqtt.didDisconnect = utils.didDisconnect(_:withError:) mqtt.didConnectAck = self.didConnect @@ -62,7 +50,6 @@ class MQTTClientCocoaMQTT: MqttClient { } utils.waitConnected() - utils.mqtt = mqtt utils.installMessageDispatch( @@ -71,6 +58,22 @@ class MQTTClientCocoaMQTT: MqttClient { topic: topic(of:) ) } + + func configureClient(client mqtt: CocoaMQTT) throws { + mqtt.enableSSL = host.ssl + mqtt.allowUntrustCACertificate = host.untrustedSSL + + if host.auth == .usernamePassword { + mqtt.username = host.actualUsername + mqtt.password = host.actualPassword + } + else if host.auth == .certificate { + try mqtt.sslSettings = createSSLSettings(host: host) + } + + mqtt.keepAlive = 60 + mqtt.autoReconnect = false + } func disconnect() { utils.messageSubject.cancel() diff --git a/src/Common/mqtt/cocoamqtt/MQTTClientSync.swift b/src/Common/mqtt/cocoamqtt/MQTTClientSync.swift new file mode 100644 index 00000000..9d13b504 --- /dev/null +++ b/src/Common/mqtt/cocoamqtt/MQTTClientSync.swift @@ -0,0 +1,169 @@ +// +// PublishSync.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation +import CocoaMQTT + +enum MQTTError: String, Error { + case connectionError = "Timeout during connection" + case messageTimeout = "Message timeout" +} + +class MQTTClientSync { + class func createQueue() -> DispatchQueue { + let queue = DispatchQueue(label: "syncPublishDelegateQueue") + queue.setSpecific( + key: DispatchSpecificKey(), + value: "syncPublishDelegateQueue" + ) + return queue + } + + class func transformQos(qos: Int) -> CocoaMQTTQoS { + switch qos { + case 1: + return .qos1 + case 2: + return .qos2 + default: + return .qos0 + } + } + + class func connect(host: Host, delegate: SyncMQTTDelegate) throws -> CocoaMQTT { + let model = TopicTree() + let client = MQTTClientCocoaMQTT(host: host, model: model) + + let mqtt = client.createClient(host: host) + try client.configureClient(client: mqtt) + + mqtt.delegate = delegate + let queue = createQueue() + mqtt.delegateQueue = queue + + _ = mqtt.connect() + + if !wait(for: { delegate.isConnected }) { + throw MQTTError.connectionError + } + + return mqtt + } + + class func publish(host: Host, topic: String, message: String, retain: Bool, qos: Int) throws -> Bool { + let delegate = SyncMQTTDelegate() + let mqtt = try connect(host: host, delegate: delegate) + + mqtt.publish( + topic, + withString: message, + qos: transformQos(qos: qos), + retained: retain + ) + + if !wait(for: { delegate.sents.count >= 1 }) { + return false + } + + mqtt.disconnect() + + if !wait(for: { !delegate.isConnected }) { + return false + } + + return true + } + + class func receiveFirst(host: Host, topic: String, timeout: Int) throws -> String? { + let delegate = SyncMQTTDelegate() + let mqtt = try connect(host: host, delegate: delegate) + mqtt.subscribe(topic) + + if !wait(for: { delegate.messages.count >= 1 }) { + throw MQTTError.messageTimeout + } + + return delegate.messages.first?.dataString + } +} + +func wait(for expectation: @escaping () -> Bool, timeout seconds: Int = 10) -> Bool { + let semaphore = DispatchSemaphore(value: 0) + let thread = Thread { + while true { + sleep(1) + guard expectation() else { + continue + } + semaphore.signal() + break + } + } + thread.start() + let result = semaphore.wait(timeout: DispatchTime.now() + .advanced(by: .seconds(seconds))) + thread.cancel() + return result == .success +} + +class SyncMQTTDelegate: CocoaMQTTDelegate { + var messages = [MsgPayload]() + + var sents = [UInt16]() + + var acks = [UInt16]() + + var subs = [String]() + + var isConnected = false + + func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { + if ack == .accept { isConnected = true } + } + + func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { + sents.append(id) + } + + func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { + acks.append(id) + } + + func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { + messages.append(MsgPayload(data: message.payload)) + } + + func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) { + } + + func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) { + subs = subs.filter { (e) -> Bool in + !topics.contains(e) + } + } + + func mqttDidPing(_ mqtt: CocoaMQTT) { + } + + func mqttDidReceivePong(_ mqtt: CocoaMQTT) { + } + + func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { + isConnected = false + } + + func mqtt(_ mqtt: CocoaMQTT, didStateChangeTo state: CocoaMQTTConnState) { + } + + func mqtt(_ mqtt: CocoaMQTT, didPublishComplete id: UInt16) { + } + + func mqtt(_ mqtt: CocoaMQTT, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void) { + completionHandler(true) + } +} diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTDelegate.swift b/src/Common/mqtt/cocoamqtt/MQTTDelegate.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/MQTTDelegate.swift rename to src/Common/mqtt/cocoamqtt/MQTTDelegate.swift diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/ReceivedMessage.swift b/src/Common/mqtt/cocoamqtt/ReceivedMessage.swift similarity index 100% rename from src/MQTTAnalyzer/mqtt/cocoamqtt/ReceivedMessage.swift rename to src/Common/mqtt/cocoamqtt/ReceivedMessage.swift diff --git a/src/MQTTAnalyzer.xcodeproj/project.pbxproj b/src/MQTTAnalyzer.xcodeproj/project.pbxproj index ba5113d2..c9330ac3 100644 --- a/src/MQTTAnalyzer.xcodeproj/project.pbxproj +++ b/src/MQTTAnalyzer.xcodeproj/project.pbxproj @@ -112,6 +112,63 @@ 228104932381773100112F24 /* TopicCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228104922381773100112F24 /* TopicCellView.swift */; }; 228104952381779000112F24 /* TopicToolsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228104942381779000112F24 /* TopicToolsView.swift */; }; 228104982381796B00112F24 /* HostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228104972381796B00112F24 /* HostCellView.swift */; }; + 2282D9DF28102EB100F79E65 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2282D9DE28102EB000F79E65 /* Intents.framework */; }; + 2282D9E228102EB100F79E65 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282D9E128102EB100F79E65 /* IntentHandler.swift */; }; + 2282D9EA28102EB100F79E65 /* IntentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2282D9E928102EB100F79E65 /* IntentsUI.framework */; }; + 2282D9ED28102EB100F79E65 /* IntentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282D9EC28102EB100F79E65 /* IntentViewController.swift */; }; + 2282D9F028102EB100F79E65 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2282D9EE28102EB100F79E65 /* MainInterface.storyboard */; }; + 2282D9F428102EB100F79E65 /* MQTTAnalyzerIntentUI.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2282D9E828102EB100F79E65 /* MQTTAnalyzerIntentUI.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 2282D9F828102EB100F79E65 /* MQTTAnalyzerIntent.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2282D9DD28102EB000F79E65 /* MQTTAnalyzerIntent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 2282DA0228102ED700F79E65 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA0128102ED700F79E65 /* Intents.intentdefinition */; }; + 2282DA03281038B600F79E65 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA0128102ED700F79E65 /* Intents.intentdefinition */; }; + 2282DA0428103BDD00F79E65 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA0128102ED700F79E65 /* Intents.intentdefinition */; }; + 2282DA142811254F00F79E65 /* MsgMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF1727A666C600D4327F /* MsgMessage.swift */; }; + 2282DA162811254F00F79E65 /* MsgMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF1527A6669C00D4327F /* MsgMetadata.swift */; }; + 2282DA182811254F00F79E65 /* MsgPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF1327A6667000D4327F /* MsgPayload.swift */; }; + 2282DA35281125E900F79E65 /* TopicTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FEFB27A50C9700D4327F /* TopicTree.swift */; }; + 2282DA36281125E900F79E65 /* TopicTree+Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF0527A5668200D4327F /* TopicTree+Counter.swift */; }; + 2282DA37281125E900F79E65 /* TopicTree+Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF1927A6678B00D4327F /* TopicTree+Filter.swift */; }; + 2282DA38281125E900F79E65 /* TopicTree+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2215118C27C923EB0000E385 /* TopicTree+Search.swift */; }; + 2282DA39281125E900F79E65 /* TopicTree+Flatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF2127A6AEC800D4327F /* TopicTree+Flatten.swift */; }; + 2282DA3A281125E900F79E65 /* TreeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF0D27A650CE00D4327F /* TreeUtils.swift */; }; + 2282DA3B281125E900F79E65 /* TopicTree+ReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF0B27A57DF400D4327F /* TopicTree+ReadState.swift */; }; + 2282DA3C281125E900F79E65 /* TopicTree+Deletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FF1127A65EE100D4327F /* TopicTree+Deletion.swift */; }; + 2282DA3E2811261A00F79E65 /* TimeSeriesValueUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22A38706240BD9E600DF8F94 /* TimeSeriesValueUtil.swift */; }; + 2282DA3F2811261A00F79E65 /* DiagramPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AE64332412636300C2C4FE /* DiagramPath.swift */; }; + 2282DA402811261A00F79E65 /* TimeSeriesValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AE64352412637A00C2C4FE /* TimeSeriesValue.swift */; }; + 2282DA412811261A00F79E65 /* TimeSeriesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E8971622CFBFED00A4B8A3 /* TimeSeriesModel.swift */; }; + 2282DA462811265500F79E65 /* SearchIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2200D98027C91DCA00E63E89 /* SearchIndex.swift */; }; + 2282DA4A2811268C00F79E65 /* HostModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C9F73D23BB5E5C00892C4B /* HostModel.swift */; }; + 2282DA4B2811268C00F79E65 /* Multimap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D50F9622CE4C4300F37EAF /* Multimap.swift */; }; + 2282DA4C2811268C00F79E65 /* Readstate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E469D6237FC03500D72BD6 /* Readstate.swift */; }; + 2282DA50281126D400F79E65 /* String+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F8BEE623C24A5800422BFF /* String+Utils.swift */; }; + 2282DA51281126D400F79E65 /* Date+Iso.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FEFD27A50E9000D4327F /* Date+Iso.swift */; }; + 2282DA532811276000F79E65 /* HostModel+Delete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA522811276000F79E65 /* HostModel+Delete.swift */; }; + 2282DA552811278500F79E65 /* DispatchQueue+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22061AC427CA79FE00FFF915 /* DispatchQueue+Background.swift */; }; + 2282DA57281127C800F79E65 /* Handlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA56281127C800F79E65 /* Handlers.swift */; }; + 2282DA59281127D800F79E65 /* Handlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA56281127C800F79E65 /* Handlers.swift */; }; + 2282DA5A281127F400F79E65 /* JSONUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2285C82027872D46008DA37D /* JSONUtils.swift */; }; + 2282DA5B281127F600F79E65 /* JSONUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2285C82027872D46008DA37D /* JSONUtils.swift */; }; + 2282DA5E2811285000F79E65 /* MqttClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A6B4F244457E100ACDFC3 /* MqttClient.swift */; }; + 2282DA5F2811290400F79E65 /* CocoaMQTTCertificateFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2203571E2445852800A98CD3 /* CocoaMQTTCertificateFiles.swift */; }; + 2282DA602811290400F79E65 /* MQTT5ClientCocoaMQTT+Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B3893E280EACCB00D23022 /* MQTT5ClientCocoaMQTT+Client.swift */; }; + 2282DA612811290400F79E65 /* MQTTClientCocoaMQTT+OnMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B3893C280EAA9300D23022 /* MQTTClientCocoaMQTT+OnMessage.swift */; }; + 2282DA622811290400F79E65 /* MQTTClientCocoaMQTT+Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B38940280ECCDE00D23022 /* MQTTClientCocoaMQTT+Client.swift */; }; + 2282DA632811290400F79E65 /* ReceivedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B38948280EEC6900D23022 /* ReceivedMessage.swift */; }; + 2282DA642811290400F79E65 /* Host+ActualUserPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B38942280ED2FC00D23022 /* Host+ActualUserPassword.swift */; }; + 2282DA652811290400F79E65 /* MQTT5Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22953B36280AEDB7000F8F37 /* MQTT5Delegate.swift */; }; + 2282DA662811290400F79E65 /* MQTT5ClientCocoaMQTT+OnMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B3893A280EA9FB00D23022 /* MQTT5ClientCocoaMQTT+OnMessage.swift */; }; + 2282DA672811290400F79E65 /* MQTT5ClientCocoaMQTT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22953B37280AEDC0000F8F37 /* MQTT5ClientCocoaMQTT.swift */; }; + 2282DA682811290400F79E65 /* MQTTClientCocoaMQTT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A6B5124445BA400ACDFC3 /* MQTTClientCocoaMQTT.swift */; }; + 2282DA692811290400F79E65 /* MQTTDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220357202445B2ED00A98CD3 /* MQTTDelegate.swift */; }; + 2282DA6A2811290400F79E65 /* ClientUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B38938280E885A00D23022 /* ClientUtils.swift */; }; + 2282DA6C2811297200F79E65 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA6B2811297200F79E65 /* Certificate.swift */; }; + 2282DA6E281129A100F79E65 /* String+RegExp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AB2C0E280076CB00E88875 /* String+RegExp.swift */; }; + 2282DA6F281129F800F79E65 /* CloudDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F67AE024716ED50082C79F /* CloudDataManager.swift */; }; + 2282DA70281129F800F79E65 /* FileLister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D02477D575009810E6 /* FileLister.swift */; }; + 2282DA71281129F800F79E65 /* CertificateFileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5DA2477D64B009810E6 /* CertificateFileModel.swift */; }; + 2282DA72281129F800F79E65 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA6B2811297200F79E65 /* Certificate.swift */; }; + 2282DA7328112A1900F79E65 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2200D96A27C1123000E63E89 /* Logger.swift */; }; 2285C80C2781E89B008DA37D /* LimitReachedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2285C8092781E89B008DA37D /* LimitReachedView.swift */; }; 2285C80D2781E89B008DA37D /* TopicLimitReachedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2285C80A2781E89B008DA37D /* TopicLimitReachedView.swift */; }; 2285C81227841C72008DA37D /* ResumeConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2285C81127841C72008DA37D /* ResumeConnectionView.swift */; }; @@ -176,7 +233,32 @@ 22B90E882805B2DE0083C6E5 /* SubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B90E872805B2DE0083C6E5 /* SubscriptionTests.swift */; }; 22C2856224759FD40000C1E8 /* CertificateLocationPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C2856124759FD40000C1E8 /* CertificateLocationPicker.swift */; }; 22C386A322CB84900054C385 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C386A222CB84900054C385 /* RootView.swift */; }; - 22C9F72F23B7486E00892C4B /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 22C9F72E23B7486E00892C4B /* .swiftlint.yml */; }; + 22C96DF928112B6300A4F627 /* DataMigrationEmptyTopic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2256BDA9246FBE5100F92EFE /* DataMigrationEmptyTopic.swift */; }; + 22C96DFA28112B6300A4F627 /* DataMigrationLimits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291284824685AE0006F8256 /* DataMigrationLimits.swift */; }; + 22C96DFB28112B6300A4F627 /* DataMigrationProtocolVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B38944280ED63100D23022 /* DataMigrationProtocolVersion.swift */; }; + 22C96DFC28112B6300A4F627 /* DataMigrationCertificateFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220CCD622477F12300E8CA39 /* DataMigrationCertificateFiles.swift */; }; + 22C96DFD28112B6300A4F627 /* DataMigrationMultipleTopics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229128402468590D006F8256 /* DataMigrationMultipleTopics.swift */; }; + 22C96DFE28112B6300A4F627 /* DataMigrationAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291284624685A92006F8256 /* DataMigrationAuth.swift */; }; + 22C96DFF28112B6300A4F627 /* MigrationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291284224685959006F8256 /* MigrationHelper.swift */; }; + 22C96E0028112B6300A4F627 /* DataMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F6057A23D4911000E6338B /* DataMigration.swift */; }; + 22C96E0128112B6300A4F627 /* DataMigrationClientImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291284424685A13006F8256 /* DataMigrationClientImpl.swift */; }; + 22C96E0228112B6300A4F627 /* DataMigrationNavigationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2230FEF727A4434800D4327F /* DataMigrationNavigationMode.swift */; }; + 22C96E0328112B6300A4F627 /* DataMigrationMoscapsuleDeprecation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F3AB1224A77B6600B2CF92 /* DataMigrationMoscapsuleDeprecation.swift */; }; + 22C96E0428112B6700A4F627 /* RealmPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229005CE237E7A8600D5A706 /* RealmPersistence.swift */; }; + 22C96E0528112B6700A4F627 /* StubPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224AFEAE27B0600D00DFD09F /* StubPersistence.swift */; }; + 22C96E0628112B6700A4F627 /* HostSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EF0052387084D002ADF3E /* HostSetting.swift */; }; + 22C96E0728112B6700A4F627 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224AFEB027B0602000DFD09F /* Persistence.swift */; }; + 22C96E0828112B6700A4F627 /* HostSettingExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AF3AEA23891267001D9F87 /* HostSettingExamples.swift */; }; + 22C96E0A28112BE700A4F627 /* MQTTClientSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E0928112BE700A4F627 /* MQTTClientSync.swift */; }; + 22C96E0B28112BE700A4F627 /* MQTTClientSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E0928112BE700A4F627 /* MQTTClientSync.swift */; }; + 22C96E0F2811319900A4F627 /* PublishSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2282DA0E2810738900F79E65 /* PublishSyncTests.swift */; }; + 22C96E11281172A900A4F627 /* SQLitePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E10281172A900A4F627 /* SQLitePersistence.swift */; }; + 22C96E12281172A900A4F627 /* SQLitePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E10281172A900A4F627 /* SQLitePersistence.swift */; }; + 22C96E14281177B600A4F627 /* PersistenceTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E13281177B600A4F627 /* PersistenceTransformer.swift */; }; + 22C96E15281177B600A4F627 /* PersistenceTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E13281177B600A4F627 /* PersistenceTransformer.swift */; }; + 22C96E192812C41F00A4F627 /* PublishHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E182812C41F00A4F627 /* PublishHandler.swift */; }; + 22C96E1B2812C50200A4F627 /* ReceiveHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E1A2812C50200A4F627 /* ReceiveHandler.swift */; }; + 22C96E1D2812C60200A4F627 /* Brokers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C96E1C2812C60200A4F627 /* Brokers.swift */; }; 22C9F73123B78F9700892C4B /* PublishMessageFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C9F73023B78F9700892C4B /* PublishMessageFormView.swift */; }; 22C9F73723B79A1300892C4B /* MessageTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C9F73623B79A1300892C4B /* MessageTextView.swift */; }; 22C9F73A23B8CCCC00892C4B /* QOSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C9F73923B8CCCC00892C4B /* QOSView.swift */; }; @@ -204,6 +286,7 @@ 22FD7D0522C8D2820078795F /* DataSeriesDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD7D0422C8D2810078795F /* DataSeriesDetailsView.swift */; }; 458A44A5726DF316EE6004E8 /* Pods_MQTTAnalyzerUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEE6D91479F36479741E6FCC /* Pods_MQTTAnalyzerUITests.framework */; }; B1418521773D3286B7115614 /* Pods_MQTTAnalyzer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EA00F32BE18033F923EAEA0 /* Pods_MQTTAnalyzer.framework */; }; + B871A48F9CF9EC8FD5CD46F5 /* Pods_MQTTAnalyzerIntent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A97398D1A8868944BA1F5E2 /* Pods_MQTTAnalyzerIntent.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -214,6 +297,20 @@ remoteGlobalIDString = 2253F8D122C8C007007E35A2; remoteInfo = MQTTAnalyzer; }; + 2282D9F228102EB100F79E65 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2253F8CA22C8C007007E35A2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2282D9E728102EB100F79E65; + remoteInfo = MQTTAnalyzerIntentUI; + }; + 2282D9F628102EB100F79E65 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2253F8CA22C8C007007E35A2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2282D9DC28102EB000F79E65; + remoteInfo = MQTTAnalyzerIntent; + }; 22C7F0D52416A16600534880 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2253F8CA22C8C007007E35A2 /* Project object */; @@ -223,8 +320,34 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 2282D9FA28102EB100F79E65 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 2282D9F828102EB100F79E65 /* MQTTAnalyzerIntent.appex in Embed App Extensions */, + 2282D9F428102EB100F79E65 /* MQTTAnalyzerIntentUI.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 2282DA0828103DB400F79E65 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 01671CB030544C85ED77188C /* Pods-MQTTAnalyzer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzer.debug.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzer/Pods-MQTTAnalyzer.debug.xcconfig"; sourceTree = ""; }; + 10309D143F9F34802A2586F6 /* Pods-MQTTAnalyzerIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzerIntent.debug.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzerIntent/Pods-MQTTAnalyzerIntent.debug.xcconfig"; sourceTree = ""; }; 2200D96527C0C3BF00E63E89 /* PublishTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishTests.swift; sourceTree = ""; }; 2200D96727C0CA0100E63E89 /* PublishDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishDialog.swift; sourceTree = ""; }; 2200D96A27C1123000E63E89 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -285,7 +408,7 @@ 223AF5D82477D620009810E6 /* NoFilesHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoFilesHelpView.swift; sourceTree = ""; }; 223AF5DA2477D64B009810E6 /* CertificateFileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateFileModel.swift; sourceTree = ""; }; 223EF0032382F99A002ADF3E /* MQTTAnalyzer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MQTTAnalyzer.entitlements; sourceTree = ""; }; - 223EF0052387084D002ADF3E /* HostSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HostSetting.swift; path = MQTTAnalyzer/model/HostSetting.swift; sourceTree = SOURCE_ROOT; }; + 223EF0052387084D002ADF3E /* HostSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HostSetting.swift; path = Common/model/persistence/HostSetting.swift; sourceTree = SOURCE_ROOT; }; 223EF00723870AA5002ADF3E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 2240834827AC533A00AA4A42 /* SnapshotHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = ""; }; 2240834A27AD68CE00AA4A42 /* Brokers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brokers.swift; sourceTree = ""; }; @@ -339,6 +462,24 @@ 228104922381773100112F24 /* TopicCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicCellView.swift; sourceTree = ""; }; 228104942381779000112F24 /* TopicToolsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicToolsView.swift; sourceTree = ""; }; 228104972381796B00112F24 /* HostCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HostCellView.swift; path = MQTTAnalyzer/views/HostCellView.swift; sourceTree = SOURCE_ROOT; }; + 2282D9DD28102EB000F79E65 /* MQTTAnalyzerIntent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MQTTAnalyzerIntent.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 2282D9DE28102EB000F79E65 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; + 2282D9E128102EB100F79E65 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; + 2282D9E328102EB100F79E65 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2282D9E828102EB100F79E65 /* MQTTAnalyzerIntentUI.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MQTTAnalyzerIntentUI.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 2282D9E928102EB100F79E65 /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; }; + 2282D9EC28102EB100F79E65 /* IntentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentViewController.swift; sourceTree = ""; }; + 2282D9EF28102EB100F79E65 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 2282D9F128102EB100F79E65 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2282D9F528102EB100F79E65 /* MQTTAnalyzerIntentUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MQTTAnalyzerIntentUI.entitlements; sourceTree = ""; }; + 2282D9F928102EB100F79E65 /* MQTTAnalyzerIntent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MQTTAnalyzerIntent.entitlements; sourceTree = ""; }; + 2282DA0128102ED700F79E65 /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; + 2282DA0528103DB400F79E65 /* Pods_MQTTAnalyzer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_MQTTAnalyzer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2282DA0928103DF200F79E65 /* CocoaMQTT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CocoaMQTT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2282DA0E2810738900F79E65 /* PublishSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishSyncTests.swift; sourceTree = ""; }; + 2282DA522811276000F79E65 /* HostModel+Delete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HostModel+Delete.swift"; sourceTree = ""; }; + 2282DA56281127C800F79E65 /* Handlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handlers.swift; sourceTree = ""; }; + 2282DA6B2811297200F79E65 /* Certificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Certificate.swift; sourceTree = ""; }; 2285C8092781E89B008DA37D /* LimitReachedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LimitReachedView.swift; path = MQTTAnalyzer/views/topic/connection/LimitReachedView.swift; sourceTree = SOURCE_ROOT; }; 2285C80A2781E89B008DA37D /* TopicLimitReachedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TopicLimitReachedView.swift; path = MQTTAnalyzer/views/topic/connection/TopicLimitReachedView.swift; sourceTree = SOURCE_ROOT; }; 2285C81127841C72008DA37D /* ResumeConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeConnectionView.swift; sourceTree = ""; }; @@ -351,7 +492,7 @@ 2285C82227887CDC008DA37D /* PublishMessageFormModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishMessageFormModelTests.swift; sourceTree = ""; }; 2285C82427887F79008DA37D /* TopicSuffixPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicSuffixPickerView.swift; sourceTree = ""; }; 228B069223FC318100E988D0 /* ModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelTests.swift; sourceTree = ""; }; - 229005CE237E7A8600D5A706 /* RealmPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RealmPersistence.swift; path = MQTTAnalyzer/model/persistence/RealmPersistence.swift; sourceTree = SOURCE_ROOT; }; + 229005CE237E7A8600D5A706 /* RealmPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RealmPersistence.swift; path = Common/model/persistence/RealmPersistence.swift; sourceTree = SOURCE_ROOT; }; 2291283B24681CAD006F8256 /* SubscriptionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionDetailsView.swift; sourceTree = ""; }; 2291283D24682494006F8256 /* TopicCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicCell.swift; sourceTree = ""; }; 229128402468590D006F8256 /* DataMigrationMultipleTopics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataMigrationMultipleTopics.swift; sourceTree = ""; }; @@ -407,6 +548,12 @@ 22C386A222CB84900054C385 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; 22C7F0D02416A16600534880 /* MQTTAnalyzerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MQTTAnalyzerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 22C7F0D42416A16600534880 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 22C96E0928112BE700A4F627 /* MQTTClientSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTClientSync.swift; sourceTree = ""; }; + 22C96E10281172A900A4F627 /* SQLitePersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLitePersistence.swift; sourceTree = ""; }; + 22C96E13281177B600A4F627 /* PersistenceTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceTransformer.swift; sourceTree = ""; }; + 22C96E182812C41F00A4F627 /* PublishHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishHandler.swift; sourceTree = ""; }; + 22C96E1A2812C50200A4F627 /* ReceiveHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveHandler.swift; sourceTree = ""; }; + 22C96E1C2812C60200A4F627 /* Brokers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brokers.swift; sourceTree = ""; }; 22C9F72E23B7486E00892C4B /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 22C9F73023B78F9700892C4B /* PublishMessageFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishMessageFormView.swift; sourceTree = ""; }; 22C9F73623B79A1300892C4B /* MessageTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTextView.swift; sourceTree = ""; }; @@ -437,7 +584,9 @@ 6EA00F32BE18033F923EAEA0 /* Pods_MQTTAnalyzer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MQTTAnalyzer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 771859B939F07AF34A710F07 /* Pods_MQTTAnalyzerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MQTTAnalyzerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 92AF6D7587A8674FE6DBEE42 /* Pods-MQTTAnalyzerUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzerUITests.debug.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzerUITests/Pods-MQTTAnalyzerUITests.debug.xcconfig"; sourceTree = ""; }; + 9A97398D1A8868944BA1F5E2 /* Pods_MQTTAnalyzerIntent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MQTTAnalyzerIntent.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AEE6D91479F36479741E6FCC /* Pods_MQTTAnalyzerUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MQTTAnalyzerUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BD79B0138F517C71F6255C4C /* Pods-MQTTAnalyzerIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzerIntent.release.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzerIntent/Pods-MQTTAnalyzerIntent.release.xcconfig"; sourceTree = ""; }; C1D6B6244A390AA6C066AB20 /* Pods-MQTTAnalyzer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzer.release.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzer/Pods-MQTTAnalyzer.release.xcconfig"; sourceTree = ""; }; CCEE79FFC156E6A7A3EB4B17 /* Pods-MQTTAnalyzerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzerTests.release.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzerTests/Pods-MQTTAnalyzerTests.release.xcconfig"; sourceTree = ""; }; D7D50319E94CFEFF222234D3 /* Pods-MQTTAnalyzerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MQTTAnalyzerTests.debug.xcconfig"; path = "Target Support Files/Pods-MQTTAnalyzerTests/Pods-MQTTAnalyzerTests.debug.xcconfig"; sourceTree = ""; }; @@ -462,6 +611,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2282D9DA28102EB000F79E65 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2282D9DF28102EB100F79E65 /* Intents.framework in Frameworks */, + B871A48F9CF9EC8FD5CD46F5 /* Pods_MQTTAnalyzerIntent.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2282D9E528102EB100F79E65 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2282D9EA28102EB100F79E65 /* IntentsUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 22C7F0CD2416A16600534880 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -490,52 +656,26 @@ path = "aws-iot"; sourceTree = ""; }; - 2200D97F27C91DBC00E63E89 /* search */ = { - isa = PBXGroup; - children = ( - 2200D98027C91DCA00E63E89 /* SearchIndex.swift */, - ); - path = search; - sourceTree = ""; - }; 2203571D2445850F00A98CD3 /* cocoamqtt */ = { isa = PBXGroup; children = ( + 22B38938280E885A00D23022 /* ClientUtils.swift */, + 2203571E2445852800A98CD3 /* CocoaMQTTCertificateFiles.swift */, + 22B38942280ED2FC00D23022 /* Host+ActualUserPassword.swift */, 22953B37280AEDC0000F8F37 /* MQTT5ClientCocoaMQTT.swift */, - 22B3893A280EA9FB00D23022 /* MQTT5ClientCocoaMQTT+OnMessage.swift */, 22B3893E280EACCB00D23022 /* MQTT5ClientCocoaMQTT+Client.swift */, + 22B3893A280EA9FB00D23022 /* MQTT5ClientCocoaMQTT+OnMessage.swift */, 22953B36280AEDB7000F8F37 /* MQTT5Delegate.swift */, 226A6B5124445BA400ACDFC3 /* MQTTClientCocoaMQTT.swift */, - 22B3893C280EAA9300D23022 /* MQTTClientCocoaMQTT+OnMessage.swift */, 22B38940280ECCDE00D23022 /* MQTTClientCocoaMQTT+Client.swift */, + 22B3893C280EAA9300D23022 /* MQTTClientCocoaMQTT+OnMessage.swift */, 220357202445B2ED00A98CD3 /* MQTTDelegate.swift */, - 2203571E2445852800A98CD3 /* CocoaMQTTCertificateFiles.swift */, - 22B38938280E885A00D23022 /* ClientUtils.swift */, - 22B38942280ED2FC00D23022 /* Host+ActualUserPassword.swift */, + 22C96E0928112BE700A4F627 /* MQTTClientSync.swift */, 22B38948280EEC6900D23022 /* ReceivedMessage.swift */, ); path = cocoamqtt; sourceTree = ""; }; - 2230FF0027A5206400D4327F /* v2 */ = { - isa = PBXGroup; - children = ( - 2200D97F27C91DBC00E63E89 /* search */, - 2230FEFB27A50C9700D4327F /* TopicTree.swift */, - 2230FF0B27A57DF400D4327F /* TopicTree+ReadState.swift */, - 2230FF2127A6AEC800D4327F /* TopicTree+Flatten.swift */, - 2230FF1127A65EE100D4327F /* TopicTree+Deletion.swift */, - 2230FF0527A5668200D4327F /* TopicTree+Counter.swift */, - 2230FF1927A6678B00D4327F /* TopicTree+Filter.swift */, - 2215118C27C923EB0000E385 /* TopicTree+Search.swift */, - 2230FF0D27A650CE00D4327F /* TreeUtils.swift */, - 2230FF1327A6667000D4327F /* MsgPayload.swift */, - 2230FF1527A6669C00D4327F /* MsgMetadata.swift */, - 2230FF1727A666C600D4327F /* MsgMessage.swift */, - ); - path = v2; - sourceTree = ""; - }; 2230FF2527A6BAD800D4327F /* news */ = { isa = PBXGroup; children = ( @@ -567,6 +707,7 @@ 223AF5CF2477D55C009810E6 /* icloud */ = { isa = PBXGroup; children = ( + 2282DA6B2811297200F79E65 /* Certificate.swift */, 22F67AE024716ED50082C79F /* CloudDataManager.swift */, 223AF5D02477D575009810E6 /* FileLister.swift */, 223AF5DA2477D64B009810E6 /* CertificateFileModel.swift */, @@ -574,19 +715,6 @@ path = icloud; sourceTree = ""; }; - 223EF0042387082E002ADF3E /* persistence */ = { - isa = PBXGroup; - children = ( - 2291283F246858FB006F8256 /* migration */, - 223EF0052387084D002ADF3E /* HostSetting.swift */, - 22AF3AEA23891267001D9F87 /* HostSettingExamples.swift */, - 224AFEB027B0602000DFD09F /* Persistence.swift */, - 229005CE237E7A8600D5A706 /* RealmPersistence.swift */, - 224AFEAE27B0600D00DFD09F /* StubPersistence.swift */, - ); - path = persistence; - sourceTree = ""; - }; 224AFEB827B2CE7F00DFD09F /* extensions */ = { isa = PBXGroup; children = ( @@ -605,9 +733,12 @@ children = ( 2240834827AC533A00AA4A42 /* SnapshotHelper.swift */, 22C9F72E23B7486E00892C4B /* .swiftlint.yml */, + 2282DA112811249300F79E65 /* Common */, 2253F8D422C8C007007E35A2 /* MQTTAnalyzer */, 2253F8EE22C8C008007E35A2 /* MQTTAnalyzerTests */, 22C7F0D12416A16600534880 /* MQTTAnalyzerUITests */, + 2282D9E028102EB100F79E65 /* MQTTAnalyzerIntent */, + 2282D9EB28102EB100F79E65 /* MQTTAnalyzerIntentUI */, 2253F8D322C8C007007E35A2 /* Products */, B2BAC619436C49079AC0498E /* Pods */, 45E8AFAA8B14B8DFB865D13B /* Frameworks */, @@ -622,6 +753,8 @@ 2253F8D222C8C007007E35A2 /* MQTTAnalyzer.app */, 2253F8EB22C8C008007E35A2 /* MQTTAnalyzerTests.xctest */, 22C7F0D02416A16600534880 /* MQTTAnalyzerUITests.xctest */, + 2282D9DD28102EB000F79E65 /* MQTTAnalyzerIntent.appex */, + 2282D9E828102EB100F79E65 /* MQTTAnalyzerIntentUI.appex */, ); name = Products; sourceTree = ""; @@ -629,14 +762,11 @@ 2253F8D422C8C007007E35A2 /* MQTTAnalyzer */ = { isa = PBXGroup; children = ( - 2200D96927C1122200E63E89 /* logger */, - 2285C81F27872D2A008DA37D /* json */, 223EF0032382F99A002ADF3E /* MQTTAnalyzer.entitlements */, 22FD7D0722C8D39D0078795F /* model */, 228104782381708E00112F24 /* views */, 22810479238170A200112F24 /* extensions */, 22FD7D0622C8D3760078795F /* mqtt */, - 223AF5CF2477D55C009810E6 /* icloud */, 2253F8D722C8C007007E35A2 /* SceneDelegate.swift */, 2253F8D522C8C007007E35A2 /* AppDelegate.swift */, 2253F8DE22C8C008007E35A2 /* Assets.xcassets */, @@ -680,6 +810,7 @@ 2230FF2327A6B80900D4327F /* TopicLimitTests.swift */, 2230FEF927A50C8000D4327F /* TreeModelTests.swift */, 2230FF0F27A6511F00D4327F /* TreeUtilsTests.swift */, + 2282DA0E2810738900F79E65 /* PublishSyncTests.swift */, ); path = MQTTAnalyzerTests; sourceTree = ""; @@ -774,11 +905,7 @@ isa = PBXGroup; children = ( 2230FF3227A830C900D4327F /* DataProtocol+Hex.swift */, - 22F8BEE623C24A5800422BFF /* String+Utils.swift */, - 2230FEFD27A50E9000D4327F /* Date+Iso.swift */, 224AFEA627AE88DC00DFD09F /* Color+SystemColors.swift */, - 22061AC427CA79FE00FFF915 /* DispatchQueue+Background.swift */, - 22AB2C0E280076CB00E88875 /* String+RegExp.swift */, ); path = extensions; sourceTree = ""; @@ -831,6 +958,105 @@ path = host; sourceTree = ""; }; + 2282D9E028102EB100F79E65 /* MQTTAnalyzerIntent */ = { + isa = PBXGroup; + children = ( + 2282D9F928102EB100F79E65 /* MQTTAnalyzerIntent.entitlements */, + 2282D9E128102EB100F79E65 /* IntentHandler.swift */, + 22C96E1C2812C60200A4F627 /* Brokers.swift */, + 22C96E182812C41F00A4F627 /* PublishHandler.swift */, + 22C96E1A2812C50200A4F627 /* ReceiveHandler.swift */, + 2282D9E328102EB100F79E65 /* Info.plist */, + ); + path = MQTTAnalyzerIntent; + sourceTree = ""; + }; + 2282D9EB28102EB100F79E65 /* MQTTAnalyzerIntentUI */ = { + isa = PBXGroup; + children = ( + 2282D9F528102EB100F79E65 /* MQTTAnalyzerIntentUI.entitlements */, + 2282D9EC28102EB100F79E65 /* IntentViewController.swift */, + 2282D9EE28102EB100F79E65 /* MainInterface.storyboard */, + 2282D9F128102EB100F79E65 /* Info.plist */, + ); + path = MQTTAnalyzerIntentUI; + sourceTree = ""; + }; + 2282DA112811249300F79E65 /* Common */ = { + isa = PBXGroup; + children = ( + 2282DA4D281126BD00F79E65 /* extensions */, + 2200D96927C1122200E63E89 /* logger */, + 2282DA132811253600F79E65 /* model */, + 2282DA5C2811282700F79E65 /* mqtt */, + 2285C81F27872D2A008DA37D /* json */, + 223AF5CF2477D55C009810E6 /* icloud */, + 2282DA0128102ED700F79E65 /* Intents.intentdefinition */, + ); + path = Common; + sourceTree = ""; + }; + 2282DA132811253600F79E65 /* model */ = { + isa = PBXGroup; + children = ( + 22C96DF828112B3500A4F627 /* persistence */, + 2282DA1E2811258B00F79E65 /* search */, + 2282DA20281125BE00F79E65 /* tree */, + 228911CF22CF86B20095301F /* timeseries */, + 2230FF1327A6667000D4327F /* MsgPayload.swift */, + 2230FF1527A6669C00D4327F /* MsgMetadata.swift */, + 2230FF1727A666C600D4327F /* MsgMessage.swift */, + 22D50F9622CE4C4300F37EAF /* Multimap.swift */, + 22E469D6237FC03500D72BD6 /* Readstate.swift */, + 22C9F73D23BB5E5C00892C4B /* HostModel.swift */, + 2282DA56281127C800F79E65 /* Handlers.swift */, + ); + path = model; + sourceTree = ""; + }; + 2282DA1E2811258B00F79E65 /* search */ = { + isa = PBXGroup; + children = ( + 2200D98027C91DCA00E63E89 /* SearchIndex.swift */, + ); + path = search; + sourceTree = ""; + }; + 2282DA20281125BE00F79E65 /* tree */ = { + isa = PBXGroup; + children = ( + 2230FEFB27A50C9700D4327F /* TopicTree.swift */, + 2230FF0B27A57DF400D4327F /* TopicTree+ReadState.swift */, + 2230FF2127A6AEC800D4327F /* TopicTree+Flatten.swift */, + 2230FF1127A65EE100D4327F /* TopicTree+Deletion.swift */, + 2230FF0527A5668200D4327F /* TopicTree+Counter.swift */, + 2230FF1927A6678B00D4327F /* TopicTree+Filter.swift */, + 2215118C27C923EB0000E385 /* TopicTree+Search.swift */, + 2230FF0D27A650CE00D4327F /* TreeUtils.swift */, + ); + path = tree; + sourceTree = ""; + }; + 2282DA4D281126BD00F79E65 /* extensions */ = { + isa = PBXGroup; + children = ( + 22F8BEE623C24A5800422BFF /* String+Utils.swift */, + 22AB2C0E280076CB00E88875 /* String+RegExp.swift */, + 2230FEFD27A50E9000D4327F /* Date+Iso.swift */, + 22061AC427CA79FE00FFF915 /* DispatchQueue+Background.swift */, + ); + path = extensions; + sourceTree = ""; + }; + 2282DA5C2811282700F79E65 /* mqtt */ = { + isa = PBXGroup; + children = ( + 2203571D2445850F00A98CD3 /* cocoamqtt */, + 226A6B4F244457E100ACDFC3 /* MqttClient.swift */, + ); + path = mqtt; + sourceTree = ""; + }; 2285C81F27872D2A008DA37D /* json */ = { isa = PBXGroup; children = ( @@ -905,6 +1131,7 @@ 2240834A27AD68CE00AA4A42 /* Brokers.swift */, 224AFEB427B2C3FA00DFD09F /* Broker.swift */, 2240834E27AD6F1800AA4A42 /* ExampleMessages.swift */, + 2282DA0528103DB400F79E65 /* Pods_MQTTAnalyzer.framework */, ); name = "Recovered References"; sourceTree = ""; @@ -947,6 +1174,21 @@ path = MQTTAnalyzerUITests; sourceTree = ""; }; + 22C96DF828112B3500A4F627 /* persistence */ = { + isa = PBXGroup; + children = ( + 2291283F246858FB006F8256 /* migration */, + 223EF0052387084D002ADF3E /* HostSetting.swift */, + 22AF3AEA23891267001D9F87 /* HostSettingExamples.swift */, + 224AFEB027B0602000DFD09F /* Persistence.swift */, + 224AFEAE27B0600D00DFD09F /* StubPersistence.swift */, + 229005CE237E7A8600D5A706 /* RealmPersistence.swift */, + 22C96E10281172A900A4F627 /* SQLitePersistence.swift */, + 22C96E13281177B600A4F627 /* PersistenceTransformer.swift */, + ); + path = persistence; + sourceTree = ""; + }; 22C9F73823B79BFD00892C4B /* multiline-text */ = { isa = PBXGroup; children = ( @@ -976,9 +1218,7 @@ 22FD7D0622C8D3760078795F /* mqtt */ = { isa = PBXGroup; children = ( - 2203571D2445850F00A98CD3 /* cocoamqtt */, 22FD7CF822C8D2650078795F /* MQTTSessionController.swift */, - 226A6B4F244457E100ACDFC3 /* MqttClient.swift */, ); path = mqtt; sourceTree = ""; @@ -986,14 +1226,9 @@ 22FD7D0722C8D39D0078795F /* model */ = { isa = PBXGroup; children = ( - 2230FF0027A5206400D4327F /* v2 */, - 223EF0042387082E002ADF3E /* persistence */, - 228911CF22CF86B20095301F /* timeseries */, 22FD7CFB22C8D2650078795F /* RootModel.swift */, - 22D50F9622CE4C4300F37EAF /* Multimap.swift */, - 22E469D6237FC03500D72BD6 /* Readstate.swift */, - 22C9F73D23BB5E5C00892C4B /* HostModel.swift */, 2230FF2E27A6CDB800D4327F /* Welcome.swift */, + 2282DA522811276000F79E65 /* HostModel+Delete.swift */, ); path = model; sourceTree = ""; @@ -1001,6 +1236,7 @@ 45E8AFAA8B14B8DFB865D13B /* Frameworks */ = { isa = PBXGroup; children = ( + 2282DA0928103DF200F79E65 /* CocoaMQTT.framework */, 2240835027AD701000AA4A42 /* CocoaMQTT.framework */, 223EF00723870AA5002ADF3E /* CloudKit.framework */, 229D30C923197D8200632896 /* CocoaAsyncSocket.framework */, @@ -1008,6 +1244,9 @@ 6EA00F32BE18033F923EAEA0 /* Pods_MQTTAnalyzer.framework */, 771859B939F07AF34A710F07 /* Pods_MQTTAnalyzerTests.framework */, AEE6D91479F36479741E6FCC /* Pods_MQTTAnalyzerUITests.framework */, + 2282D9DE28102EB000F79E65 /* Intents.framework */, + 2282D9E928102EB100F79E65 /* IntentsUI.framework */, + 9A97398D1A8868944BA1F5E2 /* Pods_MQTTAnalyzerIntent.framework */, ); name = Frameworks; sourceTree = ""; @@ -1021,6 +1260,8 @@ CCEE79FFC156E6A7A3EB4B17 /* Pods-MQTTAnalyzerTests.release.xcconfig */, 92AF6D7587A8674FE6DBEE42 /* Pods-MQTTAnalyzerUITests.debug.xcconfig */, 4ADA38A28345B5524D23563B /* Pods-MQTTAnalyzerUITests.release.xcconfig */, + 10309D143F9F34802A2586F6 /* Pods-MQTTAnalyzerIntent.debug.xcconfig */, + BD79B0138F517C71F6255C4C /* Pods-MQTTAnalyzerIntent.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1038,10 +1279,13 @@ 2253F8D022C8C007007E35A2 /* Resources */, 22C9F72D23B747E800892C4B /* ShellScript */, E5E6333FBC9D713242D1C693 /* [CP] Embed Pods Frameworks */, + 2282D9FA28102EB100F79E65 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 2282D9F328102EB100F79E65 /* PBXTargetDependency */, + 2282D9F728102EB100F79E65 /* PBXTargetDependency */, ); name = MQTTAnalyzer; packageProductDependencies = ( @@ -1070,6 +1314,42 @@ productReference = 2253F8EB22C8C008007E35A2 /* MQTTAnalyzerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 2282D9DC28102EB000F79E65 /* MQTTAnalyzerIntent */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2282DA0028102EB100F79E65 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerIntent" */; + buildPhases = ( + 6B08FEE4E1DF7FC7F71DEBB2 /* [CP] Check Pods Manifest.lock */, + 2282D9D928102EB000F79E65 /* Sources */, + 2282D9DA28102EB000F79E65 /* Frameworks */, + 2282D9DB28102EB000F79E65 /* Resources */, + 2282DA0828103DB400F79E65 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MQTTAnalyzerIntent; + productName = MQTTAnalyzerIntent; + productReference = 2282D9DD28102EB000F79E65 /* MQTTAnalyzerIntent.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 2282D9E728102EB100F79E65 /* MQTTAnalyzerIntentUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2282D9FF28102EB100F79E65 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerIntentUI" */; + buildPhases = ( + 2282D9E428102EB100F79E65 /* Sources */, + 2282D9E528102EB100F79E65 /* Frameworks */, + 2282D9E628102EB100F79E65 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MQTTAnalyzerIntentUI; + productName = MQTTAnalyzerIntentUI; + productReference = 2282D9E828102EB100F79E65 /* MQTTAnalyzerIntentUI.appex */; + productType = "com.apple.product-type.app-extension"; + }; 22C7F0CF2416A16600534880 /* MQTTAnalyzerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 22C7F0D72416A16600534880 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerUITests" */; @@ -1096,7 +1376,7 @@ 2253F8CA22C8C007007E35A2 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1130; + LastSwiftUpdateCheck = 1330; LastUpgradeCheck = 1230; ORGANIZATIONNAME = "Philipp Arndt"; TargetAttributes = { @@ -1107,6 +1387,12 @@ CreatedOnToolsVersion = 11.0; TestTargetID = 2253F8D122C8C007007E35A2; }; + 2282D9DC28102EB000F79E65 = { + CreatedOnToolsVersion = 13.3.1; + }; + 2282D9E728102EB100F79E65 = { + CreatedOnToolsVersion = 13.3.1; + }; 22C7F0CF2416A16600534880 = { CreatedOnToolsVersion = 11.3.1; TestTargetID = 2253F8D122C8C007007E35A2; @@ -1132,6 +1418,8 @@ 2253F8D122C8C007007E35A2 /* MQTTAnalyzer */, 2253F8EA22C8C008007E35A2 /* MQTTAnalyzerTests */, 22C7F0CF2416A16600534880 /* MQTTAnalyzerUITests */, + 2282D9DC28102EB000F79E65 /* MQTTAnalyzerIntent */, + 2282D9E728102EB100F79E65 /* MQTTAnalyzerIntentUI */, ); }; /* End PBXProject section */ @@ -1141,7 +1429,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 22C9F72F23B7486E00892C4B /* .swiftlint.yml in Resources */, 2253F8E522C8C008007E35A2 /* LaunchScreen.storyboard in Resources */, 2253F8E222C8C008007E35A2 /* Preview Assets.xcassets in Resources */, 2253F8DF22C8C008007E35A2 /* Assets.xcassets in Resources */, @@ -1155,6 +1442,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2282D9DB28102EB000F79E65 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2282D9E628102EB100F79E65 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2282D9F028102EB100F79E65 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 22C7F0CE2416A16600534880 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1204,6 +1506,28 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 6B08FEE4E1DF7FC7F71DEBB2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-MQTTAnalyzerIntent-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; A135683D35F54EB2DBA70F65 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1296,6 +1620,7 @@ 22A351DE24A76463001B8AEE /* PublishMessageModel.swift in Sources */, 22A38707240BD9E600DF8F94 /* TimeSeriesValueUtil.swift in Sources */, 2202FFE224059D2A00161AD9 /* AuthenticationTypePicker.swift in Sources */, + 22C96E11281172A900A4F627 /* SQLitePersistence.swift in Sources */, 22061AC527CA79FE00FFF915 /* DispatchQueue+Background.swift in Sources */, 2281048C2381744300112F24 /* TopicView.swift in Sources */, 2230FF1427A6667000D4327F /* MsgPayload.swift in Sources */, @@ -1329,6 +1654,7 @@ 2230FF1827A666C600D4327F /* MsgMessage.swift in Sources */, 22D50F9722CE4C4300F37EAF /* Multimap.swift in Sources */, 220CCD632477F12300E8CA39 /* DataMigrationCertificateFiles.swift in Sources */, + 2282DA57281127C800F79E65 /* Handlers.swift in Sources */, 22810488238173BB00112F24 /* DataSeriesView.swift in Sources */, 223AF5D72477D60A009810E6 /* PKCS12HelpView.swift in Sources */, 2281048A2381740000112F24 /* MessageView.swift in Sources */, @@ -1391,6 +1717,8 @@ 226A6B50244457E100ACDFC3 /* MqttClient.swift in Sources */, 22953B33280A92D8000F8F37 /* ClientCertsHelpView.swift in Sources */, 22F6057B23D4911000E6338B /* DataMigration.swift in Sources */, + 22C96E0A28112BE700A4F627 /* MQTTClientSync.swift in Sources */, + 2282DA0428103BDD00F79E65 /* Intents.intentdefinition in Sources */, 2285C81C27847786008DA37D /* MetadataView.swift in Sources */, 2291284524685A13006F8256 /* DataMigrationClientImpl.swift in Sources */, 228104952381779000112F24 /* TopicToolsView.swift in Sources */, @@ -1398,6 +1726,7 @@ 22B3893F280EACCB00D23022 /* MQTT5ClientCocoaMQTT+Client.swift in Sources */, 22AF3AE9238885AF001D9F87 /* EditHostFormDialog.swift in Sources */, 2230FF0827A56BDE00D4327F /* FolderNavigationView.swift in Sources */, + 2282DA6C2811297200F79E65 /* Certificate.swift in Sources */, 2285C81427841CA1008DA37D /* LoginView.swift in Sources */, 22B38947280ED79000D23022 /* ProtocolVersionPicker.swift in Sources */, 22AE64342412636300C2C4FE /* DiagramPath.swift in Sources */, @@ -1409,6 +1738,7 @@ 2200D97827C237EF00E63E89 /* AWSIOTHelpView.swift in Sources */, 226A6B65244575AF00ACDFC3 /* LimitsFormView.swift in Sources */, 226A6B5F2445754D00ACDFC3 /* ServerFormView.swift in Sources */, + 22C96E14281177B600A4F627 /* PersistenceTransformer.swift in Sources */, 2291424C23BF78000086C251 /* AboutView.swift in Sources */, 22AE64362412637A00C2C4FE /* TimeSeriesValue.swift in Sources */, 22C9F73723B79A1300892C4B /* MessageTextView.swift in Sources */, @@ -1424,6 +1754,7 @@ 2230FF1227A65EE100D4327F /* TopicTree+Deletion.swift in Sources */, 2230FF2B27A6BC1C00D4327F /* InformationDetailView.swift in Sources */, 226A6B5624449F5400ACDFC3 /* ProtocolPicker.swift in Sources */, + 2282DA532811276000F79E65 /* HostModel+Delete.swift in Sources */, 2230FF0627A5668200D4327F /* TopicTree+Counter.swift in Sources */, 223AF5D12477D575009810E6 /* FileLister.swift in Sources */, 223AF5DB2477D64B009810E6 /* CertificateFileModel.swift in Sources */, @@ -1436,6 +1767,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 22C96E0F2811319900A4F627 /* PublishSyncTests.swift in Sources */, 228B069323FC318100E988D0 /* ModelTests.swift in Sources */, 221C57202466CC2800C0DD02 /* AWSIOTPresetTests.swift in Sources */, 22A387052409768100DF8F94 /* HostModelPersistenceTests.swift in Sources */, @@ -1443,6 +1775,7 @@ 22AB2C132800854700E88875 /* MqttClientCocoaMQTTTests.swift in Sources */, 2230FEFA27A50C8000D4327F /* TreeModelTests.swift in Sources */, 2230FF1027A6511F00D4327F /* TreeUtilsTests.swift in Sources */, + 2282DA5A281127F400F79E65 /* JSONUtils.swift in Sources */, 22E914DF25A8420700BEC599 /* HostFormModelTests.swift in Sources */, 2230FF0227A53EA700D4327F /* ModelTestExtensions.swift in Sources */, 22D236F323FEF86E0003D87F /* MultimapTests.swift in Sources */, @@ -1459,6 +1792,89 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2282D9D928102EB000F79E65 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2282DA5E2811285000F79E65 /* MqttClient.swift in Sources */, + 2282DA162811254F00F79E65 /* MsgMetadata.swift in Sources */, + 2282DA50281126D400F79E65 /* String+Utils.swift in Sources */, + 2282DA612811290400F79E65 /* MQTTClientCocoaMQTT+OnMessage.swift in Sources */, + 2282DA3E2811261A00F79E65 /* TimeSeriesValueUtil.swift in Sources */, + 2282DA462811265500F79E65 /* SearchIndex.swift in Sources */, + 2282DA5B281127F600F79E65 /* JSONUtils.swift in Sources */, + 22C96E0228112B6300A4F627 /* DataMigrationNavigationMode.swift in Sources */, + 22C96E0128112B6300A4F627 /* DataMigrationClientImpl.swift in Sources */, + 2282DA682811290400F79E65 /* MQTTClientCocoaMQTT.swift in Sources */, + 2282DA7328112A1900F79E65 /* Logger.swift in Sources */, + 22C96E0528112B6700A4F627 /* StubPersistence.swift in Sources */, + 2282DA4B2811268C00F79E65 /* Multimap.swift in Sources */, + 2282DA632811290400F79E65 /* ReceivedMessage.swift in Sources */, + 2282DA412811261A00F79E65 /* TimeSeriesModel.swift in Sources */, + 2282DA622811290400F79E65 /* MQTTClientCocoaMQTT+Client.swift in Sources */, + 22C96E15281177B600A4F627 /* PersistenceTransformer.swift in Sources */, + 2282DA4A2811268C00F79E65 /* HostModel.swift in Sources */, + 22C96E0428112B6700A4F627 /* RealmPersistence.swift in Sources */, + 2282DA70281129F800F79E65 /* FileLister.swift in Sources */, + 2282DA652811290400F79E65 /* MQTT5Delegate.swift in Sources */, + 2282DA3B281125E900F79E65 /* TopicTree+ReadState.swift in Sources */, + 2282DA0228102ED700F79E65 /* Intents.intentdefinition in Sources */, + 2282DA182811254F00F79E65 /* MsgPayload.swift in Sources */, + 22C96E192812C41F00A4F627 /* PublishHandler.swift in Sources */, + 2282DA36281125E900F79E65 /* TopicTree+Counter.swift in Sources */, + 2282DA6F281129F800F79E65 /* CloudDataManager.swift in Sources */, + 2282DA672811290400F79E65 /* MQTT5ClientCocoaMQTT.swift in Sources */, + 2282DA692811290400F79E65 /* MQTTDelegate.swift in Sources */, + 2282DA38281125E900F79E65 /* TopicTree+Search.swift in Sources */, + 22C96E0828112B6700A4F627 /* HostSettingExamples.swift in Sources */, + 22C96DF928112B6300A4F627 /* DataMigrationEmptyTopic.swift in Sources */, + 22C96DFA28112B6300A4F627 /* DataMigrationLimits.swift in Sources */, + 2282D9E228102EB100F79E65 /* IntentHandler.swift in Sources */, + 22C96E0B28112BE700A4F627 /* MQTTClientSync.swift in Sources */, + 22C96DFE28112B6300A4F627 /* DataMigrationAuth.swift in Sources */, + 22C96DFC28112B6300A4F627 /* DataMigrationCertificateFiles.swift in Sources */, + 2282DA642811290400F79E65 /* Host+ActualUserPassword.swift in Sources */, + 2282DA59281127D800F79E65 /* Handlers.swift in Sources */, + 2282DA552811278500F79E65 /* DispatchQueue+Background.swift in Sources */, + 2282DA3A281125E900F79E65 /* TreeUtils.swift in Sources */, + 22C96DFF28112B6300A4F627 /* MigrationHelper.swift in Sources */, + 22C96E12281172A900A4F627 /* SQLitePersistence.swift in Sources */, + 2282DA71281129F800F79E65 /* CertificateFileModel.swift in Sources */, + 22C96DFB28112B6300A4F627 /* DataMigrationProtocolVersion.swift in Sources */, + 2282DA72281129F800F79E65 /* Certificate.swift in Sources */, + 2282DA5F2811290400F79E65 /* CocoaMQTTCertificateFiles.swift in Sources */, + 2282DA6E281129A100F79E65 /* String+RegExp.swift in Sources */, + 2282DA3C281125E900F79E65 /* TopicTree+Deletion.swift in Sources */, + 2282DA142811254F00F79E65 /* MsgMessage.swift in Sources */, + 2282DA35281125E900F79E65 /* TopicTree.swift in Sources */, + 2282DA602811290400F79E65 /* MQTT5ClientCocoaMQTT+Client.swift in Sources */, + 22C96E0028112B6300A4F627 /* DataMigration.swift in Sources */, + 2282DA662811290400F79E65 /* MQTT5ClientCocoaMQTT+OnMessage.swift in Sources */, + 2282DA4C2811268C00F79E65 /* Readstate.swift in Sources */, + 2282DA39281125E900F79E65 /* TopicTree+Flatten.swift in Sources */, + 22C96E0628112B6700A4F627 /* HostSetting.swift in Sources */, + 2282DA3F2811261A00F79E65 /* DiagramPath.swift in Sources */, + 22C96E1D2812C60200A4F627 /* Brokers.swift in Sources */, + 22C96E1B2812C50200A4F627 /* ReceiveHandler.swift in Sources */, + 22C96E0328112B6300A4F627 /* DataMigrationMoscapsuleDeprecation.swift in Sources */, + 22C96E0728112B6700A4F627 /* Persistence.swift in Sources */, + 22C96DFD28112B6300A4F627 /* DataMigrationMultipleTopics.swift in Sources */, + 2282DA6A2811290400F79E65 /* ClientUtils.swift in Sources */, + 2282DA51281126D400F79E65 /* Date+Iso.swift in Sources */, + 2282DA402811261A00F79E65 /* TimeSeriesValue.swift in Sources */, + 2282DA37281125E900F79E65 /* TopicTree+Filter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2282D9E428102EB100F79E65 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2282DA03281038B600F79E65 /* Intents.intentdefinition in Sources */, + 2282D9ED28102EB100F79E65 /* IntentViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 22C7F0CC2416A16600534880 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1502,6 +1918,16 @@ target = 2253F8D122C8C007007E35A2 /* MQTTAnalyzer */; targetProxy = 2253F8EC22C8C008007E35A2 /* PBXContainerItemProxy */; }; + 2282D9F328102EB100F79E65 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2282D9E728102EB100F79E65 /* MQTTAnalyzerIntentUI */; + targetProxy = 2282D9F228102EB100F79E65 /* PBXContainerItemProxy */; + }; + 2282D9F728102EB100F79E65 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2282D9DC28102EB000F79E65 /* MQTTAnalyzerIntent */; + targetProxy = 2282D9F628102EB100F79E65 /* PBXContainerItemProxy */; + }; 22C7F0D62416A16600534880 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2253F8D122C8C007007E35A2 /* MQTTAnalyzer */; @@ -1518,6 +1944,14 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + 2282D9EE28102EB100F79E65 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 2282D9EF28102EB100F79E65 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -1641,10 +2075,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = 01671CB030544C85ED77188C /* Pods-MQTTAnalyzer.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MQTTAnalyzer/MQTTAnalyzer.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 146; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_ASSET_PATHS = "MQTTAnalyzer/Preview\\ Content"; DEVELOPMENT_TEAM = 643R6YSRER; @@ -1668,10 +2103,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = C1D6B6244A390AA6C066AB20 /* Pods-MQTTAnalyzer.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MQTTAnalyzer/MQTTAnalyzer.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 146; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_ASSET_PATHS = "MQTTAnalyzer/Preview\\ Content"; DEVELOPMENT_TEAM = 643R6YSRER; @@ -1735,6 +2171,124 @@ }; name = Release; }; + 2282D9FB28102EB100F79E65 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 10309D143F9F34802A2586F6 /* Pods-MQTTAnalyzerIntent.debug.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = MQTTAnalyzerIntent/MQTTAnalyzerIntent.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 146; + DEVELOPMENT_TEAM = 643R6YSRER; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MQTTAnalyzerIntent/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MQTTAnalyzerIntent; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Philipp Arndt. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.7.0; + PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer.MQTTAnalyzerIntent; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2282D9FC28102EB100F79E65 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BD79B0138F517C71F6255C4C /* Pods-MQTTAnalyzerIntent.release.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = MQTTAnalyzerIntent/MQTTAnalyzerIntent.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 146; + DEVELOPMENT_TEAM = 643R6YSRER; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MQTTAnalyzerIntent/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MQTTAnalyzerIntent; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Philipp Arndt. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.7.0; + PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer.MQTTAnalyzerIntent; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 2282D9FD28102EB100F79E65 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = MQTTAnalyzerIntentUI/MQTTAnalyzerIntentUI.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 146; + DEVELOPMENT_TEAM = 643R6YSRER; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MQTTAnalyzerIntentUI/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MQTTAnalyzerIntentUI; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Philipp Arndt. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.7.0; + PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer.MQTTAnalyzerIntentUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2282D9FE28102EB100F79E65 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = MQTTAnalyzerIntentUI/MQTTAnalyzerIntentUI.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 146; + DEVELOPMENT_TEAM = 643R6YSRER; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MQTTAnalyzerIntentUI/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MQTTAnalyzerIntentUI; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Philipp Arndt. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.7.0; + PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer.MQTTAnalyzerIntentUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 22C7F0D82416A16600534880 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 92AF6D7587A8674FE6DBEE42 /* Pods-MQTTAnalyzerUITests.debug.xcconfig */; @@ -1807,6 +2361,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 2282D9FF28102EB100F79E65 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerIntentUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2282D9FD28102EB100F79E65 /* Debug */, + 2282D9FE28102EB100F79E65 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2282DA0028102EB100F79E65 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerIntent" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2282D9FB28102EB100F79E65 /* Debug */, + 2282D9FC28102EB100F79E65 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 22C7F0D72416A16600534880 /* Build configuration list for PBXNativeTarget "MQTTAnalyzerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/src/MQTTAnalyzer/Info.plist b/src/MQTTAnalyzer/Info.plist index 19e4bbd0..ea116123 100644 --- a/src/MQTTAnalyzer/Info.plist +++ b/src/MQTTAnalyzer/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 141 + 146 ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -29,6 +29,8 @@ NSAllowsArbitraryLoads + NSSiriUsageDescription + Siri support is used to suggest shortcuts NSUbiquitousContainers iCloud.de.rnd7.MQTTAnalyzer @@ -41,6 +43,11 @@ One + NSUserActivityTypes + + PublishMQTTMessageIntent + ReceiveMQTTMessageIntent + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/src/MQTTAnalyzer/MQTTAnalyzer.entitlements b/src/MQTTAnalyzer/MQTTAnalyzer.entitlements index 7a83897f..7cd874a8 100644 --- a/src/MQTTAnalyzer/MQTTAnalyzer.entitlements +++ b/src/MQTTAnalyzer/MQTTAnalyzer.entitlements @@ -13,6 +13,8 @@ CloudKit CloudDocuments + com.apple.developer.siri + com.apple.developer.ubiquity-container-identifiers iCloud.de.rnd7.MQTTAnalyzer @@ -21,6 +23,10 @@ $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox + com.apple.security.application-groups + + group.de.rnd7.mqttanalyzer + com.apple.security.network.client diff --git a/src/MQTTAnalyzer/model/HostModel+Delete.swift b/src/MQTTAnalyzer/model/HostModel+Delete.swift new file mode 100644 index 00000000..ff946647 --- /dev/null +++ b/src/MQTTAnalyzer/model/HostModel+Delete.swift @@ -0,0 +1,26 @@ +// +// HostModel+Delete.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 21.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation + +extension HostsModel { + func delete(at offsets: IndexSet, persistence: Persistence) { + let original = hostsSorted + + for idx in offsets { + persistence.delete(original[idx]) + } + + persistence.load() + } + + func delete(_ host: Host, persistence: Persistence) { + persistence.delete(host) + persistence.load() + } +} diff --git a/src/MQTTAnalyzer/model/RootModel.swift b/src/MQTTAnalyzer/model/RootModel.swift index b30102d4..dad33846 100644 --- a/src/MQTTAnalyzer/model/RootModel.swift +++ b/src/MQTTAnalyzer/model/RootModel.swift @@ -9,18 +9,6 @@ import SwiftUI import Combine -protocol InitHost: AnyObject { - func initHost(host: Host) -} - -protocol ReconnectDelegate: AnyObject { - func reconnect(host: Host) -} - -protocol DisconnectDelegate: AnyObject { - func disconnect(host: Host) -} - class RootModel: ObservableObject { static let controller = MQTTSessionController() diff --git a/src/MQTTAnalyzer/model/Welcome.swift b/src/MQTTAnalyzer/model/Welcome.swift index 779eb0ce..b5a722cd 100644 --- a/src/MQTTAnalyzer/model/Welcome.swift +++ b/src/MQTTAnalyzer/model/Welcome.swift @@ -9,5 +9,5 @@ import Foundation class Welcome { - static let key = "welcome-2.0.0-b63" + static let key = "welcome-2.7.0-b146" } diff --git a/src/MQTTAnalyzer/views/host/form/HostFormModel.swift b/src/MQTTAnalyzer/views/host/form/HostFormModel.swift index 926499ff..28674cd3 100644 --- a/src/MQTTAnalyzer/views/host/form/HostFormModel.swift +++ b/src/MQTTAnalyzer/views/host/form/HostFormModel.swift @@ -111,10 +111,6 @@ func copyHost(target: Host, source host: HostFormModel) -> Host? { return target } -func getCertificate(_ host: Host, type: CertificateFileType) -> CertificateFile? { - return host.certificates.filter { $0.type == type }.first -} - func transformHost(source host: Host) -> HostFormModel { return HostFormModel(alias: host.alias, hostname: host.hostname, diff --git a/src/MQTTAnalyzer/views/news/InformationContainerView.swift b/src/MQTTAnalyzer/views/news/InformationContainerView.swift index 327941e9..f015f087 100644 --- a/src/MQTTAnalyzer/views/news/InformationContainerView.swift +++ b/src/MQTTAnalyzer/views/news/InformationContainerView.swift @@ -11,23 +11,29 @@ import SwiftUI struct InformationContainerView: View { var body: some View { VStack(alignment: .leading) { + HStack(alignment: .center) { + Spacer() + Text("New in 2.7").font(.subheadline) + Spacer() + } + InformationDetailView( - title: "Folder and flat view", - subTitle: "Toggle between folder and flat view. You will have a much more structured view of your broker.", - imageName: "folder", + title: "Siri Shortcuts", + subTitle: "Publish and receive messages in the Shortcuts app.", + imageName: "flowchart", color: .secondary ) InformationDetailView( - title: "Mac application", - subTitle: "MQTTAnalyzer is now available in the App Store on your Mac for free and open source.", - imageName: "desktopcomputer", + title: "MQTT 5", + subTitle: "Connect to MQTT 5.0 brokers. View message MIME types and properties.", + imageName: "5.circle", color: .secondary ) InformationDetailView( title: "Feedback welcome", - subTitle: "App Store and GitHub stars are good for motivation. Feedback and contributions like ideas, documentation, source code are welcome.", + subTitle: "App Store and GitHub stars are good for motivation. Feedback and contributions like ideas, documentation, and source code are welcome.", imageName: "star", color: .yellow ) diff --git a/src/MQTTAnalyzer/views/news/InformationDetailView.swift b/src/MQTTAnalyzer/views/news/InformationDetailView.swift index da7613b3..0ea13b1f 100644 --- a/src/MQTTAnalyzer/views/news/InformationDetailView.swift +++ b/src/MQTTAnalyzer/views/news/InformationDetailView.swift @@ -28,6 +28,7 @@ struct InformationDetailView: View { .font(.headline) .foregroundColor(.primary) .accessibility(addTraits: .isHeader) + .padding([.bottom], 1) Text(subTitle) .font(.body) diff --git a/src/MQTTAnalyzerIntent/Brokers.swift b/src/MQTTAnalyzerIntent/Brokers.swift new file mode 100644 index 00000000..37828d93 --- /dev/null +++ b/src/MQTTAnalyzerIntent/Brokers.swift @@ -0,0 +1,25 @@ +// +// Brokers.swift +// MQTTAnalyzerIntent +// +// Created by Philipp Arndt on 22.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Foundation + +func loadBrokers() -> [NSString] { + let sqlite = SQLitePersistence() + let brokers = sqlite.allNames() + sqlite.close() + + return brokers + .map { $0 as NSString } +} + +func firstBroker(by name: String) -> Host? { + let sqlite = SQLitePersistence() + let broker = sqlite.first(by: name) + sqlite.close() + return broker +} diff --git a/src/MQTTAnalyzerIntent/Info.plist b/src/MQTTAnalyzerIntent/Info.plist new file mode 100644 index 00000000..5cd37d48 --- /dev/null +++ b/src/MQTTAnalyzerIntent/Info.plist @@ -0,0 +1,30 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSExtension + + NSExtensionAttributes + + IntentsRestrictedWhileLocked + + IntentsRestrictedWhileProtectedDataUnavailable + + IntentsSupported + + PublishMQTTMessageIntent + ReceiveMQTTMessageIntent + + + NSExtensionPointIdentifier + com.apple.intents-service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).IntentHandler + + + diff --git a/src/MQTTAnalyzerIntent/IntentHandler.swift b/src/MQTTAnalyzerIntent/IntentHandler.swift new file mode 100644 index 00000000..87a20ded --- /dev/null +++ b/src/MQTTAnalyzerIntent/IntentHandler.swift @@ -0,0 +1,26 @@ +// +// IntentHandler.swift +// MQTTAnalyzerIntent +// +// Created by Philipp Arndt on 20.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Intents +import SwiftUI +import CocoaMQTT + +class IntentHandler: INExtension { + + override func handler(for intent: INIntent) -> Any { + if intent is PublishMQTTMessageIntent { + return PublishHandler() + } + else if intent is ReceiveMQTTMessageIntent { + return ReceiveHandler() + } + + fatalError("Unhandled Intent error : \(intent)") + } + +} diff --git a/src/MQTTAnalyzerIntent/MQTTAnalyzerIntent.entitlements b/src/MQTTAnalyzerIntent/MQTTAnalyzerIntent.entitlements new file mode 100644 index 00000000..a064ada6 --- /dev/null +++ b/src/MQTTAnalyzerIntent/MQTTAnalyzerIntent.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.de.rnd7.mqttanalyzer + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/src/MQTTAnalyzerIntent/PublishHandler.swift b/src/MQTTAnalyzerIntent/PublishHandler.swift new file mode 100644 index 00000000..a326789d --- /dev/null +++ b/src/MQTTAnalyzerIntent/PublishHandler.swift @@ -0,0 +1,102 @@ +// +// PushHandler.swift +// MQTTAnalyzerIntent +// +// Created by Philipp Arndt on 22.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Intents +import CocoaMQTT + +class PublishHandler: INExtension, PublishMQTTMessageIntentHandling { + func transformQos(qos: Qos) -> Int { + switch qos { + case .qos1: + return 1 + case .qos2: + return 2 + default: + return 0 + } + } + + func handle(intent: PublishMQTTMessageIntent, completion: @escaping (PublishMQTTMessageIntentResponse) -> Void) { + + if let brokerName = intent.broker, + let topic = intent.topic, + let message = intent.message, + let retain = intent.retainMessage as? Bool { + + if let broker = firstBroker(by: brokerName) { + do { + let result = try MQTTClientSync.publish( + host: broker, + topic: topic.trimmingCharacters(in: [" "]), + message: message, + retain: retain, + qos: transformQos(qos: intent.qos) + ) + + completion(response(from: result ? nil : "Publish failed")) + } + catch { + NSLog("Error during publish \(error)") + completion(response(from: "\(error)")) + } + } + else { + completion(response(from: "Error finding broker")) + } + } + } + + func response(from error: String?) -> PublishMQTTMessageIntentResponse { + let response = PublishMQTTMessageIntentResponse( + code: error == nil ? .success : .failure, + userActivity: nil) + + response.error = error + + return response + } + + func provideBrokerOptionsCollection(for intent: PublishMQTTMessageIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + completion(INObjectCollection(items: loadBrokers()), nil) + } + + func resolveTopic(for intent: PublishMQTTMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + resolve(string: intent.topic, with: completion) + } + + func resolveMessage(for intent: PublishMQTTMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + resolve(string: intent.message, with: completion) + } + + func resolveBroker(for intent: PublishMQTTMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + resolve(string: intent.broker, with: completion) + } + + func resolveRetain(for intent: PublishMQTTMessageIntent, with completion: @escaping (INBooleanResolutionResult) -> Void) { + resolve(boolean: intent.retainMessage, with: completion) + } +} + +extension PublishHandler { + func resolve(string value: String?, with completion: @escaping (INStringResolutionResult) -> Void) { + if let val = value { + completion(INStringResolutionResult.success(with: val)) + } else { + completion(INStringResolutionResult.needsValue()) + } + } + + func resolve(boolean value: NSNumber?, with completion: @escaping (INBooleanResolutionResult) -> Void) { + if let val = value as? Bool { + completion(INBooleanResolutionResult.success(with: val)) + } else { + completion(INBooleanResolutionResult.needsValue()) + } + } +} + diff --git a/src/MQTTAnalyzerIntent/ReceiveHandler.swift b/src/MQTTAnalyzerIntent/ReceiveHandler.swift new file mode 100644 index 00000000..15e5000d --- /dev/null +++ b/src/MQTTAnalyzerIntent/ReceiveHandler.swift @@ -0,0 +1,56 @@ +// +// ReceiveHandler.swift +// MQTTAnalyzerIntent +// +// Created by Philipp Arndt on 22.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import Intents +import CocoaMQTT + +class ReceiveHandler: INExtension, ReceiveMQTTMessageIntentHandling { + + func provideBrokerOptionsCollection(for intent: ReceiveMQTTMessageIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + completion(INObjectCollection(items: loadBrokers()), nil) + } + + func handle(intent: ReceiveMQTTMessageIntent, completion: @escaping (ReceiveMQTTMessageIntentResponse) -> Void) { + + if let brokerName = intent.broker, + let topic = intent.topic, + let timeout = intent.timeoutSeconds { + + if let broker = firstBroker(by: brokerName) { + do { + let result = try MQTTClientSync.receiveFirst( + host: broker, + topic: topic.trimmingCharacters(in: [" "]), + timeout: Int(truncating: timeout) + ) + + completion(response(from: result)) + } + catch { + NSLog("Error during receive \(error)") + completion(ReceiveMQTTMessageIntentResponse( + code: .failure, + userActivity: nil)) + } + } + else { + completion(ReceiveMQTTMessageIntentResponse( + code: .failure, + userActivity: nil)) + } + } + } + + func response(from message: String?) -> ReceiveMQTTMessageIntentResponse { + let response = ReceiveMQTTMessageIntentResponse( + code: message != nil ? .success : .failure, + userActivity: nil) + response.message = message + return response + } +} diff --git a/src/MQTTAnalyzerIntentUI/Base.lproj/MainInterface.storyboard b/src/MQTTAnalyzerIntentUI/Base.lproj/MainInterface.storyboard new file mode 100644 index 00000000..1aea3b30 --- /dev/null +++ b/src/MQTTAnalyzerIntentUI/Base.lproj/MainInterface.storyboard @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MQTTAnalyzerIntentUI/Info.plist b/src/MQTTAnalyzerIntentUI/Info.plist new file mode 100644 index 00000000..493a1318 --- /dev/null +++ b/src/MQTTAnalyzerIntentUI/Info.plist @@ -0,0 +1,21 @@ + + + + + NSExtension + + NSExtensionAttributes + + IntentsSupported + + PublishMQTTMessageIntent + ReceiveMQTTMessageIntent + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.intents-ui-service + + + diff --git a/src/MQTTAnalyzerIntentUI/IntentViewController.swift b/src/MQTTAnalyzerIntentUI/IntentViewController.swift new file mode 100644 index 00000000..d54adf04 --- /dev/null +++ b/src/MQTTAnalyzerIntentUI/IntentViewController.swift @@ -0,0 +1,39 @@ +// +// IntentViewController.swift +// MQTTAnalyzerIntentUI +// +// Created by Philipp Arndt on 20.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import IntentsUI + +class IntentViewController: UIViewController, INUIHostedViewControlling { + @IBOutlet weak var contentLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - INUIHostedViewControlling + + // Prepare your view controller for the interaction to handle. + func configureView(for parameters: Set, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set, CGSize) -> Void) { + + guard let intent = interaction.intent as? PublishMQTTMessageIntent else { + completion(false, Set(), .zero) + return + } + + if let broker = intent.broker { + self.contentLabel.text = "Message published to broker \(broker)" + } + + completion(true, parameters, self.desiredSize) + } + + var desiredSize: CGSize { + return CGSize.init(width: 10, height: 100) + } + +} diff --git a/src/MQTTAnalyzerIntentUI/MQTTAnalyzerIntentUI.entitlements b/src/MQTTAnalyzerIntentUI/MQTTAnalyzerIntentUI.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/src/MQTTAnalyzerIntentUI/MQTTAnalyzerIntentUI.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/src/MQTTAnalyzerTests/HostModelPersistenceTests.swift b/src/MQTTAnalyzerTests/HostModelPersistenceTests.swift index c4a47735..1897a987 100644 --- a/src/MQTTAnalyzerTests/HostModelPersistenceTests.swift +++ b/src/MQTTAnalyzerTests/HostModelPersistenceTests.swift @@ -22,7 +22,7 @@ class HostModelPersistenceTests: XCTestCase, InitHost { let host = Host(id: "some-id") - RealmPersistenceTransformer.copy(from: host, to: setting) + PersistenceTransformer.copy(from: host, to: setting) // ID is a primary key and must not be overwritten XCTAssertEqual(setting.id, id) @@ -65,7 +65,7 @@ class HostModelPersistenceTests: XCTestCase, InitHost { setting.limitTopic = 4 setting.limitMessagesBatch = 5 - let transformed = RealmPersistenceTransformer.transform(setting) + let transformed = PersistenceTransformer.transform(from: setting) XCTAssertEqual("alias", transformed.alias) XCTAssertEqual("hostname", transformed.hostname) XCTAssertEqual(1, transformed.port) @@ -87,48 +87,48 @@ class HostModelPersistenceTests: XCTestCase, InitHost { func testTransformFromPersistenceModelAuthType() { let setting = HostSetting() setting.authType = AuthenticationType.none - let transformed1 = RealmPersistenceTransformer.transform(setting) + let transformed1 = PersistenceTransformer.transform(from: setting) XCTAssertEqual(HostAuthenticationType.none, transformed1.auth) setting.authType = AuthenticationType.certificate - let transformed2 = RealmPersistenceTransformer.transform(setting) + let transformed2 = PersistenceTransformer.transform(from: setting) XCTAssertEqual(HostAuthenticationType.certificate, transformed2.auth) setting.authType = AuthenticationType.usernamePassword - let transformed3 = RealmPersistenceTransformer.transform(setting) + let transformed3 = PersistenceTransformer.transform(from: setting) XCTAssertEqual(HostAuthenticationType.usernamePassword, transformed3.auth) } func testTransformFromPersistenceModelClientImplType() { let setting = HostSetting() setting.protocolVersion = HostProtocolVersionType.mqtt3 - let transformed1 = RealmPersistenceTransformer.transform(setting) + let transformed1 = PersistenceTransformer.transform(from: setting) XCTAssertEqual(HostProtocolVersion.mqtt3, transformed1.protocolVersion) setting.protocolVersion = HostProtocolVersionType.mqtt5 - let transformed2 = RealmPersistenceTransformer.transform(setting) + let transformed2 = PersistenceTransformer.transform(from: setting) XCTAssertEqual(HostProtocolVersion.mqtt5, transformed2.protocolVersion) } func testTransformFromPersistenceModelSSL() { let setting = HostSetting() setting.ssl = false - let transformed1 = RealmPersistenceTransformer.transform(setting) + let transformed1 = PersistenceTransformer.transform(from: setting) XCTAssertFalse(transformed1.ssl) setting.ssl = true - let transformed2 = RealmPersistenceTransformer.transform(setting) + let transformed2 = PersistenceTransformer.transform(from: setting) XCTAssertTrue(transformed2.ssl) } func testTransformFromPersistenceModelSSLUntrusted() { let setting = HostSetting() setting.untrustedSSL = false - let transformed1 = RealmPersistenceTransformer.transform(setting) + let transformed1 = PersistenceTransformer.transform(from: setting) XCTAssertFalse(transformed1.untrustedSSL) setting.untrustedSSL = true - let transformed2 = RealmPersistenceTransformer.transform(setting) + let transformed2 = PersistenceTransformer.transform(from: setting) XCTAssertTrue(transformed2.untrustedSSL) } @@ -156,7 +156,7 @@ class HostModelPersistenceTests: XCTestCase, InitHost { host.limitTopic = 4 host.limitMessagesBatch = 5 - let transformed = RealmPersistenceTransformer.transform(host) + let transformed = PersistenceTransformer.transformToRealm(from: host) XCTAssertEqual("alias", transformed.alias) XCTAssertEqual("hostname", transformed.hostname) XCTAssertEqual(1, transformed.port) @@ -188,48 +188,48 @@ class HostModelPersistenceTests: XCTestCase, InitHost { func testTransformToPersistenceModelAuthType() { let host = Host() host.auth = .none - let transformed1 = RealmPersistenceTransformer.transform(host) - XCTAssertEqual(AuthenticationType.none, transformed1.authType) + XCTAssertEqual(AuthenticationType.none, PersistenceTransformer.transformToRealm(from: host).authType) + XCTAssertEqual(Int(AuthenticationType.none), PersistenceTransformer.transformToSQLite(from: host).authType) host.auth = .certificate - let transformed2 = RealmPersistenceTransformer.transform(host) - XCTAssertEqual(AuthenticationType.certificate, transformed2.authType) + XCTAssertEqual(AuthenticationType.certificate, PersistenceTransformer.transformToRealm(from: host).authType) + XCTAssertEqual(Int(AuthenticationType.certificate), PersistenceTransformer.transformToSQLite(from: host).authType) host.auth = .usernamePassword - let transformed3 = RealmPersistenceTransformer.transform(host) - XCTAssertEqual(AuthenticationType.usernamePassword, transformed3.authType) + XCTAssertEqual(AuthenticationType.usernamePassword, PersistenceTransformer.transformToRealm(from: host).authType) + XCTAssertEqual(Int(AuthenticationType.usernamePassword), PersistenceTransformer.transformToSQLite(from: host).authType) } func testTransformToPersistenceModelClientImplType() { let host = Host() host.protocolVersion = .mqtt3 - let transformed1 = RealmPersistenceTransformer.transform(host) - XCTAssertEqual(HostProtocolVersionType.mqtt3, transformed1.protocolVersion) + XCTAssertEqual(HostProtocolVersionType.mqtt3, PersistenceTransformer.transformToRealm(from: host).protocolVersion) + XCTAssertEqual(Int(HostProtocolVersionType.mqtt3), PersistenceTransformer.transformToSQLite(from: host).protocolVersion) host.protocolVersion = .mqtt5 - let transformed2 = RealmPersistenceTransformer.transform(host) - XCTAssertEqual(HostProtocolVersionType.mqtt5, transformed2.protocolVersion) + XCTAssertEqual(HostProtocolVersionType.mqtt5, PersistenceTransformer.transformToRealm(from: host).protocolVersion) + XCTAssertEqual(Int(HostProtocolVersionType.mqtt5), PersistenceTransformer.transformToSQLite(from: host).protocolVersion) } func testTransformToPersistenceModelSSL() { let host = Host() host.ssl = false - let transformed1 = RealmPersistenceTransformer.transform(host) - XCTAssertFalse(transformed1.ssl) + XCTAssertFalse(PersistenceTransformer.transformToRealm(from: host).ssl) + XCTAssertFalse(PersistenceTransformer.transformToSQLite(from: host).ssl) host.ssl = true - let transformed2 = RealmPersistenceTransformer.transform(host) - XCTAssertTrue(transformed2.ssl) + XCTAssertTrue(PersistenceTransformer.transformToRealm(from: host).ssl) + XCTAssertTrue(PersistenceTransformer.transformToSQLite(from: host).ssl) } func testTransformToPersistenceModelSSLUntrusted() { let host = Host() host.untrustedSSL = false - let transformed1 = RealmPersistenceTransformer.transform(host) - XCTAssertFalse(transformed1.untrustedSSL) + XCTAssertFalse(PersistenceTransformer.transformToRealm(from: host).untrustedSSL) + XCTAssertFalse(PersistenceTransformer.transformToSQLite(from: host).untrustedSSL) host.untrustedSSL = true - let transformed2 = RealmPersistenceTransformer.transform(host) - XCTAssertTrue(transformed2.untrustedSSL) + XCTAssertTrue(PersistenceTransformer.transformToRealm(from: host).untrustedSSL) + XCTAssertTrue(PersistenceTransformer.transformToSQLite(from: host).untrustedSSL) } } diff --git a/src/MQTTAnalyzerTests/Info.plist b/src/MQTTAnalyzerTests/Info.plist index c6ffebe4..fb315978 100644 --- a/src/MQTTAnalyzerTests/Info.plist +++ b/src/MQTTAnalyzerTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 141 + 146 diff --git a/src/MQTTAnalyzerTests/PublishSyncTests.swift b/src/MQTTAnalyzerTests/PublishSyncTests.swift new file mode 100644 index 00000000..11621134 --- /dev/null +++ b/src/MQTTAnalyzerTests/PublishSyncTests.swift @@ -0,0 +1,61 @@ +// +// SensSnycTest.swift +// MQTTAnalyzerUITests +// +// Created by Philipp Arndt on 20.04.22. +// Copyright © 2022 Philipp Arndt. All rights reserved. +// + +import XCTest +import CocoaMQTT + +private let clientID = "ClientForUnitTesting-" + +private let host = "localhost" +private let port: UInt16 = 1883 +private let delegate_queue_key = DispatchSpecificKey() +private let delegate_queue_val = "_custom_delegate_queue_" + +class PublishSyncTests: XCTestCase { + var deleQueue: DispatchQueue! + + override func setUp() { + deleQueue = DispatchQueue(label: "cttest") + deleQueue.setSpecific(key: delegate_queue_key, value: delegate_queue_val) + super.setUp() + } + + func testConnect() { +// let caller = SyncMQTTDelegate() +// let mqtt = CocoaMQTT(clientID: clientID, host: host, port: port) +// mqtt.delegateQueue = deleQueue +// mqtt.delegate = caller +// mqtt.logLevel = .debug +// mqtt.autoReconnect = false +// +// _ = mqtt.connect() +// +// XCTAssertTrue(MQTTAnalyzerUITests.wait(for: { caller.isConnected })) +// +// let topics = ["t/0", "t/1", "t/2"] +// +// mqtt.publish(topics[0], withString: "0", qos: .qos0, retained: false) +// mqtt.publish(topics[1], withString: "1", qos: .qos1, retained: false) +// mqtt.publish(topics[2], withString: "2", qos: .qos2, retained: false) +// +// XCTAssertTrue(MQTTAnalyzerUITests.wait(for: { +// if caller.sents.count >= 3 { +// return true +// } +// return false +// })) +// +// mqtt.disconnect() +// +// XCTAssertTrue(MQTTAnalyzerUITests.wait(for: { +// caller.isConnected == false +// })) +// +// XCTAssertEqual(mqtt.connState, .disconnected) + } +} diff --git a/src/MQTTAnalyzerUITests/Info.plist b/src/MQTTAnalyzerUITests/Info.plist index c6ffebe4..fb315978 100644 --- a/src/MQTTAnalyzerUITests/Info.plist +++ b/src/MQTTAnalyzerUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 141 + 146 diff --git a/src/Podfile b/src/Podfile index 2a8bf3d4..be38c127 100644 --- a/src/Podfile +++ b/src/Podfile @@ -55,4 +55,9 @@ target 'MQTTAnalyzer' do inherit! :search_paths shared_pods end + + target 'MQTTAnalyzerIntent' do + inherit! :search_paths + shared_pods + end end diff --git a/src/Podfile.lock b/src/Podfile.lock index 959ab0f9..eee09921 100644 --- a/src/Podfile.lock +++ b/src/Podfile.lock @@ -76,6 +76,6 @@ SPEC CHECKSUMS: SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e -PODFILE CHECKSUM: 6f9396ab7323e4e6381886ede5adde1db5edecef +PODFILE CHECKSUM: 89980e14a2e9231b4957277214a26b88554c9944 COCOAPODS: 1.11.3