Updated searching to use a publisher and cancel it (instead of db interrupt)

pull/1061/head
Morgan Pretty 10 months ago
parent e11375fdeb
commit 846aa695c2

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Combine
import GRDB
import SignalUtilitiesKit
import SessionUIKit
@ -85,31 +86,25 @@ extension ConversationSearchController: UISearchResultsUpdating {
}
let threadId: String = self.threadId
DispatchQueue.global(qos: .default).async { [weak self] in
let results: [Interaction.TimestampInfo]? = dependencies[singleton: .storage].read { db -> [Interaction.TimestampInfo] in
self?.resultsBar.willStartSearching(readConnection: db)
return try Interaction.idsForTermWithin(
let searchCancellable: AnyCancellable = dependencies[singleton: .storage]
.readPublisher { db -> [Interaction.TimestampInfo] in
try Interaction.idsForTermWithin(
threadId: threadId,
pattern: try SessionThreadViewModel.pattern(db, searchTerm: searchText)
)
.fetchAll(db)
}
// If we didn't get results back then we most likely interrupted the query so
// should ignore the results (if there are no results we would succeed and get
// an empty array back)
guard let results: [Interaction.TimestampInfo] = results else { return }
DispatchQueue.main.async {
guard let strongSelf = self else { return }
self?.resultsBar.stopLoading()
self?.resultsBar.updateResults(results: results, visibleItemIds: self?.delegate?.currentVisibleIds())
self?.delegate?.conversationSearchController(strongSelf, didUpdateSearchResults: results, searchText: searchText)
}
}
.subscribe(on: DispatchQueue.global(qos: .default), using: dependencies)
.receive(on: DispatchQueue.main, using: dependencies)
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] results in
self?.resultsBar.stopLoading()
self?.resultsBar.updateResults(results: results, visibleItemIds: self?.delegate?.currentVisibleIds())
self?.delegate?.conversationSearchController(self, didUpdateSearchResults: results, searchText: searchText)
}
)
self.resultsBar.willStartSearching(searchCancellable: searchCancellable)
}
}
@ -138,7 +133,7 @@ protocol SearchResultsBarDelegate: AnyObject {
public final class SearchResultsBar: UIView {
@ThreadSafe private var hasResults: Bool = false
@ThreadSafeObject private var results: [Interaction.TimestampInfo] = []
@ThreadSafeObject private var readConnection: Database? = nil
@ThreadSafeObject private var currentSearchCancellable: AnyCancellable? = nil
var currentIndex: Int?
weak var resultsBarDelegate: SearchResultsBarDelegate?
@ -275,8 +270,7 @@ public final class SearchResultsBar: UIView {
// MARK: - Content
/// This method will be called within a DB read block
func willStartSearching(readConnection: Database) {
func willStartSearching(searchCancellable: AnyCancellable) {
let hasNoExistingResults: Bool = hasResults
DispatchQueue.main.async { [weak self] in
@ -287,8 +281,8 @@ public final class SearchResultsBar: UIView {
self?.startLoading()
}
self.readConnection?.interrupt()
self._readConnection.set(to: readConnection)
currentSearchCancellable?.cancel()
_currentSearchCancellable.set(to: searchCancellable)
}
func updateResults(results: [Interaction.TimestampInfo]?, visibleItemIds: [Int64]?) {
@ -311,7 +305,6 @@ public final class SearchResultsBar: UIView {
return 0
}()
self._readConnection.set(to: nil)
self._results.performUpdate { _ in (results ?? []) }
self.hasResults = (results != nil)
@ -366,6 +359,6 @@ public final class SearchResultsBar: UIView {
public protocol ConversationSearchControllerDelegate: UISearchControllerDelegate {
func conversationSearchControllerDependencies() -> Dependencies
func currentVisibleIds() -> [Int64]
func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?)
func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionInfo: Interaction.TimestampInfo)
func conversationSearchController(_ conversationSearchController: ConversationSearchController?, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?)
func conversationSearchController(_ conversationSearchController: ConversationSearchController?, didSelectInteractionInfo: Interaction.TimestampInfo)
}

@ -1968,12 +1968,12 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa
func conversationSearchControllerDependencies() -> Dependencies { return viewModel.dependencies }
func currentVisibleIds() -> [Int64] { return (fullyVisibleCellViewModels() ?? []).map { $0.id } }
func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?) {
func conversationSearchController(_ conversationSearchController: ConversationSearchController?, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?) {
viewModel.lastSearchedText = searchText
tableView.reloadRows(at: tableView.indexPathsForVisibleRows ?? [], with: UITableView.RowAnimation.none)
}
func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionInfo interactionInfo: Interaction.TimestampInfo) {
func conversationSearchController(_ conversationSearchController: ConversationSearchController?, didSelectInteractionInfo interactionInfo: Interaction.TimestampInfo) {
scrollToInteractionIfNeeded(with: interactionInfo, focusBehaviour: .highlight)
}

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Combine
import GRDB
import DifferenceKit
import SessionUIKit
@ -106,7 +107,7 @@ class GlobalSearchViewController: BaseVC, LibSessionRespondingViewController, UI
)
}()
@ThreadSafeObject private var readConnection: Database? = nil
@ThreadSafeObject private var currentSearchCancellable: AnyCancellable? = nil
private lazy var searchResultSet: SearchResultData = defaultSearchResults
private var termForCurrentSearchResultSet: String = ""
private var lastSearchText: String?
@ -256,61 +257,54 @@ class GlobalSearchViewController: BaseVC, LibSessionRespondingViewController, UI
guard force || lastSearchText != searchText else { return }
lastSearchText = searchText
DispatchQueue.global(qos: .default).async { [weak self, dependencies] in
self?.readConnection?.interrupt()
let result: Result<[SectionModel], Error>? = dependencies[singleton: .storage].read { db -> Result<[SectionModel], Error> in
self?._readConnection.set(to: db)
currentSearchCancellable?.cancel()
_currentSearchCancellable.set(to: dependencies[singleton: .storage]
.readPublisher { [dependencies] db -> [SectionModel] in
let userSessionId: SessionId = dependencies[cache: .general].sessionId
let contactsAndGroupsResults: [SessionThreadViewModel] = try SessionThreadViewModel
.contactsAndGroupsQuery(
userSessionId: userSessionId,
pattern: try SessionThreadViewModel.pattern(db, searchTerm: searchText),
searchTerm: searchText
)
.fetchAll(db)
Thread.sleep(forTimeInterval: 1)
let messageResults: [SessionThreadViewModel] = try SessionThreadViewModel
.messagesQuery(
userSessionId: userSessionId,
pattern: try SessionThreadViewModel.pattern(db, searchTerm: searchText)
)
.fetchAll(db)
do {
let userSessionId: SessionId = dependencies[cache: .general].sessionId
let contactsAndGroupsResults: [SessionThreadViewModel] = try SessionThreadViewModel
.contactsAndGroupsQuery(
userSessionId: userSessionId,
pattern: try SessionThreadViewModel.pattern(db, searchTerm: searchText),
searchTerm: searchText
)
.fetchAll(db)
let messageResults: [SessionThreadViewModel] = try SessionThreadViewModel
.messagesQuery(
userSessionId: userSessionId,
pattern: try SessionThreadViewModel.pattern(db, searchTerm: searchText)
)
.fetchAll(db)
return .success([
ArraySection(model: .contactsAndGroups, elements: contactsAndGroupsResults),
ArraySection(model: .messages, elements: messageResults)
])
}
catch {
// Don't log the 'interrupt' error as that's just the user typing too fast
if (error as? DatabaseError)?.resultCode != DatabaseError.SQLITE_INTERRUPT {
SNLog("[GlobalSearch] Failed to find results due to error: \(error)")
}
return .failure(error)
}
return [
ArraySection(model: .contactsAndGroups, elements: contactsAndGroupsResults),
ArraySection(model: .messages, elements: messageResults)
]
}
self?._readConnection.set(to: nil)
DispatchQueue.main.async {
switch result {
case .success(let sections):
self?.termForCurrentSearchResultSet = searchText
self?.searchResultSet = SearchResultData(
state: (sections.map { $0.elements.count }.reduce(0, +) > 0) ? .results : .none,
data: sections
)
self?.isLoading = false
self?.tableView.reloadData()
self?.refreshTimer = nil
default: break
.subscribe(on: DispatchQueue.global(qos: .default), using: dependencies)
.receive(on: DispatchQueue.main, using: dependencies)
.sink(
receiveCompletion: { result in
/// Cancelling the search results in `receiveCompletion` not getting called so we can just log any
/// errors we get without needing to filter out "cancelled search" cases
switch result {
case .finished: break
case .failure(let error):
SNLog("[GlobalSearch] Failed to find results due to error: \(error)")
}
},
receiveValue: { [weak self] sections in
self?.termForCurrentSearchResultSet = searchText
self?.searchResultSet = SearchResultData(
state: (sections.map { $0.elements.count }.reduce(0, +) > 0) ? .results : .none,
data: sections
)
self?.isLoading = false
self?.tableView.reloadData()
self?.refreshTimer = nil
}
}
}
))
}
@objc func cancel() {

@ -21,12 +21,6 @@ public extension Database {
return try makeFTS5Pattern(rawPattern: rawPattern, forTable: table.databaseTableName)
}
func interrupt() {
guard sqliteConnection != nil else { return }
sqlite3_interrupt(sqliteConnection)
}
/// This is a custom implementation of the `afterNextTransaction` method which executes the closures within their own
/// transactions to allow for nesting of 'afterNextTransaction' actions
///

Loading…
Cancel
Save