Observe tunnel data count periodically (5s)

Use Timer as KVO is not possible on App Group defaults.

Be tolerant about missing sections, return type is optional.

Also reword data count cell caption.
This commit is contained in:
Davide De Rosa 2019-03-29 11:17:50 +01:00
parent 6ba68b7f9a
commit bc0a0d40dc
8 changed files with 106 additions and 40 deletions

View File

@ -30,6 +30,7 @@ class PacketTunnelProvider: TunnelKitProvider {
appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)" appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)"
dnsTimeout = GroupConstants.VPN.dnsTimeout dnsTimeout = GroupConstants.VPN.dnsTimeout
logSeparator = GroupConstants.VPN.sessionMarker logSeparator = GroupConstants.VPN.sessionMarker
dataCountInterval = GroupConstants.VPN.dataCountInterval
super.startTunnel(options: options, completionHandler: completionHandler) super.startTunnel(options: options, completionHandler: completionHandler)
} }
} }

View File

@ -50,6 +50,8 @@ class ServiceViewController: UIViewController, TableModelHost {
private var shouldDeleteLogOnDisconnection = false private var shouldDeleteLogOnDisconnection = false
private var currentDataCount: (Int, Int)?
// MARK: Table // MARK: Table
var model: TableModel<SectionType, RowType> = TableModel() var model: TableModel<SectionType, RowType> = TableModel()
@ -103,6 +105,7 @@ class ServiceViewController: UIViewController, TableModelHost {
nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidChangeStatus, object: nil) nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidChangeStatus, object: nil)
nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidReinstall, object: nil) nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidReinstall, object: nil)
nc.addObserver(self, selector: #selector(intentDidUpdateService), name: .IntentDidUpdateService, object: nil) nc.addObserver(self, selector: #selector(intentDidUpdateService), name: .IntentDidUpdateService, object: nil)
nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount(_:)), name: .ConnectionServiceDidUpdateDataCount, object: nil)
// run this no matter what // run this no matter what
// XXX: convenient here vs AppDelegate for updating table // XXX: convenient here vs AppDelegate for updating table
@ -373,32 +376,32 @@ class ServiceViewController: UIViewController, TableModelHost {
} }
} }
private func displayDataCount() { // private func displayDataCount() {
guard vpn.isEnabled else { // guard vpn.isEnabled else {
let alert = Macros.alert( // let alert = Macros.alert(
L10n.Service.Cells.DataCount.caption, // L10n.Service.Cells.DataCount.caption,
L10n.Service.Alerts.DataCount.Messages.notAvailable // L10n.Service.Alerts.DataCount.Messages.notAvailable
) // )
alert.addCancelAction(L10n.Global.ok) // alert.addCancelAction(L10n.Global.ok)
present(alert, animated: true, completion: nil) // present(alert, animated: true, completion: nil)
return // return
} // }
//
vpn.requestBytesCount { // vpn.requestBytesCount {
let message: String // let message: String
if let count = $0 { // if let count = $0 {
message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1)) // message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1))
} else { // } else {
message = L10n.Service.Alerts.DataCount.Messages.notAvailable // message = L10n.Service.Alerts.DataCount.Messages.notAvailable
} // }
let alert = Macros.alert( // let alert = Macros.alert(
L10n.Service.Cells.DataCount.caption, // L10n.Service.Cells.DataCount.caption,
message // message
) // )
alert.addCancelAction(L10n.Global.ok) // alert.addCancelAction(L10n.Global.ok)
self.present(alert, animated: true, completion: nil) // self.present(alert, animated: true, completion: nil)
} // }
} // }
private func togglePrivateDataMasking(cell: ToggleTableViewCell) { private func togglePrivateDataMasking(cell: ToggleTableViewCell) {
let handler = { let handler = {
@ -468,6 +471,13 @@ class ServiceViewController: UIViewController, TableModelHost {
reloadModel() reloadModel()
updateViewsIfNeeded() updateViewsIfNeeded()
} }
@objc private func serviceDidUpdateDataCount(_ notification: Notification) {
guard let dataCount = notification.userInfo?[ConnectionService.NotificationKeys.dataCount] as? (Int, Int) else {
return
}
refreshDataCount(dataCount)
}
} }
// MARK: - // MARK: -
@ -551,6 +561,10 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
return model.indexPath(row: .connectionStatus, section: .vpn) return model.indexPath(row: .connectionStatus, section: .vpn)
} }
private var dataCountIndexPath: IndexPath? {
return model.indexPath(row: .dataCount, section: .diagnostics)
}
private var endpointIndexPath: IndexPath { private var endpointIndexPath: IndexPath {
guard let ip = model.indexPath(row: .endpoint, section: .configuration) else { guard let ip = model.indexPath(row: .endpoint, section: .configuration) else {
fatalError("Could not locate endpointIndexPath") fatalError("Could not locate endpointIndexPath")
@ -743,6 +757,13 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
case .dataCount: case .dataCount:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath) let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = L10n.Service.Cells.DataCount.caption cell.leftText = L10n.Service.Cells.DataCount.caption
if let count = currentDataCount, vpn.status == .connected {
cell.rightText = "\(count.0)/\(count.1)"
} else {
cell.rightText = nil
}
cell.accessoryType = .none
cell.isTappable = false
return cell return cell
case .debugLog: case .debugLog:
@ -859,8 +880,8 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
case .testConnectivity: case .testConnectivity:
testInternetConnectivity() testInternetConnectivity()
case .dataCount: // case .dataCount:
displayDataCount() // displayDataCount()
case .debugLog: case .debugLog:
perform(segue: StoryboardSegue.Main.debugLogSegueIdentifier, sender: cell) perform(segue: StoryboardSegue.Main.debugLogSegueIdentifier, sender: cell)
@ -1018,10 +1039,23 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
guard service.isActiveProfile(profile) else { guard service.isActiveProfile(profile) else {
return return
} }
var ips: [IndexPath] = []
guard let statusIndexPath = statusIndexPath else { guard let statusIndexPath = statusIndexPath else {
return return
} }
tableView.reloadRows(at: [statusIndexPath], with: .none) ips.append(statusIndexPath)
if let dataCountIndexPath = dataCountIndexPath {
ips.append(dataCountIndexPath)
}
tableView.reloadRows(at: ips, with: .none)
}
private func refreshDataCount(_ dataCount: (Int, Int)?) {
currentDataCount = dataCount
guard let dataCountIndexPath = dataCountIndexPath else {
return
}
tableView.reloadRows(at: [dataCountIndexPath], with: .none)
} }
func reloadSelectedRow(andRowAt indexPath: IndexPath? = nil) { func reloadSelectedRow(andRowAt indexPath: IndexPath? = nil) {

View File

@ -93,7 +93,7 @@ class TableModel<S: Hashable, R: Equatable> {
func indexPath(row rowObject: R, section sectionObject: S) -> IndexPath? { func indexPath(row rowObject: R, section sectionObject: S) -> IndexPath? {
guard let sectionIndex = sections.index(of: sectionObject) else { guard let sectionIndex = sections.index(of: sectionObject) else {
fatalError("Missing section: \(sectionObject)") return nil
} }
guard let row = rowsBySection[sectionObject]?.index(of: rowObject) else { guard let row = rowsBySection[sectionObject]?.index(of: rowObject) else {
return nil return nil

View File

@ -107,7 +107,7 @@
"service.cells.trusted_add_wifi.caption" = "Add current Wi-Fi"; "service.cells.trusted_add_wifi.caption" = "Add current Wi-Fi";
"service.cells.trusted_policy.caption" = "Trust disables VPN"; "service.cells.trusted_policy.caption" = "Trust disables VPN";
"service.cells.test_connectivity.caption" = "Test connectivity"; "service.cells.test_connectivity.caption" = "Test connectivity";
"service.cells.data_count.caption" = "Exchanged bytes count"; "service.cells.data_count.caption" = "Exchanged data count";
"service.cells.debug_log.caption" = "Debug log"; "service.cells.debug_log.caption" = "Debug log";
"service.cells.masks_private_data.caption" = "Mask network data"; "service.cells.masks_private_data.caption" = "Mask network data";
"service.cells.report_issue.caption" = "Report connectivity issue"; "service.cells.report_issue.caption" = "Report connectivity issue";

View File

@ -79,5 +79,7 @@ public class GroupConstants {
public static let dnsTimeout = 5000 public static let dnsTimeout = 5000
public static let sessionMarker = "--- EOF ---" public static let sessionMarker = "--- EOF ---"
public static let dataCountInterval = 5000
} }
} }

View File

@ -42,6 +42,10 @@ public protocol ConnectionServiceDelegate: class {
func connectionService(didActivate profile: ConnectionProfile) func connectionService(didActivate profile: ConnectionProfile)
} }
public extension Notification.Name {
static let ConnectionServiceDidUpdateDataCount = Notification.Name("ConnectionServiceDidUpdateDataCount")
}
public class ConnectionService: Codable { public class ConnectionService: Codable {
public enum CodingKeys: String, CodingKey { public enum CodingKeys: String, CodingKey {
case build case build
@ -55,6 +59,10 @@ public class ConnectionService: Codable {
case preferences case preferences
} }
public struct NotificationKeys {
public static let dataCount = "DataCount"
}
public var directory: String? = nil public var directory: String? = nil
public var rootURL: URL { public var rootURL: URL {
@ -85,6 +93,8 @@ public class ConnectionService: Codable {
private var cache: [ProfileKey: ConnectionProfile] private var cache: [ProfileKey: ConnectionProfile]
private var dataCountObserver: Timer?
public private(set) var activeProfileKey: ProfileKey? { public private(set) var activeProfileKey: ProfileKey? {
willSet { willSet {
if let oldProfile = activeProfile { if let oldProfile = activeProfile {
@ -130,6 +140,10 @@ public class ConnectionService: Codable {
cache = [:] cache = [:]
} }
deinit {
dataCountObserver?.invalidate()
}
// MARK: Codable // MARK: Codable
public required init(from decoder: Decoder) throws { public required init(from decoder: Decoder) throws {
@ -502,6 +516,14 @@ public class ConnectionService: Codable {
return baseConfiguration.existingLog(in: appGroup) ?? "" return baseConfiguration.existingLog(in: appGroup) ?? ""
} }
public func eraseVpnLog() {
log.info("Erasing VPN log...")
guard let url = baseConfiguration.urlForLog(in: appGroup) else {
return
}
try? FileManager.default.removeItem(at: url)
}
public var vpnLastError: TunnelKitProvider.ProviderError? { public var vpnLastError: TunnelKitProvider.ProviderError? {
return baseConfiguration.lastError(in: appGroup) return baseConfiguration.lastError(in: appGroup)
} }
@ -510,11 +532,17 @@ public class ConnectionService: Codable {
baseConfiguration.clearLastError(in: appGroup) baseConfiguration.clearLastError(in: appGroup)
} }
public func eraseVpnLog() { public func observeVPNDataCount(interval: TimeInterval) {
log.info("Erasing VPN log...") dataCountObserver?.invalidate()
guard let url = baseConfiguration.urlForLog(in: appGroup) else { dataCountObserver = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [weak self] (_) in
return guard let dataCount = self?.vpnDataCount else {
} return
try? FileManager.default.removeItem(at: url) }
NotificationCenter.default.post(name: .ConnectionServiceDidUpdateDataCount, object: nil, userInfo: [NotificationKeys.dataCount: dataCount])
})
}
public var vpnDataCount: (Int, Int)? {
return baseConfiguration.dataCount(in: appGroup)
} }
} }

View File

@ -107,6 +107,7 @@ public class TransientStore {
// _ = service.addProfile(HostConnectionProfile(title: "vps"), credentials: Credentials(username: "foo", password: "bar")) // _ = service.addProfile(HostConnectionProfile(title: "vps"), credentials: Credentials(username: "foo", password: "bar"))
// service.activateProfile(service.profiles.first!) // service.activateProfile(service.profiles.first!)
} }
service.observeVPNDataCount(interval: TimeInterval(GroupConstants.VPN.dataCountInterval) / 1000.0)
} }
public func serialize(withProfiles: Bool) { public func serialize(withProfiles: Bool) {

View File

@ -541,7 +541,7 @@ public enum L10n {
public static let caption = L10n.tr("Localizable", "service.cells.connection_status.caption") public static let caption = L10n.tr("Localizable", "service.cells.connection_status.caption")
} }
public enum DataCount { public enum DataCount {
/// Exchanged bytes count /// Exchanged data count
public static let caption = L10n.tr("Localizable", "service.cells.data_count.caption") public static let caption = L10n.tr("Localizable", "service.cells.data_count.caption")
} }
public enum DebugLog { public enum DebugLog {