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)"
dnsTimeout = GroupConstants.VPN.dnsTimeout
logSeparator = GroupConstants.VPN.sessionMarker
dataCountInterval = GroupConstants.VPN.dataCountInterval
super.startTunnel(options: options, completionHandler: completionHandler)
}
}

View File

@ -50,6 +50,8 @@ class ServiceViewController: UIViewController, TableModelHost {
private var shouldDeleteLogOnDisconnection = false
private var currentDataCount: (Int, Int)?
// MARK: Table
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: .VPNDidReinstall, 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
// XXX: convenient here vs AppDelegate for updating table
@ -373,32 +376,32 @@ class ServiceViewController: UIViewController, TableModelHost {
}
}
private func displayDataCount() {
guard vpn.isEnabled else {
let alert = Macros.alert(
L10n.Service.Cells.DataCount.caption,
L10n.Service.Alerts.DataCount.Messages.notAvailable
)
alert.addCancelAction(L10n.Global.ok)
present(alert, animated: true, completion: nil)
return
}
vpn.requestBytesCount {
let message: String
if let count = $0 {
message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1))
} else {
message = L10n.Service.Alerts.DataCount.Messages.notAvailable
}
let alert = Macros.alert(
L10n.Service.Cells.DataCount.caption,
message
)
alert.addCancelAction(L10n.Global.ok)
self.present(alert, animated: true, completion: nil)
}
}
// private func displayDataCount() {
// guard vpn.isEnabled else {
// let alert = Macros.alert(
// L10n.Service.Cells.DataCount.caption,
// L10n.Service.Alerts.DataCount.Messages.notAvailable
// )
// alert.addCancelAction(L10n.Global.ok)
// present(alert, animated: true, completion: nil)
// return
// }
//
// vpn.requestBytesCount {
// let message: String
// if let count = $0 {
// message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1))
// } else {
// message = L10n.Service.Alerts.DataCount.Messages.notAvailable
// }
// let alert = Macros.alert(
// L10n.Service.Cells.DataCount.caption,
// message
// )
// alert.addCancelAction(L10n.Global.ok)
// self.present(alert, animated: true, completion: nil)
// }
// }
private func togglePrivateDataMasking(cell: ToggleTableViewCell) {
let handler = {
@ -468,6 +471,13 @@ class ServiceViewController: UIViewController, TableModelHost {
reloadModel()
updateViewsIfNeeded()
}
@objc private func serviceDidUpdateDataCount(_ notification: Notification) {
guard let dataCount = notification.userInfo?[ConnectionService.NotificationKeys.dataCount] as? (Int, Int) else {
return
}
refreshDataCount(dataCount)
}
}
// MARK: -
@ -551,6 +561,10 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
return model.indexPath(row: .connectionStatus, section: .vpn)
}
private var dataCountIndexPath: IndexPath? {
return model.indexPath(row: .dataCount, section: .diagnostics)
}
private var endpointIndexPath: IndexPath {
guard let ip = model.indexPath(row: .endpoint, section: .configuration) else {
fatalError("Could not locate endpointIndexPath")
@ -743,6 +757,13 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
case .dataCount:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
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
case .debugLog:
@ -859,8 +880,8 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
case .testConnectivity:
testInternetConnectivity()
case .dataCount:
displayDataCount()
// case .dataCount:
// displayDataCount()
case .debugLog:
perform(segue: StoryboardSegue.Main.debugLogSegueIdentifier, sender: cell)
@ -1018,10 +1039,23 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
guard service.isActiveProfile(profile) else {
return
}
var ips: [IndexPath] = []
guard let statusIndexPath = statusIndexPath else {
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) {

View File

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

View File

@ -107,7 +107,7 @@
"service.cells.trusted_add_wifi.caption" = "Add current Wi-Fi";
"service.cells.trusted_policy.caption" = "Trust disables VPN";
"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.masks_private_data.caption" = "Mask network data";
"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 sessionMarker = "--- EOF ---"
public static let dataCountInterval = 5000
}
}

View File

@ -42,6 +42,10 @@ public protocol ConnectionServiceDelegate: class {
func connectionService(didActivate profile: ConnectionProfile)
}
public extension Notification.Name {
static let ConnectionServiceDidUpdateDataCount = Notification.Name("ConnectionServiceDidUpdateDataCount")
}
public class ConnectionService: Codable {
public enum CodingKeys: String, CodingKey {
case build
@ -55,6 +59,10 @@ public class ConnectionService: Codable {
case preferences
}
public struct NotificationKeys {
public static let dataCount = "DataCount"
}
public var directory: String? = nil
public var rootURL: URL {
@ -85,6 +93,8 @@ public class ConnectionService: Codable {
private var cache: [ProfileKey: ConnectionProfile]
private var dataCountObserver: Timer?
public private(set) var activeProfileKey: ProfileKey? {
willSet {
if let oldProfile = activeProfile {
@ -130,6 +140,10 @@ public class ConnectionService: Codable {
cache = [:]
}
deinit {
dataCountObserver?.invalidate()
}
// MARK: Codable
public required init(from decoder: Decoder) throws {
@ -502,6 +516,14 @@ public class ConnectionService: Codable {
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? {
return baseConfiguration.lastError(in: appGroup)
}
@ -510,11 +532,17 @@ public class ConnectionService: Codable {
baseConfiguration.clearLastError(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 func observeVPNDataCount(interval: TimeInterval) {
dataCountObserver?.invalidate()
dataCountObserver = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [weak self] (_) in
guard let dataCount = self?.vpnDataCount else {
return
}
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.activateProfile(service.profiles.first!)
}
service.observeVPNDataCount(interval: TimeInterval(GroupConstants.VPN.dataCountInterval) / 1000.0)
}
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 enum DataCount {
/// Exchanged bytes count
/// Exchanged data count
public static let caption = L10n.tr("Localizable", "service.cells.data_count.caption")
}
public enum DebugLog {