diff --git a/main/Data Source/GroupedDomainDataSource.swift b/main/Data Source/GroupedDomainDataSource.swift index e9276c1..8decb6f 100644 --- a/main/Data Source/GroupedDomainDataSource.swift +++ b/main/Data Source/GroupedDomainDataSource.swift @@ -104,7 +104,7 @@ extension GroupedDomainDataSource { pipeline.reset(dataSource: logs) } - func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange) { + func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) { guard let latest = AppDB?.dnsLogsGrouped(range: rows, parentDomain: parent) else { assertionFailure("NotifySyncInsert fired with empty range") return @@ -122,7 +122,12 @@ extension GroupedDomainDataSource { cellAnimationsCommit() } - func syncUpdate(_: SyncUpdate, remove rows: SQLiteRowRange) { + func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) { + if affects == .Latest { + // TODO: alternatively query last modified from db (last entry _before_ range) + syncUpdate(sender, reset: sender.rows) + return + } guard let outdated = AppDB?.dnsLogsGrouped(range: rows, parentDomain: parent), outdated.count > 0 else { return diff --git a/main/Data Source/SyncUpdate.swift b/main/Data Source/SyncUpdate.swift index 2bfcc6c..00d05f4 100644 --- a/main/Data Source/SyncUpdate.swift +++ b/main/Data Source/SyncUpdate.swift @@ -102,10 +102,10 @@ class SyncUpdate { if filterType == .ABRange { // ... even if we filter a few later if let r = rows(tsMin, tsMax, scope: newest) { - notify(insert: r, front: false) + notify(insert: r, .Latest) } } else { - notify(insert: newest, front: false) + notify(insert: newest, .Latest) } } if filterType == .LastXMin { @@ -117,6 +117,10 @@ class SyncUpdate { // MARK: - Internal + private func rows(_ ts1: Timestamp, _ ts2: Timestamp, scope: SQLiteRowRange = (0,0)) -> SQLiteRowRange? { + AppDB?.dnsLogsRowRange(between: ts1, and: ts2, within: scope) + } + private func reloadRangeFromDB() { // `nil` is not SQLiteRowRange(0,0) aka. full collection. // `nil` means invalid range. e.g. ts restriction too high or empty db. @@ -132,11 +136,11 @@ class SyncUpdate { if let (old, new) = tsEarliest <-/ newEarliest { if old != nil, (new == nil || new! < old!) { if let r = rows(from(new), to(old!), scope: (0, range?.start ?? 0)) { - notify(insert: r, front: true) + notify(insert: r, .Earliest) } } else if range != nil { if let r = rows(from(old), to(new!), scope: range!) { - notify(remove: r, front: true) + notify(remove: r, .Earliest) } } } @@ -152,34 +156,38 @@ class SyncUpdate { if let (old, new) = tsLatest <-/ newLatest { if old != nil, (new == nil || old! < new!) { if let r = rows(from(old!), to(new), scope: (range?.end ?? 0, 0)) { - notify(insert: r, front: false) + notify(insert: r, .Latest) } } else if range != nil { // FIXME: removing latest entries will invalidate "last changed" label if let r = rows(from(new!), to(old), scope: range!) { - notify(remove: r, front: false) + notify(remove: r, .Latest) } } } } - private func rows(_ ts1: Timestamp, _ ts2: Timestamp, scope: SQLiteRowRange = (0,0)) -> SQLiteRowRange? { - AppDB?.dnsLogsRowRange(between: ts1, and: ts2, within: scope) - } - /// - Warning: Always call from a background thread! - private func notify(insert r: SQLiteRowRange, front: Bool) { + private func notify(insert r: SQLiteRowRange, _ end: SyncUpdateEnd) { if range == nil { range = r } - else { front ? (range!.start = r.start) : (range!.end = r.end) } - notifyObservers { $0.syncUpdate(self, insert: r) } + else { + switch end { + case .Earliest: range!.start = r.start + case .Latest: range!.end = r.end + } + } + notifyObservers { $0.syncUpdate(self, insert: r, affects: end) } } /// - Warning: `range` must not be `nil`! /// - Warning: Always call from a background thread! - private func notify(remove r: SQLiteRowRange, front: Bool) { - front ? (range!.start = r.end + 1) : (range!.end = r.start - 1) + private func notify(remove r: SQLiteRowRange, _ end: SyncUpdateEnd) { + switch end { + case .Earliest: range!.start = r.end + 1 + case .Latest: range!.end = r.start - 1 + } if range!.start > range!.end { range = nil } - notifyObservers { $0.syncUpdate(self, remove: r) } + notifyObservers { $0.syncUpdate(self, remove: r, affects: end) } } @@ -211,6 +219,8 @@ private struct WeakObserver { weak var pullToRefresh: UIRefreshControl? } +enum SyncUpdateEnd { case Earliest, Latest } + protocol SyncUpdateDelegate : AnyObject { /// `SyncUpdate` has unpredictable changes. Reload your `dataSource`. /// - Warning: This function will **always** be called from a background thread. @@ -218,11 +228,11 @@ protocol SyncUpdateDelegate : AnyObject { /// `SyncUpdate` added new `rows` to database. Sync changes to your `dataSource`. /// - Warning: This function will **always** be called from a background thread. - func syncUpdate(_ sender: SyncUpdate, insert rows: SQLiteRowRange) + func syncUpdate(_ sender: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) /// `SyncUpdate` outdated some `rows` in database. Sync changes to your `dataSource`. /// - Warning: This function will **always** be called from a background thread. - func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange) + func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) /// Background process did delete some entries in database that match `affectedDomain`. /// Update or remove entries from your `dataSource`. diff --git a/main/Extensions/TableView.swift b/main/Extensions/TableView.swift index 28b4e69..b3268dc 100644 --- a/main/Extensions/TableView.swift +++ b/main/Extensions/TableView.swift @@ -19,18 +19,26 @@ extension UITableView { /// Returns `true` if this `tableView` is the currently frontmost visible var isFrontmost: Bool { window?.isKeyWindow ?? false } + /// If frontmost window, perform `insertRows()`; If not, perform `reloadData()` + func safeInsertRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) { + isFrontmost ? insertRows(at: [IndexPath(row: index)], with: animation) : reloadData() + } + /// If frontmost window, perform `insertRows()`; If not, perform `reloadData()` + func safeInsertRows(_ range: Range, with animation: UITableView.RowAnimation = .automatic) { + isFrontmost ? insertRows(at: range.map {IndexPath(row: $0)}, with: animation) : reloadData() + } /// If frontmost window, perform `deleteRows()`; If not, perform `reloadData()` func safeDeleteRows(_ indices: [Int], with animation: UITableView.RowAnimation = .automatic) { isFrontmost ? deleteRows(at: indices.map {IndexPath(row: $0)}, with: animation) : reloadData() } + /// If frontmost window, perform `deleteRows()`; If not, perform `reloadData()` + func safeDeleteRows(_ range: Range, with animation: UITableView.RowAnimation = .automatic) { + isFrontmost ? deleteRows(at: range.map {IndexPath(row: $0)}, with: animation) : reloadData() + } /// If frontmost window, perform `reloadRows()`; If not, perform `reloadData()` func safeReloadRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) { isFrontmost ? reloadRows(at: [IndexPath(row: index)], with: animation) : reloadData() } - /// If frontmost window, perform `insertRows()`; If not, perform `reloadData()` - func safeInsertRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) { - isFrontmost ? insertRows(at: [IndexPath(row: index)], with: animation) : reloadData() - } /// If frontmost window, perform `moveRow()`; If not, perform `reloadData()` func safeMoveRow(_ from: Int, to: Int) { isFrontmost ? moveRow(at: IndexPath(row: from), to: IndexPath(row: to)) : reloadData() diff --git a/main/Requests/TVCHostDetails.swift b/main/Requests/TVCHostDetails.swift index a05dc92..f693c9f 100644 --- a/main/Requests/TVCHostDetails.swift +++ b/main/Requests/TVCHostDetails.swift @@ -42,34 +42,41 @@ extension TVCHostDetails { DispatchQueue.main.sync { tableView.reloadData() } } - func syncUpdate(_ _: SyncUpdate, insert rows: SQLiteRowRange) { + func syncUpdate(_ _: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) { guard let latest = AppDB?.timesForDomain(fullDomain, range: rows), latest.count > 0 else { return } - // TODO: if filter will be ever editable at this point, we cannot insert at 0 - dataSource.insert(contentsOf: latest, at: 0) - DispatchQueue.main.sync { - if tableView.isFrontmost { - let indices = (0.. + switch affects { + case .Earliest: + range = dataSource.endIndex..<(dataSource.endIndex + latest.count) + dataSource.append(contentsOf: latest) + case .Latest: + range = dataSource.startIndex..<(dataSource.startIndex + latest.count) + dataSource.insert(contentsOf: latest, at: 0) } + DispatchQueue.main.sync { tableView.safeInsertRows(range, with: .left) } } - func syncUpdate(_ sender: SyncUpdate, remove _: SQLiteRowRange) { + func syncUpdate(_ sender: SyncUpdate, remove _: SQLiteRowRange, affects: SyncUpdateEnd) { // Assuming they are ordered by ts and in descending order - if let t = sender.tsEarliest, let i = dataSource.lastIndex(where: { $0.ts >= t }), (i+1) < dataSource.count { - let indices = ((i+1).. + switch affects { + case .Earliest: + guard let t = sender.tsEarliest, + let i = dataSource.lastIndex(where: { $0.ts >= t }), + (i+1) < dataSource.count else { return } + range = (i+1).. 0 { - let indices = (dataSource.startIndex.. 0 else { return } + range = dataSource.startIndex..