diff --git a/AppCheck.xcodeproj/project.pbxproj b/AppCheck.xcodeproj/project.pbxproj index 96716dd..9bb7f74 100644 --- a/AppCheck.xcodeproj/project.pbxproj +++ b/AppCheck.xcodeproj/project.pbxproj @@ -1111,7 +1111,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = main/main.entitlements; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; INFOPLIST_FILE = main/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1130,7 +1130,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = main/main.entitlements; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; INFOPLIST_FILE = main/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1149,7 +1149,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; INFOPLIST_FILE = GlassVPN/Info.plist; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN"; @@ -1167,7 +1167,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; INFOPLIST_FILE = GlassVPN/Info.plist; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN"; diff --git a/main/Common Classes/FilterPipeline.swift b/main/Common Classes/FilterPipeline.swift index b036975..dcb5632 100644 --- a/main/Common Classes/FilterPipeline.swift +++ b/main/Common Classes/FilterPipeline.swift @@ -1,24 +1,15 @@ import UIKit protocol FilterPipelineDelegate: AnyObject { - /// Call `reloadData()` on main thread. - /// - Warning: This function may be called from a background thread. + /// Call `reloadData()` func filterPipelineDidReset() - - /// Call `safeDeleteRows()` on main thread. - /// - Warning: This function may be called from a background thread. + /// Call `safeDeleteRows()` func filterPipeline(delete rows: [Int]) - - /// Call `safeInsertRow()` on main thread. - /// - Warning: This function may be called from a background thread. + /// Call `safeInsertRow()` func filterPipeline(insert row: Int) - - /// Call `safeReloadRow()` on main thread. - /// - Warning: This function may be called from a background thread. + /// Call `safeReloadRow()` func filterPipeline(update row: Int) - - /// Call `safeMoveRow()` on main thread. - /// - Warning: This function may be called from a background thread. + /// Call `safeMoveRow()` func filterPipeline(move oldRow: Int, to newRow: Int) } diff --git a/main/Data Source/GroupedDomainDataSource.swift b/main/Data Source/GroupedDomainDataSource.swift index 8decb6f..4b5abd3 100644 --- a/main/Data Source/GroupedDomainDataSource.swift +++ b/main/Data Source/GroupedDomainDataSource.swift @@ -101,7 +101,9 @@ extension GroupedDomainDataSource { for (i, val) in logs.enumerated() { logs[i].options = DomainFilter[val.domain] } - pipeline.reset(dataSource: logs) + DispatchQueue.main.sync { + pipeline.reset(dataSource: logs) + } } func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) { @@ -109,17 +111,19 @@ extension GroupedDomainDataSource { assertionFailure("NotifySyncInsert fired with empty range") return } - cellAnimationsGroup(if: latest.count > 14) - for x in latest { - if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) { - pipeline.update(obj + x, at: i) - } else { - var y = x - y.options = DomainFilter[x.domain] - pipeline.addNew(y) + DispatchQueue.main.sync { + cellAnimationsGroup(if: latest.count > 14) + for x in latest { + if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) { + pipeline.update(obj + x, at: i) + } else { + var y = x + y.options = DomainFilter[x.domain] + pipeline.addNew(y) + } } + cellAnimationsCommit() } - cellAnimationsCommit() } func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) { @@ -132,21 +136,23 @@ extension GroupedDomainDataSource { outdated.count > 0 else { return } - cellAnimationsGroup(if: outdated.count > 14) - var listOfDeletes: [Int] = [] - for x in outdated { - guard let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) else { - assertionFailure("Try to remove non-existent element") - continue // should never happen - } - if obj.total > x.total { - pipeline.update(obj - x, at: i) - } else { - listOfDeletes.append(i) + DispatchQueue.main.sync { + cellAnimationsGroup(if: outdated.count > 14) + var listOfDeletes: [Int] = [] + for x in outdated { + guard let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) else { + assertionFailure("Try to remove non-existent element") + continue // should never happen + } + if obj.total > x.total { + pipeline.update(obj - x, at: i) + } else { + listOfDeletes.append(i) + } } + pipeline.remove(indices: listOfDeletes.sorted()) + cellAnimationsCommit() } - pipeline.remove(indices: listOfDeletes.sorted()) - cellAnimationsCommit() } func syncUpdate(_ sender: SyncUpdate, partialRemove affectedFQDN: String) { @@ -155,17 +161,19 @@ extension GroupedDomainDataSource { return // does not affect current table } let affected = (parent == nil ? affectedParent : affectedFQDN) - guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else { - // can only happen if delete sheet is open while background sync removed the element - return - } - if var updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected, - parentDomain: parent)?.first { - assert(old.object.domain == updated.domain) - updated.options = DomainFilter[updated.domain] - pipeline.update(updated, at: old.index) - } else { - pipeline.remove(indices: [old.index]) + let updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected, parentDomain: parent)?.first + DispatchQueue.main.sync { + guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else { + // can only happen if delete sheet is open while background sync removed the element + return + } + if var updated = updated { + assert(old.object.domain == updated.domain) + updated.options = DomainFilter[updated.domain] + pipeline.update(updated, at: old.index) + } else { + pipeline.remove(indices: [old.index]) + } } } } @@ -180,9 +188,8 @@ extension GroupedDomainDataSource { extension GroupedDomainDataSource { /// Sets `pipeline.delegate = nil` to disable individual cell animations (update, insert, delete & move). private func cellAnimationsGroup(if condition: Bool = true) { - if condition { pipeline.delegate = nil } - if pipeline.delegate != nil { - onMain { if !$0.isFrontmost { self.pipeline.delegate = nil } } + if condition || delegate?.tableView.isFrontmost == false { + pipeline.delegate = nil } } /// No-Op if cell animations are enabled already. @@ -190,33 +197,26 @@ extension GroupedDomainDataSource { private func cellAnimationsCommit() { if pipeline.delegate == nil { pipeline.delegate = self - onMain { $0.reloadData() } - } - } - /// Perform table view manipulations on main thread. - /// Set `delegate = nil` to disable `tableView` animations. - private func onMain(_ closure: (UITableView) -> Void) { - if Thread.isMainThread { - if let tv = delegate?.tableView { closure(tv) } - } else { - DispatchQueue.main.sync { - if let tv = delegate?.tableView { closure(tv) } - } + delegate?.tableView.reloadData() } } // TODO: Collect animations and post them in a single animations block. - // This will require enormous work to translate then into a final set. - func filterPipelineDidReset() { onMain { $0.reloadData() } } - func filterPipeline(delete rows: [Int]) { onMain { $0.safeDeleteRows(rows) } } - func filterPipeline(insert row: Int) { onMain { $0.safeInsertRow(row, with: .left) } } - func filterPipeline(update row: Int) { onMain { $0.safeReloadRow(row) } } + // This will require enormous work to translate them into a final set. + func filterPipelineDidReset() { delegate?.tableView.reloadData() } + func filterPipeline(delete rows: [Int]) { delegate?.tableView.safeDeleteRows(rows) } + func filterPipeline(insert row: Int) { delegate?.tableView.safeInsertRow(row, with: .left) } + func filterPipeline(update row: Int) { + guard let tv = delegate?.tableView else { return } + if !tv.isEditing { tv.safeReloadRow(row) } + else if tv.isFrontmost == true { + delegate?.groupedDomainDataSource(needsUpdate: row) + } + } func filterPipeline(move oldRow: Int, to newRow: Int) { - onMain { - $0.safeMoveRow(oldRow, to: newRow) - if $0.isFrontmost { // onMain ensures delegate is set - delegate!.groupedDomainDataSource(needsUpdate: newRow) - } + delegate?.tableView.safeMoveRow(oldRow, to: newRow) + if delegate?.tableView.isFrontmost == true { + delegate?.groupedDomainDataSource(needsUpdate: newRow) } } }