2018-05-23 19:49:10 +00:00
|
|
|
//
|
2018-05-26 07:17:36 +00:00
|
|
|
// TunnelsTableViewController.swift
|
2018-05-24 18:13:21 +00:00
|
|
|
// WireGuard
|
2018-05-23 19:49:10 +00:00
|
|
|
//
|
|
|
|
// Created by Jeroen Leenarts on 23-05-18.
|
2018-07-15 09:55:41 +00:00
|
|
|
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
|
2018-05-23 19:49:10 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
|
|
|
import CoreData
|
|
|
|
import BNRCoreDataStack
|
2018-08-21 20:27:26 +00:00
|
|
|
import NetworkExtension
|
2018-05-23 19:49:10 +00:00
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
protocol TunnelsTableViewControllerDelegate: class {
|
2018-09-02 20:31:39 +00:00
|
|
|
func exportTunnels(tunnelsTableViewController: TunnelsTableViewController, barButtonItem: UIBarButtonItem)
|
2018-05-26 07:17:36 +00:00
|
|
|
func addProvider(tunnelsTableViewController: TunnelsTableViewController)
|
2018-08-08 05:04:42 +00:00
|
|
|
func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
2018-08-21 20:27:26 +00:00
|
|
|
func disconnect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
2018-05-26 07:17:36 +00:00
|
|
|
func configure(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
|
|
|
func delete(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController)
|
2018-08-21 20:27:26 +00:00
|
|
|
func status(for tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) -> NEVPNStatus
|
2018-05-23 19:49:10 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
class TunnelsTableViewController: UITableViewController {
|
|
|
|
weak var delegate: TunnelsTableViewControllerDelegate?
|
2018-05-23 19:49:10 +00:00
|
|
|
|
|
|
|
var viewContext: NSManagedObjectContext!
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
private lazy var fetchedResultsController: FetchedResultsController<Tunnel> = {
|
|
|
|
let fetchRequest = NSFetchRequest<Tunnel>()
|
|
|
|
fetchRequest.entity = Tunnel.entity()
|
2018-05-24 18:14:01 +00:00
|
|
|
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
|
2018-05-26 07:17:36 +00:00
|
|
|
let frc = FetchedResultsController<Tunnel>(fetchRequest: fetchRequest,
|
2018-05-24 18:14:01 +00:00
|
|
|
managedObjectContext: viewContext)
|
|
|
|
frc.setDelegate(self.frcDelegate)
|
|
|
|
return frc
|
|
|
|
}()
|
|
|
|
|
2018-08-21 20:27:26 +00:00
|
|
|
public func updateStatus(for tunnelIdentifier: String) {
|
|
|
|
viewContext.perform {
|
|
|
|
let tunnel = try? Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
|
|
|
if let tunnel = tunnel {
|
|
|
|
if let indexPath = self.fetchedResultsController.indexPathForObject(tunnel!) {
|
|
|
|
self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
private lazy var frcDelegate: TunnelFetchedResultsControllerDelegate = { // swiftlint:disable:this weak_delegate
|
|
|
|
return TunnelFetchedResultsControllerDelegate(tableView: self.tableView)
|
2018-05-24 18:14:01 +00:00
|
|
|
}()
|
|
|
|
|
2018-05-23 19:49:10 +00:00
|
|
|
override func viewDidLoad() {
|
|
|
|
super.viewDidLoad()
|
2018-05-24 18:14:01 +00:00
|
|
|
do {
|
|
|
|
try fetchedResultsController.performFetch()
|
|
|
|
} catch {
|
|
|
|
print("Failed to fetch objects: \(error)")
|
|
|
|
}
|
2018-08-08 05:04:42 +00:00
|
|
|
|
|
|
|
// Get rid of seperator lines in table.
|
|
|
|
tableView.tableFooterView = UIView(frame: CGRect.zero)
|
2018-05-24 18:14:01 +00:00
|
|
|
}
|
|
|
|
|
2018-09-02 20:31:39 +00:00
|
|
|
@IBAction func exportTunnels(_ sender: UIBarButtonItem) {
|
|
|
|
delegate?.exportTunnels(tunnelsTableViewController: self, barButtonItem: sender)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func addProvider(_ sender: UIBarButtonItem) {
|
2018-05-26 07:17:36 +00:00
|
|
|
delegate?.addProvider(tunnelsTableViewController: self)
|
2018-05-24 18:14:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
|
|
return fetchedResultsController.sections?[0].objects.count ?? 0
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
2018-05-26 07:17:36 +00:00
|
|
|
let cell = tableView.dequeueReusableCell(type: TunnelTableViewCell.self, for: indexPath)
|
2018-08-21 20:27:26 +00:00
|
|
|
cell.delegate = self
|
2018-05-24 18:14:01 +00:00
|
|
|
|
|
|
|
guard let sections = fetchedResultsController.sections else {
|
|
|
|
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
let section = sections[indexPath.section]
|
2018-05-26 07:17:36 +00:00
|
|
|
let tunnel = section.objects[indexPath.row]
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-08-21 20:27:26 +00:00
|
|
|
cell.configure(tunnel: tunnel, status: delegate?.status(for: tunnel, tunnelsTableViewController: self) ?? .invalid)
|
2018-05-24 18:14:01 +00:00
|
|
|
|
|
|
|
return cell
|
2018-05-23 19:49:10 +00:00
|
|
|
}
|
|
|
|
|
2018-05-24 18:14:01 +00:00
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
|
|
guard let sections = fetchedResultsController.sections else {
|
|
|
|
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
let section = sections[indexPath.section]
|
2018-05-26 07:17:36 +00:00
|
|
|
let tunnel = section.objects[indexPath.row]
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-08-21 18:04:14 +00:00
|
|
|
delegate?.configure(tunnel: tunnel, tunnelsTableViewController: self)
|
2018-05-24 18:14:01 +00:00
|
|
|
|
|
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
2018-05-23 19:49:10 +00:00
|
|
|
}
|
|
|
|
|
2018-05-24 18:14:01 +00:00
|
|
|
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
|
|
|
guard let sections = fetchedResultsController.sections else {
|
|
|
|
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
let section = sections[indexPath.section]
|
2018-05-26 07:17:36 +00:00
|
|
|
let tunnel = section.objects[indexPath.row]
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
delegate?.configure(tunnel: tunnel, tunnelsTableViewController: self)
|
2018-05-24 18:14:01 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
|
|
|
|
if editingStyle == .delete {
|
|
|
|
|
|
|
|
guard let sections = fetchedResultsController.sections else {
|
|
|
|
fatalError("FetchedResultsController \(fetchedResultsController) should have sections, but found nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
let section = sections[indexPath.section]
|
2018-05-26 07:17:36 +00:00
|
|
|
let tunnel = section.objects[indexPath.row]
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
delegate?.delete(tunnel: tunnel, tunnelsTableViewController: self)
|
2018-05-24 18:14:01 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-23 19:49:10 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 20:27:26 +00:00
|
|
|
extension TunnelsTableViewController: TunnelTableViewCellDelegate {
|
|
|
|
func connect(tunnelIdentifier: String) {
|
|
|
|
let tunnel = try? Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
|
|
|
if let tunnel = tunnel {
|
|
|
|
self.delegate?.connect(tunnel: tunnel!, tunnelsTableViewController: self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func disconnect(tunnelIdentifier: String) {
|
|
|
|
let tunnel = try? Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier))
|
|
|
|
if let tunnel = tunnel {
|
|
|
|
self.delegate?.disconnect(tunnel: tunnel!, tunnelsTableViewController: self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
extension TunnelsTableViewController: Identifyable {}
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
class TunnelFetchedResultsControllerDelegate: NSObject, FetchedResultsControllerDelegate {
|
2018-05-24 18:14:01 +00:00
|
|
|
|
|
|
|
private weak var tableView: UITableView?
|
|
|
|
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
init(tableView: UITableView) {
|
|
|
|
self.tableView = tableView
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
func fetchedResultsControllerDidPerformFetch(_ controller: FetchedResultsController<Tunnel>) {
|
2018-05-24 18:14:01 +00:00
|
|
|
tableView?.reloadData()
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
func fetchedResultsControllerWillChangeContent(_ controller: FetchedResultsController<Tunnel>) {
|
2018-05-24 18:14:01 +00:00
|
|
|
tableView?.beginUpdates()
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
func fetchedResultsControllerDidChangeContent(_ controller: FetchedResultsController<Tunnel>) {
|
2018-05-24 18:14:01 +00:00
|
|
|
tableView?.endUpdates()
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
func fetchedResultsController(_ controller: FetchedResultsController<Tunnel>, didChangeObject change: FetchedResultsObjectChange<Tunnel>) {
|
2018-05-24 18:14:01 +00:00
|
|
|
guard let tableView = tableView else { return }
|
|
|
|
switch change {
|
|
|
|
case let .insert(_, indexPath):
|
|
|
|
tableView.insertRows(at: [indexPath], with: .automatic)
|
|
|
|
|
|
|
|
case let .delete(_, indexPath):
|
|
|
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
|
|
|
|
|
|
|
case let .move(_, fromIndexPath, toIndexPath):
|
|
|
|
tableView.moveRow(at: fromIndexPath, to: toIndexPath)
|
|
|
|
|
|
|
|
case let .update(_, indexPath):
|
|
|
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
func fetchedResultsController(_ controller: FetchedResultsController<Tunnel>, didChangeSection change: FetchedResultsSectionChange<Tunnel>) {
|
2018-05-24 18:14:01 +00:00
|
|
|
guard let tableView = tableView else { return }
|
|
|
|
switch change {
|
|
|
|
case let .insert(_, index):
|
|
|
|
tableView.insertSections(IndexSet(integer: index), with: .automatic)
|
|
|
|
|
|
|
|
case let .delete(_, index):
|
|
|
|
tableView.deleteSections(IndexSet(integer: index), with: .automatic)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-21 20:27:26 +00:00
|
|
|
protocol TunnelTableViewCellDelegate: class {
|
|
|
|
func connect(tunnelIdentifier: String)
|
|
|
|
func disconnect(tunnelIdentifier: String)
|
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
class TunnelTableViewCell: UITableViewCell {
|
2018-05-24 18:14:01 +00:00
|
|
|
|
2018-08-21 20:27:26 +00:00
|
|
|
@IBOutlet weak var tunnelTitleLabel: UILabel!
|
|
|
|
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
2018-08-22 18:34:08 +00:00
|
|
|
@IBOutlet weak var tunnelSwitch: UISwitch!
|
2018-08-21 20:27:26 +00:00
|
|
|
|
|
|
|
weak var delegate: TunnelTableViewCellDelegate?
|
|
|
|
private var tunnelIdentifier: String?
|
|
|
|
|
|
|
|
@IBAction func tunnelSwitchChanged(_ sender: Any) {
|
2018-08-22 18:34:08 +00:00
|
|
|
tunnelSwitch.isEnabled = false
|
2018-08-21 20:27:26 +00:00
|
|
|
guard let tunnelIdentifier = tunnelIdentifier else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if tunnelSwitch.isOn {
|
|
|
|
delegate?.connect(tunnelIdentifier: tunnelIdentifier)
|
|
|
|
} else {
|
|
|
|
delegate?.disconnect(tunnelIdentifier: tunnelIdentifier)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func configure(tunnel: Tunnel, status: NEVPNStatus) {
|
|
|
|
self.tunnelTitleLabel?.text = tunnel.title
|
|
|
|
tunnelIdentifier = tunnel.tunnelIdentifier
|
2018-08-22 18:34:08 +00:00
|
|
|
|
|
|
|
if status == .connecting || status == .disconnecting || status == .reasserting {
|
2018-08-21 20:27:26 +00:00
|
|
|
activityIndicator.startAnimating()
|
2018-08-22 18:34:08 +00:00
|
|
|
tunnelSwitch.isHidden = true
|
|
|
|
} else {
|
2018-08-21 20:27:26 +00:00
|
|
|
activityIndicator.stopAnimating()
|
2018-08-22 18:34:08 +00:00
|
|
|
tunnelSwitch.isHidden = false
|
2018-08-21 20:27:26 +00:00
|
|
|
}
|
2018-08-22 18:34:08 +00:00
|
|
|
|
|
|
|
tunnelSwitch.isOn = status == .connected
|
|
|
|
tunnelSwitch.onTintColor = status == .invalid || status == .reasserting ? .gray : .green
|
|
|
|
tunnelSwitch.isEnabled = true
|
2018-08-21 20:27:26 +00:00
|
|
|
}
|
2018-05-24 18:14:01 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 07:17:36 +00:00
|
|
|
extension TunnelTableViewCell: Identifyable {}
|