From dc8f27c5c36a212336318f62c1c45e31bb9d95d6 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Fri, 18 Jan 2019 01:27:17 +0530 Subject: [PATCH] macOS: Rafactor by introducing a TunnelsTracker The TunnelTracker is now the central place to track what the current tunnel is, and for keeping track of the tunnel list. Signed-off-by: Roopesh Chander --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 + .../WireGuard/UI/macOS/AppDelegate.swift | 38 +++- .../UI/macOS/ImportPanelPresenter.swift | 4 +- WireGuard/WireGuard/UI/macOS/StatusMenu.swift | 164 ++++++------------ .../WireGuard/UI/macOS/TunnelsTracker.swift | 126 ++++++++++++++ 5 files changed, 218 insertions(+), 118 deletions(-) create mode 100644 WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index b6b1c32..ae18c66 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */; }; 6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; }; 6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; }; + 6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; }; 6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; }; 6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; }; 6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; }; @@ -265,6 +266,7 @@ 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditTableViewController.swift; sourceTree = ""; }; 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = ""; }; 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = ""; }; + 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = ""; }; 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = ""; }; 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = ""; }; 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = ""; }; @@ -516,6 +518,7 @@ 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */, 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */, 6FBA101621D655340051C35F /* StatusMenu.swift */, + 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */, 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */, 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */, 6FB1BD6121D2607E00A991BF /* Assets.xcassets */, @@ -1106,6 +1109,7 @@ 6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */, 6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */, 6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */, + 6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */, 6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */, 6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */, 6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */, diff --git a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift index 3f08987..95c1aa6 100644 --- a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift +++ b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift @@ -6,8 +6,12 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { + var tunnelsManager: TunnelsManager? + var tunnelsTracker: TunnelsTracker? var statusItemController: StatusItemController? - var currentTunnelStatusObserver: AnyObject? + + var manageTunnelsRootVC: ManageTunnelsRootViewController? + var manageTunnelsWindowObject: NSWindow? func applicationDidFinishLaunching(_ aNotification: Notification) { Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path) @@ -20,19 +24,35 @@ class AppDelegate: NSObject, NSApplicationDelegate { } let tunnelsManager: TunnelsManager = result.value! - let statusItemController = StatusItemController() let statusMenu = StatusMenu(tunnelsManager: tunnelsManager) + statusMenu.windowDelegate = self + let statusItemController = StatusItemController() statusItemController.statusItem.menu = statusMenu - statusItemController.currentTunnel = statusMenu.currentTunnel - self.currentTunnelStatusObserver = statusMenu.observe(\.currentTunnel) { statusMenu, _ in - statusItemController.currentTunnel = statusMenu.currentTunnel - } - self.statusItemController = statusItemController - tunnelsManager.tunnelsListDelegate = statusMenu - tunnelsManager.activationDelegate = statusMenu + let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager) + tunnelsTracker.statusMenu = statusMenu + tunnelsTracker.statusItemController = statusItemController + + self.tunnelsManager = tunnelsManager + self.tunnelsTracker = tunnelsTracker + self.statusItemController = statusItemController } } } + +extension AppDelegate: StatusMenuWindowDelegate { + func manageTunnelsWindow() -> NSWindow { + if manageTunnelsWindowObject == nil { + manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager!) + let window = NSWindow(contentViewController: manageTunnelsRootVC!) + window.title = tr("macWindowTitleManageTunnels") + window.setContentSize(NSSize(width: 800, height: 480)) + window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size + manageTunnelsWindowObject = window + tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC + } + return manageTunnelsWindowObject! + } +} diff --git a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift index 03b1be7..95fc46a 100644 --- a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift +++ b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift @@ -4,8 +4,8 @@ import Cocoa class ImportPanelPresenter { - static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController) { - guard let window = sourceVC.view.window else { return } + static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController?) { + guard let window = sourceVC?.view.window else { return } let openPanel = NSOpenPanel() openPanel.prompt = tr("macSheetButtonImport") openPanel.allowedFileTypes = ["conf", "zip"] diff --git a/WireGuard/WireGuard/UI/macOS/StatusMenu.swift b/WireGuard/WireGuard/UI/macOS/StatusMenu.swift index 634ea9f..2ae3013 100644 --- a/WireGuard/WireGuard/UI/macOS/StatusMenu.swift +++ b/WireGuard/WireGuard/UI/macOS/StatusMenu.swift @@ -3,27 +3,25 @@ import Cocoa +protocol StatusMenuWindowDelegate: class { + func manageTunnelsWindow() -> NSWindow +} + class StatusMenu: NSMenu { let tunnelsManager: TunnelsManager - var tunnelStatusObservers = [AnyObject]() var statusMenuItem: NSMenuItem? var networksMenuItem: NSMenuItem? var firstTunnelMenuItemIndex = 0 var numberOfTunnelMenuItems = 0 - @objc dynamic var currentTunnel: TunnelContainer? - - var manageTunnelsRootVC: ManageTunnelsRootViewController? - lazy var manageTunnelsWindow: NSWindow = { - manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager) - let window = NSWindow(contentViewController: manageTunnelsRootVC!) - window.title = tr("macWindowTitleManageTunnels") - window.setContentSize(NSSize(width: 800, height: 480)) - window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size - return window - }() + var currentTunnel: TunnelContainer? { + didSet { + updateStatusMenuItems(with: currentTunnel) + } + } + weak var windowDelegate: StatusMenuWindowDelegate? init(tunnelsManager: TunnelsManager) { self.tunnelsManager = tunnelsManager @@ -31,16 +29,6 @@ class StatusMenu: NSMenu { addStatusMenuItems() addItem(NSMenuItem.separator()) - for index in 0 ..< tunnelsManager.numberOfTunnels() { - let tunnel = tunnelsManager.tunnel(at: index) - if tunnel.status != .inactive { - currentTunnel = tunnel - } - let isUpdated = updateStatusMenuItems(with: tunnel, ignoreInactive: true) - if isUpdated { - break - } - } firstTunnelMenuItemIndex = numberOfItems let isAdded = addTunnelMenuItems() @@ -69,19 +57,21 @@ class StatusMenu: NSMenu { self.networksMenuItem = networksMenuItem } - @discardableResult //swiftlint:disable:next cyclomatic_complexity - func updateStatusMenuItems(with tunnel: TunnelContainer, ignoreInactive: Bool) -> Bool { - guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return false } + func updateStatusMenuItems(with tunnel: TunnelContainer?) { + guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return } + guard let tunnel = tunnel else { + statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive")) + networksMenuItem.title = "" + networksMenuItem.isHidden = true + return + } var statusText: String switch tunnel.status { case .waiting: - return false + statusText = tr("tunnelStatusWaiting") case .inactive: - if ignoreInactive { - return false - } statusText = tr("tunnelStatusInactive") case .activating: statusText = tr("tunnelStatusActivating") @@ -98,7 +88,7 @@ class StatusMenu: NSMenu { statusMenuItem.title = tr(format: "macStatus (%@)", statusText) if tunnel.status == .inactive { - networksMenuItem.title = tr("macMenuNetworksInactive") + networksMenuItem.title = "" networksMenuItem.isHidden = true } else { let allowedIPs = tunnel.tunnelConfiguration?.peers.flatMap { $0.allowedIPs }.map { $0.stringRepresentation }.joined(separator: ", ") ?? "" @@ -109,7 +99,6 @@ class StatusMenu: NSMenu { } networksMenuItem.isHidden = false } - return true } func addTunnelMenuItems() -> Bool { @@ -140,24 +129,25 @@ class StatusMenu: NSMenu { } @objc func tunnelClicked(sender: AnyObject) { - guard let tunnelMenuItem = sender as? NSMenuItem else { return } - guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return } + guard let tunnelMenuItem = sender as? TunnelMenuItem else { return } if tunnelMenuItem.state == .off { - tunnelsManager.startActivation(of: tunnel) + tunnelsManager.startActivation(of: tunnelMenuItem.tunnel) } else { - tunnelsManager.startDeactivation(of: tunnel) + tunnelsManager.startDeactivation(of: tunnelMenuItem.tunnel) } } @objc func manageTunnelsClicked() { NSApp.activate(ignoringOtherApps: true) + guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return } manageTunnelsWindow.makeKeyAndOrderFront(self) } @objc func importTunnelsClicked() { NSApp.activate(ignoringOtherApps: true) + guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return } manageTunnelsWindow.makeKeyAndOrderFront(self) - ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsRootVC!) + ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsWindow.contentViewController) } @objc func aboutClicked() { @@ -179,22 +169,8 @@ class StatusMenu: NSMenu { extension StatusMenu { func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) { - let menuItem = NSMenuItem(title: tunnel.name, action: #selector(tunnelClicked(sender:)), keyEquivalent: "") + let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:))) menuItem.target = self - menuItem.representedObject = tunnel - updateTunnelMenuItem(menuItem) - let statusObservationToken = tunnel.observe(\.status) { [weak self] tunnel, _ in - updateTunnelMenuItem(menuItem) - if tunnel.status == .deactivating || tunnel.status == .inactive { - if self?.currentTunnel == tunnel { - self?.currentTunnel = self?.tunnelsManager.waitingTunnel() - } - } else { - self?.currentTunnel = tunnel - } - self?.updateStatusMenuItems(with: tunnel, ignoreInactive: false) - } - tunnelStatusObservers.insert(statusObservationToken, at: tunnelIndex) insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex) if numberOfTunnelMenuItems == 0 { insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1) @@ -204,7 +180,6 @@ extension StatusMenu { func removeTunnelMenuItem(at tunnelIndex: Int) { removeItem(at: firstTunnelMenuItemIndex + tunnelIndex) - tunnelStatusObservers.remove(at: tunnelIndex) numberOfTunnelMenuItems -= 1 if numberOfTunnelMenuItems == 0 { if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem { @@ -214,73 +189,48 @@ extension StatusMenu { } func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) { - let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex)! - let oldMenuItemTitle = oldMenuItem.title - let oldMenuItemTunnel = oldMenuItem.representedObject + guard let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex) as? TunnelMenuItem else { return } + let oldMenuItemTunnel = oldMenuItem.tunnel removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex) - let menuItem = NSMenuItem(title: oldMenuItemTitle, action: #selector(tunnelClicked(sender:)), keyEquivalent: "") + let menuItem = TunnelMenuItem(tunnel: oldMenuItemTunnel, action: #selector(tunnelClicked(sender:))) menuItem.target = self - menuItem.representedObject = oldMenuItemTunnel insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex) - let statusObserver = tunnelStatusObservers.remove(at: oldTunnelIndex) - tunnelStatusObservers.insert(statusObserver, at: newTunnelIndex) + } } -private func updateTunnelMenuItem(_ tunnelMenuItem: NSMenuItem) { - guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return } - tunnelMenuItem.title = tunnel.name - let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating) - tunnelMenuItem.state = shouldShowCheckmark ? .on : .off -} +class TunnelMenuItem: NSMenuItem { -extension StatusMenu: TunnelsManagerListDelegate { - func tunnelAdded(at index: Int) { - let tunnel = tunnelsManager.tunnel(at: index) - insertTunnelMenuItem(for: tunnel, at: index) - manageTunnelsRootVC?.tunnelsListVC?.tunnelAdded(at: index) - } + var tunnel: TunnelContainer - func tunnelModified(at index: Int) { - if let tunnelMenuItem = item(at: firstTunnelMenuItemIndex + index) { - updateTunnelMenuItem(tunnelMenuItem) + private var statusObservationToken: AnyObject? + private var nameObservationToken: AnyObject? + + init(tunnel: TunnelContainer, action selector: Selector?) { + self.tunnel = tunnel + super.init(title: tunnel.name, action: selector, keyEquivalent: "") + updateStatus() + let statusObservationToken = tunnel.observe(\.status) { [weak self] _, _ in + self?.updateStatus() } - manageTunnelsRootVC?.tunnelsListVC?.tunnelModified(at: index) + updateTitle() + let nameObservationToken = tunnel.observe(\TunnelContainer.name) { [weak self] _, _ in + self?.updateTitle() + } + self.statusObservationToken = statusObservationToken + self.nameObservationToken = nameObservationToken } - func tunnelMoved(from oldIndex: Int, to newIndex: Int) { - moveTunnelMenuItem(from: oldIndex, to: newIndex) - manageTunnelsRootVC?.tunnelsListVC?.tunnelMoved(from: oldIndex, to: newIndex) + required init(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - func tunnelRemoved(at index: Int) { - removeTunnelMenuItem(at: index) - manageTunnelsRootVC?.tunnelsListVC?.tunnelRemoved(at: index) - } -} - -extension StatusMenu: TunnelsManagerActivationDelegate { - func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) { - if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsWindow.isVisible { - ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC) - } else { - ErrorPresenter.showErrorAlert(error: error, from: nil) - } - } - - func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) { - // Nothing to do - } - - func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) { - if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsWindow.isVisible { - ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC) - } else { - ErrorPresenter.showErrorAlert(error: error, from: nil) - } - } - - func tunnelActivationSucceeded(tunnel: TunnelContainer) { - // Nothing to do + func updateTitle() { + title = tunnel.name + } + + func updateStatus() { + let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating) + state = shouldShowCheckmark ? .on : .off } } diff --git a/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift b/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift new file mode 100644 index 0000000..781fa2e --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Cocoa + +// Keeps track of tunnels and informs the following objects of changes in tunnels: +// - Status menu +// - Status item controller +// - Tunnels list view controller in the Manage Tunnels window + +class TunnelsTracker { + + weak var statusMenu: StatusMenu? + weak var statusItemController: StatusItemController? + weak var manageTunnelsRootVC: ManageTunnelsRootViewController? + + private var tunnelsManager: TunnelsManager + private var tunnelStatusObservers = [AnyObject]() + private var currentTunnel: TunnelContainer? { + didSet { + statusMenu?.currentTunnel = currentTunnel + statusItemController?.currentTunnel = currentTunnel + } + } + + init(tunnelsManager: TunnelsManager) { + self.tunnelsManager = tunnelsManager + + if let waitingTunnel = tunnelsManager.waitingTunnel() { + currentTunnel = waitingTunnel + } else { + for index in 0 ..< tunnelsManager.numberOfTunnels() { + let tunnel = tunnelsManager.tunnel(at: index) + if tunnel.status != .inactive { + currentTunnel = tunnel + break + } + } + } + + for index in 0 ..< tunnelsManager.numberOfTunnels() { + let tunnel = tunnelsManager.tunnel(at: index) + let statusObservationToken = observeStatus(of: tunnel) + tunnelStatusObservers.insert(statusObservationToken, at: index) + } + + tunnelsManager.tunnelsListDelegate = self + tunnelsManager.activationDelegate = self + } + + func observeStatus(of tunnel: TunnelContainer) -> AnyObject { + return tunnel.observe(\.status) { [weak self] tunnel, _ in + guard let self = self else { return } + if tunnel.status == .deactivating || tunnel.status == .inactive { + if self.currentTunnel == tunnel { + if let waitingTunnel = self.tunnelsManager.waitingTunnel() { + self.currentTunnel = waitingTunnel + } else if tunnel.status == .inactive { + self.currentTunnel = nil + } + } + } else { + self.currentTunnel = tunnel + } + } + } +} + +extension TunnelsTracker: TunnelsManagerListDelegate { + func tunnelAdded(at index: Int) { + let tunnel = tunnelsManager.tunnel(at: index) + if tunnel.status != .deactivating && tunnel.status != .inactive { + self.currentTunnel = tunnel + } + let statusObservationToken = observeStatus(of: tunnel) + tunnelStatusObservers.insert(statusObservationToken, at: index) + + statusMenu?.insertTunnelMenuItem(for: tunnel, at: index) + manageTunnelsRootVC?.tunnelsListVC?.tunnelAdded(at: index) + } + + func tunnelModified(at index: Int) { + manageTunnelsRootVC?.tunnelsListVC?.tunnelModified(at: index) + } + + func tunnelMoved(from oldIndex: Int, to newIndex: Int) { + let statusObserver = tunnelStatusObservers.remove(at: oldIndex) + tunnelStatusObservers.insert(statusObserver, at: newIndex) + + statusMenu?.moveTunnelMenuItem(from: oldIndex, to: newIndex) + manageTunnelsRootVC?.tunnelsListVC?.tunnelMoved(from: oldIndex, to: newIndex) + } + + func tunnelRemoved(at index: Int) { + tunnelStatusObservers.remove(at: index) + + statusMenu?.removeTunnelMenuItem(at: index) + manageTunnelsRootVC?.tunnelsListVC?.tunnelRemoved(at: index) + } +} + +extension TunnelsTracker: TunnelsManagerActivationDelegate { + func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) { + if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsRootVC.view.window?.isVisible ?? false { + ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC) + } else { + ErrorPresenter.showErrorAlert(error: error, from: nil) + } + } + + func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) { + // Nothing to do + } + + func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) { + if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsRootVC.view.window?.isVisible ?? false { + ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC) + } else { + ErrorPresenter.showErrorAlert(error: error, from: nil) + } + } + + func tunnelActivationSucceeded(tunnel: TunnelContainer) { + // Nothing to do + } +}