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:
relikd
2020-06-02 21:45:08 +02:00
parent 10b43a0f67
commit b17fb3c354
36 changed files with 2214 additions and 1482 deletions

View File

@@ -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 = "AB"

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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)