20dc672dbf
This adds a sync mechanism in Ndb.swift to coordinate certain usage of nostrdb.c calls and the need to close nostrdb due to app lifecycle requirements. Furthermore, it fixes the order of operations when re-opening NostrDB, to avoid race conditions where a query uses an older Ndb generation. This sync mechanism allows multiple queries to happen simultaneously (from the Swift-side), while preventing ndb from simultaneously closing during such usages. It also does that while keeping the Ndb interface sync and nonisolated, which keeps the API easy to use from Swift/SwiftUI and allows for parallel operations to occur. If Swift Actors were to be used (e.g. creating an NdbActor), the Ndb.swift interface would change in such a way that it would propagate the need for several changes throughout the codebase, including loading logic in some ViewModels. Furthermore, it would likely decrease performance by forcing Ndb.swift operations to run sequentially when they could run in parallel. Changelog-Fixed: Fixed crashes that happened when the app went into background mode Closes: https://github.com/damus-io/damus/issues/3245 Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
147 lines
4.6 KiB
Swift
147 lines
4.6 KiB
Swift
//
|
|
// SearchingEventView.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-03-05.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
enum SearchState {
|
|
case searching
|
|
case found(NostrEvent)
|
|
case found_profile(Pubkey)
|
|
case not_found
|
|
}
|
|
|
|
enum SearchType: Equatable {
|
|
case event(NoteId)
|
|
case profile(Pubkey)
|
|
case nip05(String)
|
|
case naddr(NAddr)
|
|
}
|
|
|
|
@MainActor
|
|
struct SearchingEventView: View {
|
|
let state: DamusState
|
|
let search_type: SearchType
|
|
|
|
@State var search_state: SearchState = .searching
|
|
|
|
var search_name: String {
|
|
switch search_type {
|
|
case .nip05:
|
|
return "Nostr Address"
|
|
case .profile:
|
|
return "Profile"
|
|
case .event:
|
|
return "Note"
|
|
case .naddr:
|
|
return "Naddr"
|
|
}
|
|
}
|
|
|
|
func handle_search(search: SearchType) {
|
|
self.search_state = .searching
|
|
|
|
switch search {
|
|
case .nip05(let nip05):
|
|
if let pk = state.profiles.nip05_pubkey[nip05] {
|
|
if (try? state.profiles.lookup_key_by_pubkey(pk)) != nil {
|
|
self.search_state = .found_profile(pk)
|
|
}
|
|
} else {
|
|
Task {
|
|
guard let nip05 = NIP05.parse(nip05) else {
|
|
Task { @MainActor in
|
|
self.search_state = .not_found
|
|
}
|
|
return
|
|
}
|
|
guard let nip05_resp = await fetch_nip05(nip05: nip05) else {
|
|
Task { @MainActor in
|
|
self.search_state = .not_found
|
|
}
|
|
return
|
|
}
|
|
|
|
Task { @MainActor in
|
|
guard let pk = nip05_resp.names[nip05.username] else {
|
|
self.search_state = .not_found
|
|
return
|
|
}
|
|
|
|
self.search_state = .found_profile(pk)
|
|
}
|
|
}
|
|
}
|
|
|
|
case .event(let note_id):
|
|
Task {
|
|
let res = await state.nostrNetwork.reader.findEvent(query: .event(evid: note_id))
|
|
guard case .event(let ev) = res else {
|
|
self.search_state = .not_found
|
|
return
|
|
}
|
|
self.search_state = .found(ev)
|
|
}
|
|
case .profile(let pubkey):
|
|
Task {
|
|
let res = await state.nostrNetwork.reader.findEvent(query: .profile(pubkey: pubkey))
|
|
guard case .profile(let pubkey) = res else {
|
|
self.search_state = .not_found
|
|
return
|
|
}
|
|
self.search_state = .found_profile(pubkey)
|
|
}
|
|
case .naddr(let naddr):
|
|
Task {
|
|
let res = await state.nostrNetwork.reader.lookup(naddr: naddr)
|
|
guard let res = res else {
|
|
self.search_state = .not_found
|
|
return
|
|
}
|
|
self.search_state = .found(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch search_state {
|
|
case .searching:
|
|
HStack(spacing: 10) {
|
|
Text("Looking for \(search_name)...", comment: "Label that appears when searching for note or profile")
|
|
ProgressView()
|
|
.progressViewStyle(.circular)
|
|
}
|
|
case .found(let ev):
|
|
NavigationLink(value: Route.Thread(thread: ThreadModel(event: ev, damus_state: state))) {
|
|
EventView(damus: state, event: ev)
|
|
}
|
|
.buttonStyle(PlainButtonStyle())
|
|
case .found_profile(let pk):
|
|
NavigationLink(value: Route.ProfileByKey(pubkey: pk)) {
|
|
FollowUserView(target: .pubkey(pk), damus_state: state)
|
|
}
|
|
.buttonStyle(PlainButtonStyle())
|
|
case .not_found:
|
|
Text("\(search_name) not found", comment: "When a note or profile is not found when searching for it via its note id")
|
|
}
|
|
}
|
|
.onChange(of: search_type, debounceTime: 0.5) { stype in
|
|
handle_search(search: stype)
|
|
}
|
|
.onAppear {
|
|
handle_search(search: search_type)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SearchingEventView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
let state = test_damus_state
|
|
SearchingEventView(state: state, search_type: .event(test_note.id))
|
|
}
|
|
}
|