~/Projects/mqtt-ios
git clone https://code.lsong.org/mqtt-ios
Commit
- Commit
- d65b475bc1044fbd2eaafbf77714b7e12792cd08
- Author
- Philipp Arndt <[email protected]>
- Date
- 2020-05-22 13:19:10 +0200 +0200
- Diffstat
src/MQTTAnalyzer.xcodeproj/project.pbxproj | 64 src/MQTTAnalyzer/AppDelegate.swift | 14 src/MQTTAnalyzer/Info.plist | 12 src/MQTTAnalyzer/MQTTAnalyzer.entitlements | 6 src/MQTTAnalyzer/icloud/CertificateFile.swift | 19 src/MQTTAnalyzer/icloud/CloudDataManager.swift | 67 src/MQTTAnalyzer/icloud/FileLister.swift | 59 src/MQTTAnalyzer/model/HostModel.swift | 5 src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift | 4 src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift | 59 | 5 src/MQTTAnalyzer/views/host/form/auth/CertificateFilePickerView.swift | 84 src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateFilePickerView.swift | 49 src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateLocationPicker.swift | 31 src/MQTTAnalyzer/views/host/form/auth/certificates/FileItemView.swift | 31 src/MQTTAnalyzer/views/host/form/auth/certificates/FileListView.swift | 37 src/MQTTAnalyzer/views/host/form/auth/certificates/NoFilesHelpView.swift | 33 src/MQTTAnalyzer/views/host/form/auth/certificates/PKCS12HelpView.swift | 27
add support fo iCloud documents (incomplete)
diff --git a/src/MQTTAnalyzer/AppDelegate.swift b/src/MQTTAnalyzer/AppDelegate.swift index 9232e57c65d77759bafa1106d6e4a43c3a1892e1..f3ea0f4dd3f5f346101a9c07e5c59c4a62f61a99 100644 --- a/src/MQTTAnalyzer/AppDelegate.swift +++ b/src/MQTTAnalyzer/AppDelegate.swift @@ -18,18 +18,28 @@ var syncEngine: SyncEngine? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + DataMigration.initMigration(afterMigration: self.afterMigration) + syncEngine = SyncEngine(objects: [ SyncObject<HostSetting>() ], databaseScope: .private) + syncEngine?.pull() // AppDelegate.swift + +// AppDelegate.swift // -// Created by Philipp Arndt on 2019-06-30. // AppDelegate.swift -// + + CloudDataManager.sharedInstance.initDocumentsDirectory() +// AppDelegate.swift // Override point for customization after application launch. return true + } + + func afterMigration() { + syncEngine?.pushAll() } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { diff --git a/src/MQTTAnalyzer/Info.plist b/src/MQTTAnalyzer/Info.plist index fd9a9b939ee3a8593764d4c0a29d6f581f0a6a5d..cc0c790dd4e6f6b3dfde542422cb1a30028965ac 100644 --- a/src/MQTTAnalyzer/Info.plist +++ b/src/MQTTAnalyzer/Info.plist @@ -22,6 +22,18 @@ITSAppUsesNonExemptEncryption <false/> <key>LSRequiresIPhoneOS</key> <true/> + <key>NSUbiquitousContainers</key> + <dict> + <key>iCloud.de.rnd7.MQTTAnalyzer</key> + <dict> + <key>NSUbiquitousContainerIsDocumentScopePublic</key> + <true/> + <key>NSUbiquitousContainerName</key> + <string>MQTTAnalyzer</string> + <key>NSUbiquitousContainerSupportedFolderLevels</key> + <string>One</string> + </dict> + </dict> <key>UIApplicationSceneManifest</key> <dict> <key>UIApplicationSupportsMultipleScenes</key> diff --git a/src/MQTTAnalyzer/MQTTAnalyzer.entitlements b/src/MQTTAnalyzer/MQTTAnalyzer.entitlements index ed4b5583ee1461a0ea5be205f785bda5eaff35a0..a510f123bc6395c7c48058d5a9e4e8ec7a0db167 100644 --- a/src/MQTTAnalyzer/MQTTAnalyzer.entitlements +++ b/src/MQTTAnalyzer/MQTTAnalyzer.entitlements @@ -12,6 +12,12 @@com.apple.developer.icloud-services <array> <string>CloudKit</string> <?xml version="1.0" encoding="UTF-8"?> + <key>com.apple.developer.icloud-container-identifiers</key> + </array> + <key>com.apple.developer.ubiquity-container-identifiers</key> + <array> + <string>iCloud.de.rnd7.MQTTAnalyzer</string> +<?xml version="1.0" encoding="UTF-8"?> <key>com.apple.developer.ubiquity-kvstore-identifier</key> <string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string> </dict> diff --git a/src/MQTTAnalyzer/icloud/CertificateFile.swift b/src/MQTTAnalyzer/icloud/CertificateFile.swift new file mode 100644 index 0000000000000000000000000000000000000000..b63fffd2838d21dc60d3684387192fecf62a9304 --- /dev/null +++ b/src/MQTTAnalyzer/icloud/CertificateFile.swift @@ -0,0 +1,19 @@ +// +// File.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation + +struct CertificateFile: Identifiable, Comparable { + let name: String + let id = UUID.init() + let location: CertificateLocation + + static func < (lhs: CertificateFile, rhs: CertificateFile) -> Bool { + return lhs.name < rhs.name + } +} diff --git a/src/MQTTAnalyzer/icloud/CloudDataManager.swift b/src/MQTTAnalyzer/icloud/CloudDataManager.swift new file mode 100644 index 0000000000000000000000000000000000000000..2cb8093451da2bd3c7a37169b96f29b48ac07af2 --- /dev/null +++ b/src/MQTTAnalyzer/icloud/CloudDataManager.swift @@ -0,0 +1,67 @@ +// +// CloudDataManager.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-17. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation +class CloudDataManager { + + static let sharedInstance = CloudDataManager() // Singleton + + struct DocumentsDirectory { + static let localDocumentsURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: .userDomainMask).last! + static let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") + } + + func initDocumentsDirectory() { + if !isCloudEnabled() { + return + } + + if let url = DocumentsDirectory.iCloudDocumentsURL { + if !FileManager.default.fileExists(atPath: url.path, isDirectory: nil) { + do { + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + catch { + print(error.localizedDescription) + } + } + + do { + try FileManager.default.startDownloadingUbiquitousItem(at: url) + } + catch { + print(error.localizedDescription) + } + } + } + + // Return the Document directory (Cloud OR Local) + // To do in a background thread + func getDocumentDiretoryURL() -> URL { + if isCloudEnabled() { + return DocumentsDirectory.iCloudDocumentsURL! + } else { + return DocumentsDirectory.localDocumentsURL + } + } + + // Return true if iCloud is enabled + func isCloudEnabled() -> Bool { + if DocumentsDirectory.iCloudDocumentsURL != nil { return true } + else { return false } + } + + func getiCloudDocumentDiretoryURL() -> URL? { + return DocumentsDirectory.iCloudDocumentsURL + } + + + func getLocalDocumentDiretoryURL() -> URL { + return DocumentsDirectory.localDocumentsURL + } +} diff --git a/src/MQTTAnalyzer/icloud/FileLister.swift b/src/MQTTAnalyzer/icloud/FileLister.swift new file mode 100644 index 0000000000000000000000000000000000000000..f3145c89c6e268094366ca5257c756e3ff8fc51c --- /dev/null +++ b/src/MQTTAnalyzer/icloud/FileLister.swift @@ -0,0 +1,59 @@ +// +// FileLister.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation +import Combine + +class FileLister: ObservableObject { + @Published var files: [CertificateFile] = FileLister.listFiles(on: getDefaultLocation()) + + @Published var certificateLocation = getDefaultLocation() { + didSet { + refresh() + } + } + + func refresh() { + files = FileLister.listFiles(on: certificateLocation) + } + + class func getDefaultLocation() -> CertificateLocation { + return CloudDataManager.sharedInstance.isCloudEnabled() ? CertificateLocation.cloud : .local + } + + class func getUrl(on location: CertificateLocation) -> URL { + if location == .cloud { + if let url = CloudDataManager.sharedInstance.getiCloudDocumentDiretoryURL() { + return url + } + } + + return CloudDataManager.sharedInstance.getLocalDocumentDiretoryURL() + } + + class func listFiles(on location: CertificateLocation) -> [CertificateFile] { + do { + let url = FileLister.getUrl(on: location) + + CloudDataManager.sharedInstance.initDocumentsDirectory() + + let directoryContents = try FileManager.default + .contentsOfDirectory(at: url, includingPropertiesForKeys: nil) + + let files = directoryContents + .map { $0.lastPathComponent } + .filter { $0.lowercased().range(of: #".*\.(p12|pfx|crt|key)$"#, options: .regularExpression) != nil} + .map { CertificateFile(name: $0, location: location) } + .sorted() + + return files + } catch { + return [CertificateFile(name: error.localizedDescription, location: location)] + } + } +} diff --git a/src/MQTTAnalyzer/model/HostModel.swift b/src/MQTTAnalyzer/model/HostModel.swift index 16eaa03f3c9b9b9423f3c74a8baad9073740a552..edfc80ee1100c29ec7a96c027d5d2d49ba1c89f7 100644 --- a/src/MQTTAnalyzer/model/HostModel.swift +++ b/src/MQTTAnalyzer/model/HostModel.swift @@ -9,6 +9,11 @@ import Foundation import SwiftUI +enum CertificateLocation { + case cloud + case local +} + enum HostAuthenticationType { case none case usernamePassword diff --git a/src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift b/src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift index 5957d639bc3c81a3f3b0b75b58452004e740baca..1ce62b3176bc623daf60ceea2e3a9183da60327b 100644 --- a/src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift +++ b/src/MQTTAnalyzer/model/persistence/migration/DataMigration.swift @@ -12,7 +12,7 @@ import IceCream class DataMigration { - class func initMigration(syncEngine: SyncEngine?) { + class func initMigration(afterMigration: @escaping () -> Void) { let configuration = Realm.Configuration( schemaVersion: 20, migrationBlock: { migration, oldSchemaVersion in @@ -23,8 +23,8 @@ DataMigrationMultipleTopics.migrate(oldSchemaVersion, migration) DataMigrationEmptyTopic.migrate(oldSchemaVersion, migration) DispatchQueue.global(qos: .background).async { -// DataMigration.swift // MQTTAnalyzer +import Foundation } // Example on how to rename properties: diff --git a/src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift b/src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift index f1a2b042598cf443d926fdecff0b4b7cd3386d1c..d93e1a9e9b5a8951930f564fe890beffe286d667 100644 --- a/src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift +++ b/src/MQTTAnalyzer/mqtt/cocoamqtt/CocoaMQTTCertificateFiles.swift @@ -27,61 +27,60 @@ case noIdentify = "No identity" } private func getClientCertFromP12File(certName: String, certPassword: String) throws -> CFArray? { - if let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first { -// CertificateFiles.swift // Created by Philipp Arndt on 2020-04-14. - - guard let p12Data = NSData(contentsOfFile: filePath) else { - throw CertificateError.errorOpenFile -// CertificateFiles.swift import CocoaMQTT - -// CertificateFiles.swift +// Created by Philipp Arndt on 2020-04-14. // Create P12 File by using: +// // MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. // - + } +// // MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. // CertificateFiles.swift -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. // MQTTAnalyzer -// CertificateFiles.swift // Copyright © 2020 Philipp Arndt. All rights reserved. +// Created by Philipp Arndt on 2020-04-14. +// // MQTTAnalyzer -// Created by Philipp Arndt on 2020-04-14. -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. // Copyright © 2020 Philipp Arndt. All rights reserved. -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. +// // MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. import Foundation -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. import CocoaMQTT -// MQTTAnalyzer +// Copyright © 2020 Philipp Arndt. All rights reserved. // Create P12 File by using: + } else { // CertificateFiles.swift -import CocoaMQTT +import Foundation // CertificateFiles.swift -// Copyright © 2020 Philipp Arndt. All rights reserved. +import CocoaMQTT // Created by Philipp Arndt on 2020-04-14. + -// Created by Philipp Arndt on 2020-04-14. // +// MQTTAnalyzer - } + guard let theArray = items, CFArrayGetCount(theArray) > 0 else { + // CertificateFiles.swift -// Copyright © 2020 Philipp Arndt. All rights reserved. // Created by Philipp Arndt on 2020-04-14. -// CertificateFiles.swift + -// Created by Philipp Arndt on 2020-04-14. +// // MQTTAnalyzer - throw CertificateError.noIdentify - } - + let dictionary = (theArray as NSArray).object(at: 0) + // Created by Philipp Arndt on 2020-04-14. -// Copyright © 2020 Philipp Arndt. All rights reserved. -// CertificateFiles.swift + // Copyright © 2020 Philipp Arndt. All rights reserved. } - return nil + return [identity] as CFArray } diff --git a/src/MQTTAnalyzer/views/host/form/auth/CertificateAuthenticationView.swift b/src/MQTTAnalyzer/views/host/form/auth/CertificateAuthenticationView.swift deleted file mode 100644 index a95d191dc2526282908c773fa301d8fe1a6ea0a0..0000000000000000000000000000000000000000 --- a/src/MQTTAnalyzer/views/host/form/auth/CertificateAuthenticationView.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// AuthPicker.swift -// MQTTAnalyzer -// -// Created by Philipp Arndt on 2020-02-25. -// Copyright © 2020 Philipp Arndt. All rights reserved. -// - -import Foundation - -import SwiftUI - -struct CertificateAuthenticationView: View { - - @Binding var host: HostFormModel - @Binding var clientImpl: HostClientImplType - - @State var serverCA: String = "" - @State var clientCertificate: String = "" - @State var clientKey: String = "" - @State var clientKeyPassword: String = "" - - var body: some View { - Group { - List { - if clientImpl == .cocoamqtt { - CertificateFileItemView(name: "Client PKCS12", filename: $host.certClient) - } - else { - CertificateFileItemView(name: "Server CA", filename: $host.certServerCA) - CertificateFileItemView(name: "Client Certificate", filename: $host.certClient) - CertificateFileItemView(name: "Client Key", filename: $host.certClientKey) - } - } - - HStack { - Text("Password") - .font(.headline) - - Spacer() - - SecureField(clientImpl == .cocoamqtt ? "password" : "optional key file password", text: $host.certClientKeyPassword) - .disableAutocorrection(true) - .autocapitalization(.none) - .multilineTextAlignment(.trailing) - .font(.body) - } - - InfoBox(text: "Certificate files are not synced. " - + "Copy them to all of your devices using Finder / iTunes.") - } - } -} - -struct CertificateFileItemView: View { - let name: String - @Binding var filename: String - - var body: some View { - NavigationLink(destination: CertificateFilePickerView(type: name, fileName: $filename)) { - HStack { - Text(name) - .font(.headline) - - Spacer() - - Group { - if filename.isBlank { - Text("select") - .foregroundColor(.gray) - } - else { - Text(filename) - } - }.font(.body) - } - } - } -} diff --git a/src/MQTTAnalyzer/views/host/form/auth/CertificateFilePickerView.swift b/src/MQTTAnalyzer/views/host/form/auth/CertificateFilePickerView.swift deleted file mode 100644 index bd317736a273260b689e446fed36d016c3ee8d94..0000000000000000000000000000000000000000 --- a/src/MQTTAnalyzer/views/host/form/auth/CertificateFilePickerView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// AuthPicker.swift -// MQTTAnalyzer -// -// Created by Philipp Arndt on 2020-02-25. -// Copyright © 2020 Philipp Arndt. All rights reserved. -// - -import Foundation - -import SwiftUI - -struct FileItemView: View { - let fileName: String - @Binding var selection: String - - var body: some View { - HStack { - Image(systemName: self.selection == fileName ? "largecircle.fill.circle" : "circle") - .foregroundColor(.blue) - - Image(systemName: "doc.text.fill") - .foregroundColor(.secondary) - - Text(fileName) - - Spacer() - } - .onTapGesture { - self.selection = self.fileName - } - } -} - -struct File: Identifiable, Comparable { - let name: String - let id = UUID.init() - - static func < (lhs: File, rhs: File) -> Bool { - return lhs.name < rhs.name - } -} - -struct CertificateFilePickerView: View { - - let type: String - - @Binding var fileName: String - - var body: some View { - Group { - Spacer() - - InfoBox(text: "Add new *.p12 / *.pfx or *.crt and *.key files with Finder or iTunes.\n\n" - + "Create p12 file using:\n`openssl pkcs12 -export -in user.crt -inkey user.key -out user.p12`") - .padding(.horizontal) - - List { - ForEach(listFiles()) { file in - FileItemView(fileName: file.name, selection: self.$fileName).font(.body) - } - } - } - .navigationBarTitle("Select \(type)") - } - - func listFiles() -> [File] { - let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - - do { - let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil) - - let files = directoryContents - .map { $0.lastPathComponent } - .filter { $0.lowercased().range(of: #".*\.(p12|pfx|crt|key)"#, options: .regularExpression) != nil} - .map { File(name: $0) } - .sorted() - - return files - } catch { - return [File(name: error.localizedDescription)] - } - } -} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateAuthenticationView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateAuthenticationView.swift new file mode 100644 index 0000000000000000000000000000000000000000..3e8b8cbc7cd1dda6c13594b99248b862bd337031 --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateAuthenticationView.swift @@ -0,0 +1,76 @@ +// +// AuthPicker.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-02-25. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation + +import SwiftUI + +struct CertificateAuthenticationView: View { + + @Binding var host: HostFormModel + @Binding var clientImpl: HostClientImplType + + @State var serverCA: String = "" + @State var clientCertificate: String = "" + @State var clientKey: String = "" + @State var clientKeyPassword: String = "" + + var body: some View { + Group { + List { + if clientImpl == .cocoamqtt { + CertificateFileItemView(name: "Client PKCS#12", filename: $host.certClient) + } + else { + CertificateFileItemView(name: "Server CA", filename: $host.certServerCA) + CertificateFileItemView(name: "Client Certificate", filename: $host.certClient) + CertificateFileItemView(name: "Client Key", filename: $host.certClientKey) + } + } + + HStack { + Text("Password") + .font(.headline) + + Spacer() + + SecureField(clientImpl == .cocoamqtt ? "password" : "optional key file password", text: $host.certClientKeyPassword) + .disableAutocorrection(true) + .autocapitalization(.none) + .multilineTextAlignment(.trailing) + .font(.body) + } + } + } +} + +struct CertificateFileItemView: View { + let name: String + @Binding var filename: String + + var body: some View { + NavigationLink(destination: CertificateFilePickerView(type: name, fileName: $filename)) { + HStack { + Text(name) + .font(.headline) + + Spacer() + + Group { + if filename.isBlank { + Text("select") + .foregroundColor(.gray) + } + else { + Text(filename) + } + }.font(.body) + } + } + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateFilePickerView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateFilePickerView.swift new file mode 100644 index 0000000000000000000000000000000000000000..c9cbbe4f09bd5e0193645e2d38603baba374e45c --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateFilePickerView.swift @@ -0,0 +1,49 @@ +// +// AuthPicker.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-02-25. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation + +import SwiftUI +import Combine + +struct CertificateFilePickerView: View { + let type: String + + @Binding var fileName: String + @ObservedObject var fileLister = FileLister() + + var body: some View { + Group { + List { + if CloudDataManager.sharedInstance.isCloudEnabled() { + CertificateLocationSectionView(type: Binding( + get: { + return self.fileLister.certificateLocation + }, + set: { (newValue) in + return self.fileLister.certificateLocation = newValue + })) + } + + FileListView(refreshHandler: fileLister.refresh, + files: fileLister.files, + fileName: $fileName, + certificateLocation: Binding( + get: { + return self.fileLister.certificateLocation + }, + set: { (newValue) in + return self.fileLister.certificateLocation = newValue + })) + + PKCS12HelpView() + } + } + .navigationBarTitle("Select \(type)") + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateLocationPicker.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateLocationPicker.swift new file mode 100644 index 0000000000000000000000000000000000000000..b7fe9003149ba391f90ec5e36f92acfe76079039 --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/CertificateLocationPicker.swift @@ -0,0 +1,31 @@ +// +// AuthPicker.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-02-25. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation +import SwiftUI + +struct CertificateLocationSectionView: View { + @Binding var type: CertificateLocation + + var body: some View { + Section(header: Text("Location")) { + CertificateLocationPicker(type: $type) + } + } +} + +struct CertificateLocationPicker: View { + @Binding var type: CertificateLocation + + var body: some View { + Picker(selection: $type, label: Text("Location")) { + Text("iCloud").tag(CertificateLocation.cloud) + Text("Local").tag(CertificateLocation.local) + }.pickerStyle(SegmentedPickerStyle()) + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/FileItemView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/FileItemView.swift new file mode 100644 index 0000000000000000000000000000000000000000..bf799b5c1105a2e51666c481f5f98c5eb69e3734 --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/FileItemView.swift @@ -0,0 +1,31 @@ +// +// FileItemView.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import SwiftUI + +struct FileItemView: View { + let fileName: String + @Binding var selection: String + + var body: some View { + HStack { + Image(systemName: self.selection == fileName ? "largecircle.fill.circle" : "circle") + .foregroundColor(.blue) + + Image(systemName: "doc.text.fill") + .foregroundColor(.secondary) + + Text(fileName) + + Spacer() + } + .onTapGesture { + self.selection = self.fileName + } + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/FileListView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/FileListView.swift new file mode 100644 index 0000000000000000000000000000000000000000..a10b88af4ec97c06e06c13888ccae65cb8221191 --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/FileListView.swift @@ -0,0 +1,37 @@ +// +// FileListView.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import Foundation +import SwiftUI + +struct FileListView: View { + var refreshHandler: () -> Void + var files: [CertificateFile] + @Binding var fileName: String + @Binding var certificateLocation: CertificateLocation + + var body: some View { + Section(header: Text("Files")) { + if files.isEmpty { + NoFilesHelpView(certificateLocation: $certificateLocation) + .foregroundColor(.secondary) + } + + ForEach(files) { file in + FileItemView(fileName: file.name, selection: self.$fileName).font(.body) + } + + Button(action: refreshHandler) { + HStack { + Image(systemName: "arrow.2.circlepath") + Text("Refresh") + } + }.font(.body) + } + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/NoFilesHelpView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/NoFilesHelpView.swift new file mode 100644 index 0000000000000000000000000000000000000000..4a09fa624d567b6498fcc1639efa27d7d155939b --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/NoFilesHelpView.swift @@ -0,0 +1,33 @@ +// +// NoFilesHelpView.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import SwiftUI + +struct NoFilesHelpView: View { + @Binding var certificateLocation: CertificateLocation + + var body: some View { + VStack(alignment: .leading) { + Text("No certificate files here yet.") + .font(.headline) + + Spacer() + + if certificateLocation == .cloud { + Text("Add new *.p12 / *.pfx or *.crt and *.key files to iCloud drive (MQTTAnalyzer folder)") + Spacer() + Text("Use local files when you prefer them due to security reasons.") + } + else { + Text("Add new *.p12 / *.pfx or *.crt and *.key files with Finder or iTunes.") + Spacer() + Text("Use the iCloud drive when you like to sync your certificates between your devices.") + } + }.foregroundColor(.secondary) + } +} diff --git a/src/MQTTAnalyzer/views/host/form/auth/certificates/PKCS12HelpView.swift b/src/MQTTAnalyzer/views/host/form/auth/certificates/PKCS12HelpView.swift new file mode 100644 index 0000000000000000000000000000000000000000..ef6a0b03e396ce37f0d5679a59024ef7b0be3951 --- /dev/null +++ b/src/MQTTAnalyzer/views/host/form/auth/certificates/PKCS12HelpView.swift @@ -0,0 +1,27 @@ +// +// PKCS12HelpView.swift +// MQTTAnalyzer +// +// Created by Philipp Arndt on 2020-05-22. +// Copyright © 2020 Philipp Arndt. All rights reserved. +// + +import SwiftUI + +struct PKCS12HelpView: View { + var body: some View { + Section(header: Text("Use openssl to create PKCS#12 files:")) { + VStack(alignment: .leading) { + HStack { + Text("openssl pkcs12 -export -in user.crt -inkey user.key -out user.p12") + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(.secondary) + + Spacer() + } + } + .padding(5) + .background(Color.gray.opacity(0.2)) + } + } +} diff --git a/src/MQTTAnalyzer.xcodeproj/project.pbxproj b/src/MQTTAnalyzer.xcodeproj/project.pbxproj index fb9124fb571dc2bbc8b3f43412fef6861a8049c3..aefe449db8dd547548d39e50e38a1841f45eb657 100644 --- a/src/MQTTAnalyzer.xcodeproj/project.pbxproj +++ b/src/MQTTAnalyzer.xcodeproj/project.pbxproj @@ -17,6 +17,12 @@ 2209C86C23B720E7007C1D93 /* HostValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2209C86B23B720E7007C1D93 /* HostValidator.swift */; }; 221C571C2466847800C0DD02 /* QuestionBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221C571B2466847800C0DD02 /* QuestionBox.swift */; }; 221C571E2466C9CD00C0DD02 /* AWSIOTPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221C571D2466C9CD00C0DD02 /* AWSIOTPreset.swift */; }; 221C57202466CC2800C0DD02 /* AWSIOTPresetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221C571F2466CC2800C0DD02 /* AWSIOTPresetTests.swift */; }; + 223AF5D12477D575009810E6 /* FileLister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D02477D575009810E6 /* FileLister.swift */; }; + 223AF5D32477D5CA009810E6 /* FileListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D22477D5CA009810E6 /* FileListView.swift */; }; + 223AF5D52477D5F5009810E6 /* FileItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D42477D5F5009810E6 /* FileItemView.swift */; }; + 223AF5D72477D60A009810E6 /* PKCS12HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D62477D60A009810E6 /* PKCS12HelpView.swift */; }; + 223AF5D92477D620009810E6 /* NoFilesHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5D82477D620009810E6 /* NoFilesHelpView.swift */; }; + 223AF5DB2477D64B009810E6 /* CertificateFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223AF5DA2477D64B009810E6 /* CertificateFile.swift */; }; 223EF0062387084D002ADF3E /* HostSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EF0052387084D002ADF3E /* HostSetting.swift */; }; 223EF00823870AA5002ADF3E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 223EF00723870AA5002ADF3E /* CloudKit.framework */; }; 2253F8D622C8C007007E35A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2253F8D522C8C007007E35A2 /* AppDelegate.swift */; }; @@ -75,6 +81,7 @@ 22AE643824126A7500C2C4FE /* DiagramPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AE643724126A7500C2C4FE /* DiagramPathTests.swift */; }; 22AF3AE72388858B001D9F87 /* NewHostFormDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AF3AE62388858B001D9F87 /* NewHostFormDialog.swift */; }; 22AF3AE9238885AF001D9F87 /* EditHostFormDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AF3AE8238885AF001D9F87 /* EditHostFormDialog.swift */; }; 22AF3AEB23891267001D9F87 /* HostSettingExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AF3AEA23891267001D9F87 /* HostSettingExamples.swift */; }; + 22C2856224759FD40000C1E8 /* CertificateLocationPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C2856124759FD40000C1E8 /* CertificateLocationPicker.swift */; }; 22C386A322CB84900054C385 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C386A222CB84900054C385 /* RootView.swift */; }; 22C7F0D32416A16600534880 /* MQTTAnalyzerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C7F0D22416A16600534880 /* MQTTAnalyzerUITests.swift */; }; 22C9F72F23B7486E00892C4B /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 22C9F72E23B7486E00892C4B /* .swiftlint.yml */; }; @@ -104,6 +111,7 @@ 22E469E2238149FD00D72BD6 /* StringSizeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E469E1238149FD00D72BD6 /* StringSizeExtension.swift */; }; 22E469E423814CEF00D72BD6 /* JsonFormatString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E469E323814CEF00D72BD6 /* JsonFormatString.swift */; }; 22E8971722CFBFED00A4B8A3 /* TimeSeriesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E8971622CFBFED00A4B8A3 /* TimeSeriesModel.swift */; }; 22F6057B23D4911000E6338B /* DataMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F6057A23D4911000E6338B /* DataMigration.swift */; }; + 22F67AE124716ED50082C79F /* CloudDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F67AE024716ED50082C79F /* CloudDataManager.swift */; }; 22F8BEE723C24A5800422BFF /* StringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F8BEE623C24A5800422BFF /* StringUtils.swift */; }; 22F8BEE923C31C3B00422BFF /* MoscapsuleClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F8BEE823C31C3B00422BFF /* MoscapsuleClient.swift */; }; 22F8BEEC23C7871F00422BFF /* LoginDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F8BEEB23C7871F00422BFF /* LoginDialog.swift */; }; @@ -148,6 +156,12 @@ 221C571B2466847800C0DD02 /* QuestionBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionBox.swift; sourceTree = ""; }; 221C571D2466C9CD00C0DD02 /* AWSIOTPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSIOTPreset.swift; sourceTree = "<group>"; }; 221C571F2466CC2800C0DD02 /* AWSIOTPresetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSIOTPresetTests.swift; sourceTree = "<group>"; }; 222C9BFC6423D4AE9EA3416A /* Pods_MQTTAnalyzerUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MQTTAnalyzerUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 223AF5D02477D575009810E6 /* FileLister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLister.swift; sourceTree = "<group>"; }; + 223AF5D22477D5CA009810E6 /* FileListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileListView.swift; sourceTree = "<group>"; }; + 223AF5D42477D5F5009810E6 /* FileItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileItemView.swift; sourceTree = "<group>"; }; + 223AF5D62477D60A009810E6 /* PKCS12HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCS12HelpView.swift; sourceTree = "<group>"; }; + 223AF5D82477D620009810E6 /* NoFilesHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoFilesHelpView.swift; sourceTree = "<group>"; }; + 223AF5DA2477D64B009810E6 /* CertificateFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateFile.swift; sourceTree = "<group>"; }; 223EF0032382F99A002ADF3E /* MQTTAnalyzer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MQTTAnalyzer.entitlements; sourceTree = "<group>"; }; 223EF0052387084D002ADF3E /* HostSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HostSetting.swift; path = MQTTAnalyzer/model/HostSetting.swift; sourceTree = SOURCE_ROOT; }; 223EF00723870AA5002ADF3E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; @@ -213,6 +227,7 @@ 22AE643724126A7500C2C4FE /* DiagramPathTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagramPathTests.swift; sourceTree = " "; }; 22AF3AE62388858B001D9F87 /* NewHostFormDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHostFormDialog.swift; sourceTree = "<group>"; }; 22AF3AE8238885AF001D9F87 /* EditHostFormDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHostFormDialog.swift; sourceTree = "<group>"; }; 22AF3AEA23891267001D9F87 /* HostSettingExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSettingExamples.swift; sourceTree = "<group>"; }; + 22C2856124759FD40000C1E8 /* CertificateLocationPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateLocationPicker.swift; sourceTree = "<group>"; }; 22C386A222CB84900054C385 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; }; 22C7F0D02416A16600534880 /* MQTTAnalyzerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MQTTAnalyzerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 22C7F0D22416A16600534880 /* MQTTAnalyzerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTAnalyzerUITests.swift; sourceTree = "<group>"; }; @@ -244,6 +259,7 @@ 22E469E1238149FD00D72BD6 /* StringSizeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringSizeExtension.swift; sourceTree = " "; }; 22E469E323814CEF00D72BD6 /* JsonFormatString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonFormatString.swift; sourceTree = "<group>"; }; 22E8971622CFBFED00A4B8A3 /* TimeSeriesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeSeriesModel.swift; sourceTree = "<group>"; }; 22F6057A23D4911000E6338B /* DataMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataMigration.swift; sourceTree = "<group>"; }; + 22F67AE024716ED50082C79F /* CloudDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudDataManager.swift; sourceTree = "<group>"; }; 22F8BEE623C24A5800422BFF /* StringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtils.swift; sourceTree = "<group>"; }; 22F8BEE823C31C3B00422BFF /* MoscapsuleClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoscapsuleClient.swift; sourceTree = "<group>"; }; 22F8BEEB23C7871F00422BFF /* LoginDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDialog.swift; sourceTree = "<group>"; }; @@ -310,6 +326,30 @@ ); path = cocoamqtt; sourceTree = "<group>"; }; + 223AF5CE2477D521009810E6 /* certificates */ = { + isa = PBXGroup; + children = ( + 22A386F82409404600DF8F94 /* CertificateAuthenticationView.swift */, + 22A386FA240941B600DF8F94 /* CertificateFilePickerView.swift */, + 22C2856124759FD40000C1E8 /* CertificateLocationPicker.swift */, + 223AF5D22477D5CA009810E6 /* FileListView.swift */, + 223AF5D42477D5F5009810E6 /* FileItemView.swift */, + 223AF5D62477D60A009810E6 /* PKCS12HelpView.swift */, + 223AF5D82477D620009810E6 /* NoFilesHelpView.swift */, + ); + path = certificates; + sourceTree = "<group>"; + }; + 223AF5CF2477D55C009810E6 /* icloud */ = { + isa = PBXGroup; + children = ( + 22F67AE024716ED50082C79F /* CloudDataManager.swift */, + 223AF5D02477D575009810E6 /* FileLister.swift */, + 223AF5DA2477D64B009810E6 /* CertificateFile.swift */, + ); + path = icloud; + sourceTree = "<group>"; + }; 223EF0042387082E002ADF3E /* persistence */ = { isa = PBXGroup; children = ( @@ -353,6 +393,7 @@ 22FD7D0722C8D39D0078795F /* model */, 228104782381708E00112F24 /* views */, 22810479238170A200112F24 /* extensions */, 22FD7D0622C8D3760078795F /* mqtt */, + 223AF5CF2477D55C009810E6 /* icloud */, 2253F8D722C8C007007E35A2 /* SceneDelegate.swift */, 2253F8D522C8C007007E35A2 /* AppDelegate.swift */, 2253F8DE22C8C008007E35A2 /* Assets.xcassets */, @@ -419,10 +460,9 @@ }; 226A6B692445763A00ACDFC3 /* auth */ = { isa = PBXGroup; children = ( + 223AF5CE2477D521009810E6 /* certificates */, 22A386F624093EA200DF8F94 /* UsernamePasswordAuthenticationView.swift */, 226A6B66244575DB00ACDFC3 /* AuthFormView.swift */, - 22A386F82409404600DF8F94 /* CertificateAuthenticationView.swift */, - 22A386FA240941B600DF8F94 /* CertificateFilePickerView.swift */, 2202FFE124059D2A00161AD9 /* AuthenticationTypePicker.swift */, ); path = auth; @@ -920,9 +960,12 @@ 22C9F74223BB5EF100892C4B /* TopicModel.swift in Sources */, 2281047723816FD800112F24 /* QuickFilterTextDebounce.swift in Sources */, 22A386FB240941B600DF8F94 /* CertificateFilePickerView.swift in Sources */, 22FD7D0222C8D2660078795F /* RootModel.swift in Sources */, + 22C2856224759FD40000C1E8 /* CertificateLocationPicker.swift in Sources */, 228104912381770000112F24 /* ReconnectView.swift in Sources */, + 223AF5D32477D5CA009810E6 /* FileListView.swift in Sources */, 22D50F9722CE4C4300F37EAF /* Multimap.swift in Sources */, 22810488238173BB00112F24 /* DataSeriesView.swift in Sources */, + 223AF5D72477D60A009810E6 /* PKCS12HelpView.swift in Sources */, 2281048A2381740000112F24 /* MessageView.swift in Sources */, 2253F8D822C8C007007E35A2 /* SceneDelegate.swift in Sources */, 22C9F73C23BB5E1300892C4B /* MessagesByTopic.swift in Sources */, @@ -969,6 +1012,7 @@ 22AF3AE9238885AF001D9F87 /* EditHostFormDialog.swift in Sources */, 22AE64342412636300C2C4FE /* DiagramPath.swift in Sources */, 220357232445D6F200A98CD3 /* ClientImplFormView.swift in Sources */, 2281047623816FD800112F24 /* QuickFilterView.swift in Sources */, + 22F67AE124716ED50082C79F /* CloudDataManager.swift in Sources */, 226A6B612445757900ACDFC3 /* TopicsFormView.swift in Sources */, 22F8BEEC23C7871F00422BFF /* LoginDialog.swift in Sources */, 226A6B65244575AF00ACDFC3 /* LimitsFormView.swift in Sources */, @@ -980,12 +1024,16 @@ 2291424C23BF78000086C251 /* AboutView.swift in Sources */, 22AE64362412637A00C2C4FE /* TimeSeriesValue.swift in Sources */, 22C9F73723B79A1300892C4B /* MessageTextView.swift in Sources */, 22DF52AE246AE8E000A24EDD /* NamedLink.swift in Sources */, + 223AF5D92477D620009810E6 /* NoFilesHelpView.swift in Sources */, + 223AF5D52477D5F5009810E6 /* FileItemView.swift in Sources */, 22E469DB23801CA000D72BD6 /* ArrayUtils.swift in Sources */, 226A6B632445759600ACDFC3 /* ClientIDFormView.swift in Sources */, 226A6B67244575DB00ACDFC3 /* AuthFormView.swift in Sources */, 2291424923BF6ECE0086C251 /* KeyboardResponsiveModifier.swift in Sources */, 226A6B5624449F5400ACDFC3 /* ProtocolPicker.swift in Sources */, 220357252445DD6E00A98CD3 /* DeprecationBox.swift in Sources */, + 223AF5D12477D575009810E6 /* FileLister.swift in Sources */, + 223AF5DB2477D64B009810E6 /* CertificateFile.swift in Sources */, 22FD7D0022C8D2660078795F /* EditHostFormView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1163,9 +1211,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MQTTAnalyzer/MQTTAnalyzer.entitlements; CODE_SIGN_STYLE = Automatic; - objects = { + /* Begin PBXBuildFile section */ - objects = { + }; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_ASSET_PATHS = "MQTTAnalyzer/Preview\\ Content"; DEVELOPMENT_TEAM = 643R6YSRER; @@ -1176,7 +1224,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - }; + 22DF52AE246AE8E000A24EDD /* NamedLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22DF52AD246AE8E000A24EDD /* NamedLink.swift */; }; PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1191,9 +1239,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MQTTAnalyzer/MQTTAnalyzer.entitlements; CODE_SIGN_STYLE = Automatic; - objects = { + /* Begin PBXBuildFile section */ - objects = { + }; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_ASSET_PATHS = "MQTTAnalyzer/Preview\\ Content"; DEVELOPMENT_TEAM = 643R6YSRER; @@ -1204,7 +1252,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - }; + 22DF52AE246AE8E000A24EDD /* NamedLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22DF52AD246AE8E000A24EDD /* NamedLink.swift */; }; PRODUCT_BUNDLE_IDENTIFIER = de.rnd7.MQTTAnalyzer; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0;