macOS: Apply runtime configuration by diff-ing

And apply the diff on the tableView as insertRows/removeRows.

Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
Roopesh Chander 2019-02-04 18:25:32 +05:30
parent 8a6a60482c
commit 80de2ac6ac
1 changed files with 88 additions and 15 deletions

View File

@ -30,12 +30,12 @@ class TunnelDetailTableViewController: NSViewController {
}
}
let interfaceFields: [TunnelViewModel.InterfaceField] = [
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
.name, .publicKey, .addresses,
.listenPort, .mtu, .dns
]
let peerFields: [TunnelViewModel.PeerField] = [
static let peerFields: [TunnelViewModel.PeerField] = [
.publicKey, .preSharedKey, .endpoint,
.allowedIPs, .persistentKeepAlive,
.rxBytes, .txBytes, .lastHandshakeTime
@ -80,11 +80,12 @@ class TunnelDetailTableViewController: NSViewController {
let tunnel: TunnelContainer
var tunnelViewModel: TunnelViewModel {
didSet { updateTableViewModelRowsBySection() }
}
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]() {
didSet { updateTableViewModelRows() }
didSet {
updateTableViewModelRowsBySection()
updateTableViewModelRows()
}
}
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
private var tableViewModelRows = [TableViewModelRow]()
private var statusObservationToken: AnyObject?
@ -96,6 +97,7 @@ class TunnelDetailTableViewController: NSViewController {
self.tunnel = tunnel
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
super.init(nibName: nil, bundle: nil)
updateTableViewModelRowsBySection()
updateTableViewModelRows()
updateStatus()
statusObservationToken = tunnel.observe(\TunnelContainer.status) { [weak self] _, _ in
@ -167,7 +169,7 @@ class TunnelDetailTableViewController: NSViewController {
var modelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
for field in interfaceFields {
for field in TunnelDetailTableViewController.interfaceFields {
interfaceSection.append((isVisible: !tunnelViewModel.interfaceData[field].isEmpty, modelRow: .interfaceFieldRow(field)))
}
interfaceSection.append((isVisible: true, modelRow: .spacerRow))
@ -175,7 +177,7 @@ class TunnelDetailTableViewController: NSViewController {
for peerData in tunnelViewModel.peersData {
var peerSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
for field in peerFields {
for field in TunnelDetailTableViewController.peerFields {
peerSection.append((isVisible: !peerData[field].isEmpty, modelRow: .peerFieldRow(peer: peerData, field: field)))
}
peerSection.append((isVisible: true, modelRow: .spacerRow))
@ -219,6 +221,7 @@ class TunnelDetailTableViewController: NSViewController {
if tunnel.status == .active {
startUpdatingRuntimeConfiguration()
} else if tunnel.status == .inactive {
reloadRuntimeConfiguration()
stopUpdatingRuntimeConfiguration()
}
}
@ -255,14 +258,84 @@ class TunnelDetailTableViewController: NSViewController {
stopUpdatingRuntimeConfiguration()
}
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
func sectionChanged<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], tableView: NSTableView, rowOffset: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
for (index, field) in fields.enumerated() {
guard let change = changes[field] else { continue }
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
switch change {
case .added:
tableView.insertRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
modelRowsInSection[index].isVisible = true
case .removed:
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
modelRowsInSection[index].isVisible = false
case .modified:
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
tableView.insertRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
}
}
}
var isChanged = false
let changeHandlers = TunnelViewModel.ChangeHandlers(
interfaceChanged: { [weak self] changes in
guard let self = self else { return }
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, modelRowsInSection: &self.tableViewModelRowsBySection[0],
tableView: self.tableView, rowOffset: 0, changes: changes)
isChanged = true
},
peerChangedAt: { [weak self] peerIndex, changes in
guard let self = self else { return }
let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
sectionChanged(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex],
tableView: self.tableView, rowOffset: rowOffset, changes: changes)
isChanged = true
},
peersRemovedAt: { [weak self] peerIndices in
guard let self = self else { return }
for peerIndex in peerIndices {
let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
self.tableViewModelRowsBySection.remove(at: sectionIndex)
}
isChanged = true
},
peersInsertedAt: { [weak self] peerIndices in
guard let self = self else { return }
for peerIndex in peerIndices {
let peerData = self.tunnelViewModel.peersData[peerIndex]
let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
var modelRowsInSection: [(isVisible: Bool, modelRow: TableViewModelRow)] = TunnelDetailTableViewController.peerFields.map {
(isVisible: !peerData[$0].isEmpty, modelRow: .peerFieldRow(peer: peerData, field: $0))
}
modelRowsInSection.append((isVisible: true, modelRow: .spacerRow))
let count = modelRowsInSection.filter { $0.isVisible }.count
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
}
isChanged = true
}
)
tableView.beginUpdates()
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
if isChanged {
updateTableViewModelRows()
}
tableView.endUpdates()
}
private func reloadRuntimeConfiguration() {
tunnel.getRuntimeTunnelConfiguration(completionHandler: {
guard let tunnelConfiguration = $0 else { return }
self.tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
// TODO(roopc): make this not loose scroll position
self.tableView.reloadData()
self.tunnelEditVC = nil
})
tunnel.getRuntimeTunnelConfiguration { [weak self] tunnelConfiguration in
guard let tunnelConfiguration = tunnelConfiguration else { return }
self?.applyTunnelConfiguration(tunnelConfiguration: tunnelConfiguration)
}
}
func startUpdatingRuntimeConfiguration() {