Refactoring I.
- Revamp whole DB to Display flow - Filter Pipeline, arbitrary filtering and sorting - Binary tree arrays for faster lookup & manipulation - DB: introducing custom functions - DB scheme: split req into heap & cache - cache written by GlassVPN only - heap written by Main App only - Introducing DB separation: DBCore, DBCommon, DBAppOnly - Introducing DB data sources: TestDataSource, GroupedDomainDataSource, RecordingsDB, DomainFilter - Background sync: Move entries from cache to heap and notify all observers - GlassVPN: Binary tree filter lookup - GlassVPN: Reusing prepared statement
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
import UIKit
|
||||
|
||||
class TVCDomains: UITableViewController, IncrementalDataSourceUpdate, UISearchBarDelegate {
|
||||
class TVCDomains: UITableViewController, UISearchBarDelegate, FilterPipelineDelegate {
|
||||
|
||||
lazy var source = GroupedDomainDataSource(withDelegate: self, parent: nil)
|
||||
|
||||
internal var dataSource: [GroupedDomain] = []
|
||||
private func dataSource(at: Int) -> GroupedDomain {
|
||||
dataSource[(searchActive ? searchIndices[at] : at)]
|
||||
}
|
||||
private var searchActive: Bool = false
|
||||
private var searchIndices: [Int] = []
|
||||
private var searchTerm: String?
|
||||
private let searchBar: UISearchBar = {
|
||||
let x = UISearchBar(frame: CGRect.init(x: 0, y: 0, width: 20, height: 10))
|
||||
@@ -22,48 +19,46 @@ class TVCDomains: UITableViewController, IncrementalDataSourceUpdate, UISearchBa
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if #available(iOS 10.0, *) {
|
||||
tableView.refreshControl = UIRefreshControl(call: #selector(reloadDataSource), on: self)
|
||||
}
|
||||
NotifyLogHistoryReset.observe(call: #selector(reloadDataSource), on: self)
|
||||
reloadDataSource()
|
||||
DBWrp.dataA_delegate = self
|
||||
searchBar.delegate = self
|
||||
NotifyDateFilterChanged.observe(call: #selector(dateFilterChanged), on: self)
|
||||
dateFilterChanged()
|
||||
NotifyDateFilterChanged.observe(call: #selector(didChangeDateFilter), on: self)
|
||||
didChangeDateFilter()
|
||||
}
|
||||
|
||||
@objc func reloadDataSource() {
|
||||
dataSource = DBWrp.listOfDomains()
|
||||
if searchActive {
|
||||
searchBar(searchBar, textDidChange: "")
|
||||
} else {
|
||||
tableView.reloadData()
|
||||
private var didLoadAlready = false
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
if !didLoadAlready {
|
||||
didLoadAlready = true
|
||||
source.reloadFromSource()
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
(segue.destination as? TVCHosts)?.parentDomain = dataSource(at: index).domain
|
||||
(segue.destination as? TVCHosts)?.parentDomain = source[index].domain
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
searchActive ? searchIndices.count : dataSource.count
|
||||
}
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { source.numberOfRows }
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "DomainCell")!
|
||||
let entry = dataSource(at: indexPath.row)
|
||||
let entry = source[indexPath.row]
|
||||
cell.textLabel?.text = entry.domain
|
||||
cell.detailTextLabel?.text = entry.detailCellText
|
||||
cell.imageView?.image = entry.options?.tableRowImage()
|
||||
return cell
|
||||
}
|
||||
|
||||
func rowNeedsUpdate(_ row: Int) {
|
||||
let entry = source[row]
|
||||
let cell = tableView.cellForRow(at: IndexPath(row: row))
|
||||
cell?.detailTextLabel?.text = entry.detailCellText
|
||||
cell?.imageView?.image = entry.options?.tableRowImage()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Search
|
||||
|
||||
@@ -77,55 +72,31 @@ class TVCDomains: UITableViewController, IncrementalDataSourceUpdate, UISearchBa
|
||||
|
||||
private func setSearch(hidden: Bool) {
|
||||
searchActive = !hidden
|
||||
searchIndices = []
|
||||
searchTerm = nil
|
||||
searchBar.text = nil
|
||||
tableView.tableHeaderView = hidden ? nil : searchBar
|
||||
if !hidden { searchBar.becomeFirstResponder() }
|
||||
if searchActive {
|
||||
source.pipeline.addFilter("search") {
|
||||
$0.domain.lowercased().contains(self.searchTerm ?? "")
|
||||
}
|
||||
searchBar.becomeFirstResponder()
|
||||
} else {
|
||||
source.pipeline.removeFilter(withId: "search")
|
||||
}
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
||||
perform(#selector(performSearch), with: nil, afterDelay: 0.3)
|
||||
perform(#selector(performSearch), with: nil, afterDelay: 0.2)
|
||||
}
|
||||
|
||||
@objc private func performSearch() {
|
||||
searchTerm = searchBar.text?.lowercased() ?? ""
|
||||
searchIndices = dataSource.enumerated().compactMap {
|
||||
if $1.domain.lowercased().contains(searchTerm!) { return $0 }
|
||||
return nil
|
||||
}
|
||||
source.pipeline.reloadFilter(withId: "search")
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func shouldLiveUpdateIncrementalDataSource() -> Bool { !searchActive }
|
||||
|
||||
func didUpdateIncrementalDataSource(_ operation: IncrementalDataSourceUpdateOperation, row: Int, moveTo: Int) {
|
||||
guard searchActive else {
|
||||
return
|
||||
}
|
||||
switch operation {
|
||||
case .ReloadTable:
|
||||
DispatchQueue.main.sync { tableView.reloadData() }
|
||||
case .Insert:
|
||||
if dataSource[row].domain.lowercased().contains(searchTerm ?? "") {
|
||||
searchIndices.insert(row, at: 0)
|
||||
DispatchQueue.main.sync { tableView.safeInsertRow(0, with: .left) }
|
||||
}
|
||||
case .Delete:
|
||||
if let idx = searchIndices.firstIndex(of: row) {
|
||||
searchIndices.remove(at: idx)
|
||||
DispatchQueue.main.sync { tableView.safeDeleteRow(idx) }
|
||||
}
|
||||
case .Update, .Move:
|
||||
if let idx = searchIndices.firstIndex(of: row) {
|
||||
if operation == .Move { searchIndices[idx] = moveTo }
|
||||
DispatchQueue.main.sync { tableView.safeReloadRow(idx) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Filter
|
||||
|
||||
@@ -138,7 +109,7 @@ class TVCDomains: UITableViewController, IncrementalDataSourceUpdate, UISearchBa
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
@objc private func dateFilterChanged() {
|
||||
@objc private func didChangeDateFilter() {
|
||||
switch Pref.DateFilter.Kind {
|
||||
case .ABRange: // read start/end time
|
||||
self.filterButtonDetail.title = "A – B"
|
||||
|
||||
@@ -12,14 +12,55 @@ class TVCHostDetails: UITableViewController {
|
||||
tableView.refreshControl = UIRefreshControl(call: #selector(reloadDataSource), on: self)
|
||||
}
|
||||
NotifyLogHistoryReset.observe(call: #selector(reloadDataSource), on: self)
|
||||
NotifySyncInsert.observe(call: #selector(syncInsert), on: self)
|
||||
NotifySyncRemove.observe(call: #selector(syncRemove), on: self)
|
||||
reloadDataSource()
|
||||
}
|
||||
|
||||
@objc func reloadDataSource() {
|
||||
dataSource = DBWrp.listOfTimes(fullDomain)
|
||||
tableView.reloadData()
|
||||
@objc func reloadDataSource(sender: Any? = nil) {
|
||||
let refreshControl = sender as? UIRefreshControl
|
||||
let notification = sender as? Notification
|
||||
if let affectedDomain = notification?.object as? String {
|
||||
guard fullDomain.isSubdomain(of: affectedDomain) else { return }
|
||||
}
|
||||
DispatchQueue.global().async { [weak self] in
|
||||
self?.dataSource = AppDB?.timesForDomain(self?.fullDomain ?? "", since: sync.tsEarliest) ?? []
|
||||
DispatchQueue.main.sync {
|
||||
self?.tableView.reloadData()
|
||||
refreshControl?.endRefreshing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func syncInsert(_ notification: Notification) {
|
||||
let range = notification.object as! SQLiteRowRange
|
||||
if let latest = AppDB?.timesForDomain(fullDomain, range: range), latest.count > 0 {
|
||||
dataSource.insert(contentsOf: latest, at: 0)
|
||||
if tableView.isFrontmost {
|
||||
let indices = (0..<latest.count).map { IndexPath(row: $0) }
|
||||
tableView.insertRows(at: indices, with: .left)
|
||||
} else {
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func syncRemove(_ notification: Notification) {
|
||||
let earliest = sync.tsEarliest
|
||||
if let i = dataSource.firstIndex(where: { $0.ts < earliest }) {
|
||||
// since they are ordered, we can optimize
|
||||
let indices = (i..<dataSource.endIndex).map { IndexPath(row: $0) }
|
||||
dataSource.removeLast(dataSource.count - i)
|
||||
if tableView.isFrontmost {
|
||||
tableView.deleteRows(at: indices, with: .automatic)
|
||||
} else {
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
@@ -1,45 +1,32 @@
|
||||
import UIKit
|
||||
|
||||
class TVCHosts: UITableViewController, IncrementalDataSourceUpdate {
|
||||
class TVCHosts: UITableViewController, FilterPipelineDelegate {
|
||||
|
||||
lazy var source = GroupedDomainDataSource(withDelegate: self, parent: parentDomain)
|
||||
|
||||
public var parentDomain: String!
|
||||
internal var dataSource: [GroupedDomain] = []
|
||||
private var isSpecial: Bool = false
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.prompt = parentDomain
|
||||
isSpecial = (parentDomain.first == "#") // aka: "# IP address"
|
||||
if #available(iOS 10.0, *) {
|
||||
tableView.refreshControl = UIRefreshControl(call: #selector(reloadDataSource), on: self)
|
||||
}
|
||||
NotifyLogHistoryReset.observe(call: #selector(reloadDataSource), on: self)
|
||||
reloadDataSource()
|
||||
DBWrp.currentlyOpenParent = parentDomain
|
||||
DBWrp.dataB_delegate = self
|
||||
}
|
||||
deinit {
|
||||
DBWrp.currentlyOpenParent = nil
|
||||
}
|
||||
|
||||
@objc func reloadDataSource() {
|
||||
dataSource = DBWrp.listOfHosts(parentDomain)
|
||||
tableView.reloadData()
|
||||
source.reloadFromSource() // init lazy var
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
(segue.destination as? TVCHostDetails)?.fullDomain = dataSource[index].domain
|
||||
(segue.destination as? TVCHostDetails)?.fullDomain = source[index].domain
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data Source
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { source.numberOfRows }
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "HostCell")!
|
||||
let entry = dataSource[indexPath.row]
|
||||
let entry = source[indexPath.row]
|
||||
if isSpecial {
|
||||
// currently only used for IP addresses
|
||||
cell.textLabel?.text = entry.domain
|
||||
@@ -51,4 +38,11 @@ class TVCHosts: UITableViewController, IncrementalDataSourceUpdate {
|
||||
cell.imageView?.image = entry.options?.tableRowImage()
|
||||
return cell
|
||||
}
|
||||
|
||||
func rowNeedsUpdate(_ row: Int) {
|
||||
let entry = source[row]
|
||||
let cell = tableView.cellForRow(at: IndexPath(row: row))
|
||||
cell?.detailTextLabel?.text = entry.detailCellText
|
||||
cell?.imageView?.image = entry.options?.tableRowImage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
if Pref.DateFilter.Kind != newKind || Pref.DateFilter.LastXMin != newXMin {
|
||||
Pref.DateFilter.Kind = newKind
|
||||
Pref.DateFilter.LastXMin = newXMin
|
||||
DBWrp.reloadAfterDateFilterHasChanged()
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
dismiss(animated: true)
|
||||
|
||||
Reference in New Issue
Block a user