diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5439ab3..ab4e91de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## 1.9.0 Beta 2056 (2019-10-21)
+## Unreleased
### Changed
- Upgrade project to Xcode 11.
+- Organizer shown on launch rather than profile in use.
### Fixed
diff --git a/Passepartout-iOS/AppDelegate.swift b/Passepartout-iOS/AppDelegate.swift
index 81c4e41f..23e34cc9 100644
--- a/Passepartout-iOS/AppDelegate.swift
+++ b/Passepartout-iOS/AppDelegate.swift
@@ -32,6 +32,8 @@ import Convenience
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
+
+ private var importer: HostImporter?
override init() {
AppConstants.Log.configure()
@@ -86,7 +88,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
// MARK: UISplitViewControllerDelegate
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
- return !TransientStore.shared.service.hasActiveProfile()
+ return true//!TransientStore.shared.service.hasActiveProfile()
}
// MARK: URLs
@@ -101,26 +103,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
}
private func tryParseURL(_ url: URL, passphrase: String?, target: UIViewController) -> Bool {
- let passphraseBlock = { (passphrase) in
- _ = self.tryParseURL(url, passphrase: passphrase, target: target)
+ guard let rootViewController = window?.rootViewController else {
+ return false
}
- let passphraseCancelBlock = {
- _ = try? FileManager.default.removeItem(at: url)
+ importer = HostImporter(withConfigurationURL: url, parentViewController: rootViewController)
+ importer?.importHost(withPassphrase: passphrase, removeOnError: true, removeOnCancel: true) {
+ self.handleParsingResult($0, in: rootViewController)
}
- guard let parsingResult = OpenVPN.ConfigurationParser.Result.from(url, withErrorAlertIn: target, passphrase: passphrase, removeOnError: true, passphraseBlock: passphraseBlock, passphraseCancelBlock: passphraseCancelBlock) else {
- return true
- }
- if let warning = parsingResult.warning {
- OpenVPN.ConfigurationParser.Result.alertImportWarning(url: url, in: target, withWarning: warning) {
- if $0 {
- self.handleParsingResult(parsingResult, in: target)
- } else {
- try? FileManager.default.removeItem(at: url)
- }
- }
- return true
- }
- handleParsingResult(parsingResult, in: target)
return true
}
diff --git a/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift b/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift
deleted file mode 100644
index 157711d3..00000000
--- a/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift
+++ /dev/null
@@ -1,148 +0,0 @@
-//
-// OpenVPN.ConfigurationParserResult+Alerts.swift
-// Passepartout-iOS
-//
-// Created by Davide De Rosa on 10/27/18.
-// Copyright (c) 2019 Davide De Rosa. All rights reserved.
-//
-// https://github.com/passepartoutvpn
-//
-// This file is part of Passepartout.
-//
-// Passepartout is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Passepartout is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Passepartout. If not, see .
-//
-
-import Foundation
-import UIKit
-import TunnelKit
-import SwiftyBeaver
-import PassepartoutCore
-
-private let log = SwiftyBeaver.self
-
-extension OpenVPN.ConfigurationParser.Result {
- static func from(_ url: URL, withErrorAlertIn viewController: UIViewController, passphrase: String?, removeOnError: Bool,
- passphraseBlock: @escaping (String) -> Void, passphraseCancelBlock: (() -> Void)?) -> OpenVPN.ConfigurationParser.Result? {
-
- let result: OpenVPN.ConfigurationParser.Result
- let fm = FileManager.default
-
- log.debug("Parsing configuration URL: \(url)")
- do {
- result = try OpenVPN.ConfigurationParser.parsed(fromURL: url, passphrase: passphrase)
- } catch let e as ConfigurationError {
- switch e {
- case .encryptionPassphrase, .unableToDecrypt(_):
- let alert = UIAlertController.asAlert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.EncryptionPassphrase.message)
- alert.addTextField { (field) in
- field.isSecureTextEntry = true
- }
- alert.addPreferredAction(L10n.Core.Global.ok) {
- guard let passphrase = alert.textFields?.first?.text else {
- return
- }
- passphraseBlock(passphrase)
- }
- alert.addCancelAction(L10n.Core.Global.cancel) {
- passphraseCancelBlock?()
- }
- viewController.present(alert, animated: true, completion: nil)
-
- default:
- let message = localizedMessage(forError: e)
- alertImportError(url: url, in: viewController, withMessage: message)
- if removeOnError {
- try? fm.removeItem(at: url)
- }
- }
- return nil
- } catch let e {
- let message = localizedMessage(forError: e)
- alertImportError(url: url, in: viewController, withMessage: message)
- if removeOnError {
- try? fm.removeItem(at: url)
- }
- return nil
- }
- return result
- }
-
- private static func alertImportError(url: URL, in vc: UIViewController, withMessage message: String) {
- let alert = UIAlertController.asAlert(url.normalizedFilename, message)
-// alert.addPreferredAction(L10n.Core.ParsedFile.Alerts.Buttons.report) {
-// var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url)
-// attach.description = message
-// IssueReporter.shared.present(in: vc, withAttachments: attach)
-// }
- alert.addCancelAction(L10n.Core.Global.ok)
- vc.present(alert, animated: true, completion: nil)
- }
-
- static func alertImportWarning(url: URL, in vc: UIViewController, withWarning warning: ConfigurationError, completionHandler: @escaping (Bool) -> Void) {
- let message = details(forWarning: warning)
- let alert = UIAlertController.asAlert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message))
- alert.addPreferredAction(L10n.Core.Global.ok) {
- completionHandler(true)
- }
- alert.addCancelAction(L10n.Core.Global.cancel) {
- completionHandler(false)
- }
- vc.present(alert, animated: true, completion: nil)
- }
-
- private static func localizedMessage(forError error: Error) -> String {
- if let appError = error as? ConfigurationError {
- switch appError {
- case .malformed(let option):
- log.error("Could not parse configuration URL: malformed option, \(option)")
- return L10n.Core.ParsedFile.Alerts.Malformed.message(option)
-
- case .missingConfiguration(let option):
- log.error("Could not parse configuration URL: missing configuration, \(option)")
- return L10n.Core.ParsedFile.Alerts.Missing.message(option)
-
- case .unsupportedConfiguration(var option):
- if option.contains("external") {
- option.append(" - see FAQ")
- }
- log.error("Could not parse configuration URL: unsupported configuration, \(option)")
- return L10n.Core.ParsedFile.Alerts.Unsupported.message(option)
-
- default:
- break
- }
- }
- log.error("Could not parse configuration URL: \(error)")
- return L10n.Core.ParsedFile.Alerts.Parsing.message(error.localizedDescription)
- }
-
- private static func details(forWarning warning: ConfigurationError) -> String {
- switch warning {
- case .malformed(let option):
- return option
-
- case .missingConfiguration(let option):
- return option
-
- case .unsupportedConfiguration(var option):
- if option.contains("external") {
- option.append(" - see FAQ")
- }
- return option
-
- default:
- return "" // XXX: should never get here
- }
- }
-}
diff --git a/Passepartout-iOS/Global/HostImporter.swift b/Passepartout-iOS/Global/HostImporter.swift
new file mode 100644
index 00000000..59a9159c
--- /dev/null
+++ b/Passepartout-iOS/Global/HostImporter.swift
@@ -0,0 +1,170 @@
+//
+// HostImporter.swift
+// Passepartout-macOS
+//
+// Created by Davide De Rosa on 10/22/19.
+// Copyright (c) 2019 Davide De Rosa. All rights reserved.
+//
+// https://github.com/passepartoutvpn
+//
+// This file is part of Passepartout.
+//
+// Passepartout is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Passepartout is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Passepartout. If not, see .
+//
+
+import Foundation
+import PassepartoutCore
+import TunnelKit
+import SwiftyBeaver
+
+private let log = SwiftyBeaver.self
+
+class HostImporter {
+ private let service = TransientStore.shared.service
+
+ private weak var viewController: UIViewController?
+
+ private let configurationURL: URL
+
+ init(withConfigurationURL configurationURL: URL, parentViewController: UIViewController) {
+ self.configurationURL = configurationURL
+ log.debug("Parsing configuration URL: \(configurationURL)")
+
+ viewController = parentViewController
+ }
+
+ func importHost(withPassphrase passphrase: String?, removeOnError: Bool, removeOnCancel: Bool, completionHandler: @escaping (OpenVPN.ConfigurationParser.Result) -> Void) {
+ let result: OpenVPN.ConfigurationParser.Result
+ do {
+ result = try OpenVPN.ConfigurationParser.parsed(fromURL: configurationURL, passphrase: passphrase)
+ } catch let e as ConfigurationError {
+ switch e {
+ case .encryptionPassphrase, .unableToDecrypt(_):
+ enterPassphraseForHost(at: configurationURL, removeOnError: removeOnError, removeOnCancel: removeOnCancel, completionHandler: completionHandler)
+
+ default:
+ alertImportError(e, removeOnError: removeOnError)
+ }
+ return
+ } catch let e {
+ alertImportError(e, removeOnError: removeOnError)
+ return
+ }
+
+ if let warning = result.warning {
+ alertImportWarning(warning, removeOnCancel: removeOnCancel) {
+ completionHandler(result)
+ }
+ return
+ }
+
+ completionHandler(result)
+ }
+
+ private func alertImportError(_ error: Error, removeOnError: Bool) {
+ let message = HostImporter.localizedMessage(forError: error)
+ let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, message)
+ alert.addCancelAction(L10n.Core.Global.ok)
+ viewController?.present(alert, animated: true, completion: nil)
+
+ if removeOnError {
+ try? FileManager.default.removeItem(at: configurationURL)
+ }
+ }
+
+ private func alertImportWarning(_ warning: ConfigurationError, removeOnCancel: Bool, completionHandler: @escaping () -> Void) {
+ let message = HostImporter.localizedDetailsMessage(forWarning: warning)
+ let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message))
+ alert.addPreferredAction(L10n.Core.Global.ok) {
+ completionHandler()
+ }
+ alert.addCancelAction(L10n.Core.Global.cancel) {
+ if removeOnCancel {
+ try? FileManager.default.removeItem(at: self.configurationURL)
+ }
+ }
+ viewController?.present(alert, animated: true, completion: nil)
+ }
+
+ private func enterPassphraseForHost(at url: URL, removeOnError: Bool, removeOnCancel: Bool, completionHandler: @escaping (OpenVPN.ConfigurationParser.Result) -> Void) {
+ let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, L10n.Core.ParsedFile.Alerts.EncryptionPassphrase.message)
+ alert.addTextField { (field) in
+ field.isSecureTextEntry = true
+ }
+ alert.addPreferredAction(L10n.Core.Global.ok) {
+ guard let passphrase = alert.textFields?.first?.text else {
+ return
+ }
+ self.importHost(
+ withPassphrase: passphrase,
+ removeOnError: removeOnError,
+ removeOnCancel: removeOnCancel,
+ completionHandler: completionHandler
+ )
+ }
+ alert.addCancelAction(L10n.Core.Global.cancel) {
+ if removeOnCancel {
+ try? FileManager.default.removeItem(at: url)
+ }
+ }
+ viewController?.present(alert, animated: true, completion: nil)
+ }
+
+ // MARK: Helpers
+
+ private static func localizedMessage(forError error: Error) -> String {
+ if let appError = error as? ConfigurationError {
+ switch appError {
+ case .malformed(let option):
+ log.error("Could not parse configuration URL: malformed option, \(option)")
+ return L10n.Core.ParsedFile.Alerts.Malformed.message(option)
+
+ case .missingConfiguration(let option):
+ log.error("Could not parse configuration URL: missing configuration, \(option)")
+ return L10n.Core.ParsedFile.Alerts.Missing.message(option)
+
+ case .unsupportedConfiguration(var option):
+ if option.contains("external") {
+ option.append(" (see FAQ)")
+ }
+ log.error("Could not parse configuration URL: unsupported configuration, \(option)")
+ return L10n.Core.ParsedFile.Alerts.Unsupported.message(option)
+
+ default:
+ break
+ }
+ }
+ log.error("Could not parse configuration URL: \(error)")
+ return L10n.Core.ParsedFile.Alerts.Parsing.message(error.localizedDescription)
+ }
+
+ private static func localizedDetailsMessage(forWarning warning: ConfigurationError) -> String {
+ switch warning {
+ case .malformed(let option):
+ return option
+
+ case .missingConfiguration(let option):
+ return option
+
+ case .unsupportedConfiguration(var option):
+ if option.contains("external") {
+ option.append(" (see FAQ)")
+ }
+ return option
+
+ default:
+ return "" // XXX: should never get here
+ }
+ }
+}
diff --git a/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift b/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift
index 1c0b3ac9..bfd85f4c 100644
--- a/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift
+++ b/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift
@@ -32,6 +32,8 @@ private let log = SwiftyBeaver.self
class ImportedHostsViewController: UITableViewController {
private lazy var pendingConfigurationURLs = TransientStore.shared.service.pendingConfigurationURLs().sortedCaseInsensitive()
+
+ private var importer: HostImporter?
private var parsingResult: OpenVPN.ConfigurationParser.Result?
@@ -76,35 +78,18 @@ class ImportedHostsViewController: UITableViewController {
return false
}
let url = pendingConfigurationURLs[indexPath.row]
- return tryParseURL(url, passphrase: nil, cell: cell)
+ return tryParseURL(url, cell: cell)
}
return true
}
- private func tryParseURL(_ url: URL, passphrase: String?, cell: UITableViewCell) -> Bool {
- let passphraseBlock: (String) -> Void = { (passphrase) in
- guard self.tryParseURL(url, passphrase: passphrase, cell: cell) else {
- return
- }
- self.perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier, sender: cell)
- }
- guard let parsingResult = OpenVPN.ConfigurationParser.Result.from(url, withErrorAlertIn: self, passphrase: passphrase, removeOnError: false, passphraseBlock: passphraseBlock, passphraseCancelBlock: nil) else {
- deselectSelectedRow()
- return false
- }
- self.parsingResult = parsingResult
-
- // postpone segue until alert dismissal
- if let warning = parsingResult.warning {
- OpenVPN.ConfigurationParser.Result.alertImportWarning(url: url, in: self, withWarning: warning) {
- self.deselectSelectedRow()
- if $0 {
- self.perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier)
- } else {
- self.parsingResult = nil
- }
- }
- return false
+ private func tryParseURL(_ url: URL, cell: UITableViewCell) -> Bool {
+ deselectSelectedRow()
+
+ importer = HostImporter(withConfigurationURL: url, parentViewController: self)
+ importer?.importHost(withPassphrase: nil, removeOnError: false, removeOnCancel: false) {
+ self.parsingResult = $0
+ self.perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier)
}
return true
}
diff --git a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
index 490eab3f..39794d2a 100644
--- a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
+++ b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
@@ -115,6 +115,11 @@ class OrganizerViewController: UITableViewController, StrongTableHost {
reloadModel()
tableView.reloadData()
+ // XXX: if split vc is collapsed when a profile is in use, this vc
+ // is not loaded on app launch. consequentially, service.delegate remains
+ // nil until the Organizer is entered
+ //
+ // see UISplitViewControllerDelegate in AppDelegate (collapse is now commented out)
service.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidChangeStatus, object: nil)
diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj
index e52a9e4e..3d696a96 100644
--- a/Passepartout.xcodeproj/project.pbxproj
+++ b/Passepartout.xcodeproj/project.pbxproj
@@ -56,6 +56,7 @@
0E3152DA223FA05800F61841 /* PlaceholderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */; };
0E3152DB223FA05800F61841 /* ProfileKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D14021919F5600BB5FB2 /* ProfileKey.swift */; };
0E3152DC223FA05800F61841 /* ProviderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3AA4213DC1B000BFA2F5 /* ProviderConnectionProfile.swift */; };
+ 0E3262D9235EE8DA00B5E470 /* HostImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */; };
0E3419AD2350815E00419E18 /* Donation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3419AC2350815E00419E18 /* Donation.swift */; };
0E3586FE225BD34800509A4D /* ActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */; };
0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */; };
@@ -83,7 +84,6 @@
0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */; };
0E9CD7872257462800D033B4 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E9CD7862257462800D033B4 /* Providers.xcassets */; };
0E9CD789225746B300D033B4 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E9CD788225746B300D033B4 /* Flags.xcassets */; };
- 0EA068F4218475F800C320AD /* ConfigurationParserResult+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */; };
0EAAD71920E6669A0088754A /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; };
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; };
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */; };
@@ -179,6 +179,7 @@
0E31529B223F9EF400F61841 /* PassepartoutCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PassepartoutCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0E31529D223F9EF500F61841 /* PassepartoutCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PassepartoutCore.h; sourceTree = ""; };
0E31529E223F9EF500F61841 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostImporter.swift; sourceTree = ""; };
0E3419AC2350815E00419E18 /* Donation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Donation.swift; sourceTree = ""; };
0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTableViewCell.swift; sourceTree = ""; };
0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsAddViewController.swift; sourceTree = ""; };
@@ -247,7 +248,6 @@
0E8D97E121388B52006FB4A0 /* InfrastructurePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfrastructurePreset.swift; sourceTree = ""; };
0E9CD7862257462800D033B4 /* Providers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Providers.xcassets; sourceTree = ""; };
0E9CD788225746B300D033B4 /* Flags.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Flags.xcassets; sourceTree = ""; };
- 0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationParserResult+Alerts.swift"; sourceTree = ""; };
0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Search.swift"; sourceTree = ""; };
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedHostsViewController.swift; sourceTree = ""; };
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConnectionService+Migration.swift"; sourceTree = ""; };
@@ -534,8 +534,8 @@
isa = PBXGroup;
children = (
0E45E6E222BD793800F19312 /* App.strings */,
- 0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */,
0E3419AC2350815E00419E18 /* Donation.swift */,
+ 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */,
0EFD943D215BE10800529B64 /* IssueReporter.swift */,
0E4FD7F020D58618002221FF /* Macros.swift */,
0E24273F225951B00064A1A3 /* ProductManager.swift */,
@@ -986,6 +986,7 @@
0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */,
0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */,
0E3419AD2350815E00419E18 /* Donation.swift in Sources */,
+ 0E3262D9235EE8DA00B5E470 /* HostImporter.swift in Sources */,
0EFD9440215BED8E00529B64 /* LabelViewController.swift in Sources */,
0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */,
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */,
@@ -1000,7 +1001,6 @@
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */,
0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */,
0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */,
- 0EA068F4218475F800C320AD /* ConfigurationParserResult+Alerts.swift in Sources */,
0E57F63C20C83FC5008323CF /* AppDelegate.swift in Sources */,
0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */,
0E158ADA20E11B0B00C85A82 /* EndpointViewController.swift in Sources */,