diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings index 15ef50b..00f4d58 100644 --- a/WireGuard/WireGuard/Base.lproj/Localizable.strings +++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings @@ -23,6 +23,9 @@ // Tunnels list alerts +"alertImportedFromMultipleFilesTitle (%d)" = "Created %d tunnels"; +"alertImportedFromMultipleFilesMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from imported files"; + "alertImportedFromZipTitle (%d)" = "Created %d tunnels"; "alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive"; diff --git a/WireGuard/WireGuard/UI/TunnelImporter.swift b/WireGuard/WireGuard/UI/TunnelImporter.swift index 7625761..e51c70d 100644 --- a/WireGuard/WireGuard/UI/TunnelImporter.swift +++ b/WireGuard/WireGuard/UI/TunnelImporter.swift @@ -4,7 +4,45 @@ import Foundation class TunnelImporter { - static func importFromFile(url: URL, into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) { + static func importFromFile(urls: [URL], into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) { + guard !urls.isEmpty else { + completionHandler?() + return + } + if urls.count > 1 { + let dispatchGroup = DispatchGroup() + var configs = [TunnelConfiguration?]() + for url in urls { + if url.pathExtension.lowercased() == "zip" { + dispatchGroup.enter() + ZipImporter.importConfigFiles(from: url) { result in + if let configsInZip = result.value { + configs.append(contentsOf: configsInZip) + } + dispatchGroup.leave() + } + } else { + let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines) + let fileContents = try? String(contentsOf: url) + let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents ?? "", called: fileBaseName) + configs.append(tunnelConfiguration) + } + } + dispatchGroup.notify(queue: .main) { + tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful in + if numberSuccessful == configs.count { + completionHandler?() + return + } + let title = tr(format: "alertImportedFromMultipleFilesTitle (%d)", numberSuccessful) + let message = tr(format: "alertImportedFromMultipleFilesMessage (%1$d of %2$d)", numberSuccessful, configs.count) + errorPresenterType.showErrorAlert(title: title, message: message, from: sourceVC, onPresented: completionHandler) + } + } + return + } + assert(urls.count == 1) + let url = urls.first! if url.pathExtension.lowercased() == "zip" { ZipImporter.importConfigFiles(from: url) { result in if let error = result.error { diff --git a/WireGuard/WireGuard/UI/iOS/AppDelegate.swift b/WireGuard/WireGuard/UI/iOS/AppDelegate.swift index c295002..8743c70 100644 --- a/WireGuard/WireGuard/UI/iOS/AppDelegate.swift +++ b/WireGuard/WireGuard/UI/iOS/AppDelegate.swift @@ -28,7 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { guard let tunnelsManager = mainVC?.tunnelsManager else { return true } - TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) { + TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) { _ = FileManager.deleteFile(at: url) } return true diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift index ccef4cd..e8e0a52 100644 --- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift @@ -164,9 +164,7 @@ class TunnelsListTableViewController: UIViewController { extension TunnelsListTableViewController: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let tunnelsManager = tunnelsManager else { return } - urls.forEach { url in - TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self) - } + TunnelImporter.importFromFile(urls: urls, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self) } } diff --git a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift index 1ef8f75..67b074c 100644 --- a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift +++ b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift @@ -9,12 +9,12 @@ class ImportPanelPresenter { let openPanel = NSOpenPanel() openPanel.prompt = tr("macSheetButtonImport") openPanel.allowedFileTypes = ["conf", "zip"] + openPanel.allowsMultipleSelection = true openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in guard let tunnelsManager = tunnelsManager else { return } guard response == .OK else { return } - guard let url = openPanel.url else { return } AppStorePrivacyNotice.show(from: sourceVC, into: tunnelsManager) { - TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self) + TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self) } } }