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:
parent
6ba68b7f9a
commit
bc0a0d40dc
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -54,6 +58,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
|
||||||
|
|
||||||
|
@ -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,14 +516,6 @@ public class ConnectionService: Codable {
|
||||||
return baseConfiguration.existingLog(in: appGroup) ?? ""
|
return baseConfiguration.existingLog(in: appGroup) ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var vpnLastError: TunnelKitProvider.ProviderError? {
|
|
||||||
return baseConfiguration.lastError(in: appGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func clearVpnLastError() {
|
|
||||||
baseConfiguration.clearLastError(in: appGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func eraseVpnLog() {
|
public func eraseVpnLog() {
|
||||||
log.info("Erasing VPN log...")
|
log.info("Erasing VPN log...")
|
||||||
guard let url = baseConfiguration.urlForLog(in: appGroup) else {
|
guard let url = baseConfiguration.urlForLog(in: appGroup) else {
|
||||||
|
@ -517,4 +523,26 @@ public class ConnectionService: Codable {
|
||||||
}
|
}
|
||||||
try? FileManager.default.removeItem(at: url)
|
try? FileManager.default.removeItem(at: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var vpnLastError: TunnelKitProvider.ProviderError? {
|
||||||
|
return baseConfiguration.lastError(in: appGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func clearVpnLastError() {
|
||||||
|
baseConfiguration.clearLastError(in: appGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue