From fc9b9f2940feb199c3ec14035f8bbe1c097a34d2 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sun, 10 Sep 2023 14:51:55 -0700 Subject: [PATCH] ndb: switch profile queries to use transactions this should ensure no crashing occurs when querying profiles --- damus/Components/NIP05Badge.swift | 3 +- damus/Components/Reposted.swift | 7 +- damus/Components/UserView.swift | 3 +- damus/ContentView.swift | 60 ++++---- damus/Models/HomeModel.swift | 11 +- damus/Models/ProfileUpdate.swift | 15 +- damus/Nostr/Nostr.swift | 5 +- damus/Nostr/NostrResponse.swift | 2 +- damus/Nostr/Profiles.swift | 19 ++- damus/Notify/ProfileUpdatedNotify.swift | 4 +- damus/Util/DisplayName.swift | 2 +- damus/Util/EventCache.swift | 3 + damus/Util/NIP05.swift | 2 +- damus/Views/ActionBar/EventActionBar.swift | 2 +- damus/Views/BannerImageView.swift | 8 +- damus/Views/DMChatView.swift | 3 +- damus/Views/Events/Components/EventTop.swift | 3 +- .../Events/Components/ReplyDescription.swift | 17 ++- damus/Views/Events/Components/ReplyPart.swift | 6 +- damus/Views/Events/EventMenu.swift | 3 +- damus/Views/Events/EventProfile.swift | 5 +- damus/Views/Events/EventShell.swift | 4 +- damus/Views/Events/SelectedEventView.swift | 6 +- damus/Views/NoteContentView.swift | 3 +- .../Views/Notifications/EventGroupView.swift | 4 +- .../Notifications/NotificationsView.swift | 6 +- .../Views/Onboarding/SuggestedUserView.swift | 19 +-- .../Onboarding/SuggestedUsersViewModel.swift | 5 +- damus/Views/PostView.swift | 3 +- damus/Views/Posting/UserSearch.swift | 24 ++-- damus/Views/Profile/EditMetadataView.swift | 8 +- damus/Views/Profile/EventProfileName.swift | 42 ++++-- damus/Views/Profile/ProfileName.swift | 59 +++++--- damus/Views/Profile/ProfileNameView.swift | 12 +- damus/Views/Profile/ProfilePicView.swift | 17 ++- .../Profile/ProfilePictureSelector.swift | 2 +- damus/Views/Profile/ProfileView.swift | 50 ++++--- damus/Views/QRCodeView.swift | 8 +- damus/Views/ReplyView.swift | 3 +- damus/Views/Reposts/RepostedEvent.swift | 4 +- damus/Views/Search/SearchingEventView.swift | 14 +- damus/Views/SearchResultsView.swift | 26 ++-- damus/Views/SideMenuView.swift | 3 +- damus/Views/Wallet/WalletView.swift | 11 +- damus/Views/Zaps/ZapTypePicker.swift | 3 +- nostrdb/Ndb.swift | 130 +++++++++++++++--- nostrdb/NdbNote.swift | 7 +- nostrdb/NdbTagsIterator.swift | 2 +- nostrdb/NdbTxn.swift | 2 +- nostrdb/nostrdb.c | 26 +++- nostrdb/nostrdb.h | 1 + 51 files changed, 435 insertions(+), 252 deletions(-) diff --git a/damus/Components/NIP05Badge.swift b/damus/Components/NIP05Badge.swift index 0c2552db..bb116455 100644 --- a/damus/Components/NIP05Badge.swift +++ b/damus/Components/NIP05Badge.swift @@ -45,8 +45,7 @@ struct NIP05Badge: View { } var username_matches_nip05: Bool { - guard let profile = profiles.lookup(id: pubkey), - let name = profile.name + guard let name = profiles.lookup(id: pubkey).map({ p in p?.name }).value else { return false } diff --git a/damus/Components/Reposted.swift b/damus/Components/Reposted.swift index f4e5e80f..f03bf544 100644 --- a/damus/Components/Reposted.swift +++ b/damus/Components/Reposted.swift @@ -10,13 +10,12 @@ import SwiftUI struct Reposted: View { let damus: DamusState let pubkey: Pubkey - let profile: Profile? - + var body: some View { HStack(alignment: .center) { Image("repost") .foregroundColor(Color.gray) - ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_nip5_domain: false) + ProfileName(pubkey: pubkey, damus: damus, show_nip5_domain: false) .foregroundColor(Color.gray) Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).") .foregroundColor(Color.gray) @@ -27,6 +26,6 @@ struct Reposted: View { struct Reposted_Previews: PreviewProvider { static var previews: some View { let test_state = test_damus_state() - Reposted(damus: test_state, pubkey: test_state.pubkey, profile: make_test_profile()) + Reposted(damus: test_state, pubkey: test_state.pubkey) } } diff --git a/damus/Components/UserView.swift b/damus/Components/UserView.swift index e59fe24b..b8de6c88 100644 --- a/damus/Components/UserView.swift +++ b/damus/Components/UserView.swift @@ -37,8 +37,7 @@ struct UserView: View { ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) VStack(alignment: .leading) { - let profile = damus_state.profiles.lookup(id: pubkey) - ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false) + ProfileName(pubkey: pubkey, damus: damus_state, show_nip5_domain: false) if let about_text { about_text .lineLimit(3) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 9027b461..ee1edb14 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -359,13 +359,18 @@ struct ContentView: View { // wallet with an associated guard let ds = self.damus_state, let lud16 = nwc.lud16, - let keypair = ds.keypair.to_full(), - let profile = ds.profiles.lookup(id: ds.pubkey), - lud16 != profile.lud16 + let keypair = ds.keypair.to_full() else { return } - + + let profile_txn = ds.profiles.lookup(id: ds.pubkey) + + guard let profile = profile_txn.unsafeUnownedValue, + lud16 != profile.lud16 else { + return + } + // clear zapper cache for old lud16 if profile.lud16 != nil { // TODO: should this be somewhere else, where we process profile events!? @@ -378,15 +383,9 @@ struct ContentView: View { ds.postbox.send(ev) } .onReceive(handle_notify(.broadcast)) { ev in - guard let ds = self.damus_state else { - return - } + guard let ds = self.damus_state else { return } + ds.postbox.send(ev) - if let record = ds.profiles.lookup_with_timestamp(ev.pubkey), - let event = ds.events.lookup_by_key(record.noteKey) - { - ds.postbox.send(event) - } } .onReceive(handle_notify(.unfollow)) { target in guard let state = self.damus_state else { return } @@ -488,10 +487,12 @@ struct ContentView: View { } .onReceive(handle_notify(.onlyzaps_mode)) { hide in home.filter_events() - - guard let damus_state, - let profile = damus_state.profiles.lookup(id: damus_state.pubkey), - let keypair = damus_state.keypair.to_full() + + guard let ds = damus_state else { return } + let profile_txn = ds.profiles.lookup(id: ds.pubkey) + + guard let profile = profile_txn.unsafeUnownedValue, + let keypair = ds.keypair.to_full() else { return } @@ -499,7 +500,7 @@ struct ContentView: View { let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: profile.lud16, nip05: profile.nip05, damus_donation: profile.damus_donation, reactions: !hide) guard let profile_ev = make_metadata_event(keypair: keypair, metadata: prof) else { return } - damus_state.postbox.send(profile_ev) + ds.postbox.send(profile_ev) } .alert(NSLocalizedString("User muted", comment: "Alert message to indicate the user has been muted"), isPresented: $user_muted_confirm, actions: { Button(NSLocalizedString("Thanks!", comment: "Button to close out of alert that informs that the action to muted a user was successful.")) { @@ -507,11 +508,12 @@ struct ContentView: View { } }, message: { if let pubkey = self.muting { - let profile = damus_state!.profiles.lookup(id: pubkey) - let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) + let name = damus_state!.profiles.lookup(id: pubkey).map { profile in + Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) + }.value Text("\(name) has been muted", comment: "Alert message that informs a user was muted.") } else { - Text("User has been muted", comment: "Alert message that informs a user was d.") + Text("User has been muted", comment: "Alert message that informs a user was muted.") } }) .alert(NSLocalizedString("Create new mutelist", comment: "Title of alert prompting the user to create a new mutelist."), isPresented: $confirm_overwrite_mutelist, actions: { @@ -567,8 +569,9 @@ struct ContentView: View { } }, message: { if let pubkey = muting { - let profile = damus_state?.profiles.lookup(id: pubkey) - let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) + let name = damus_state?.profiles.lookup(id: pubkey).map({ profile in + Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) + }).value ?? "unknown" Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.") } else { Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.") @@ -799,7 +802,7 @@ enum FindEventType { } enum FoundEvent { - case profile(Profile, NostrEvent) + case profile(Pubkey) case invalid_profile(NostrEvent) case event(NostrEvent) } @@ -816,11 +819,10 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St switch query { case .profile(let pubkey): - if let record = state.profiles.lookup_with_timestamp(pubkey), - let profile = record.profile, - let event = state.events.lookup_by_key(record.noteKey) + if let record = state.ndb.lookup_profile(pubkey).unsafeUnownedValue, + record.profile != nil { - callback(.profile(profile, event)) + callback(.profile(pubkey)) return } filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey]) @@ -857,11 +859,11 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St switch query { case .profile: if ev.known_kind == .metadata { - guard let profile = state.profiles.lookup(id: ev.pubkey) else { + guard state.ndb.lookup_profile_key(ev.pubkey) != nil else { callback(.invalid_profile(ev)) return } - callback(.profile(profile, ev)) + callback(.profile(ev.pubkey)) } case .event: callback(.event(ev)) diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index 03e3b294..d2c28596 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -1108,10 +1108,13 @@ func zap_notification_title(_ zap: Zap) -> String { func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String { let src = zap.request.ev let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey - let profile = profiles.lookup(id: pk) + + let name = profiles.lookup(id: pk).map { profile in + Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50) + }.value + let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0)) let formattedSats = format_msats_abbrev(zap.invoice.amount) - let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50) if src.content.isEmpty { let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale) @@ -1361,8 +1364,8 @@ func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @esc return } - guard let record = damus_state.profiles.lookup_with_timestamp(ptag), - let lnurl = record.lnurl else { + guard let lnurl = damus_state.profiles.lookup_with_timestamp(ptag) + .map({ pr in pr?.lnurl }).value else { completion(.failed) return } diff --git a/damus/Models/ProfileUpdate.swift b/damus/Models/ProfileUpdate.swift index 7e302fba..4f383d94 100644 --- a/damus/Models/ProfileUpdate.swift +++ b/damus/Models/ProfileUpdate.swift @@ -8,7 +8,16 @@ import Foundation -struct ProfileUpdate { - let pubkey: Pubkey - let profile: Profile +enum ProfileUpdate { + case manual(pubkey: Pubkey, profile: Profile) + case remote(pubkey: Pubkey) + + var pubkey: Pubkey { + switch self { + case .manual(let pubkey, _): + return pubkey + case .remote(let pubkey): + return pubkey + } + } } diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift index 6e4ceec7..35d32f77 100644 --- a/damus/Nostr/Nostr.swift +++ b/damus/Nostr/Nostr.swift @@ -8,15 +8,18 @@ import Foundation typealias Profile = NdbProfile +typealias ProfileKey = UInt64 //typealias ProfileRecord = NdbProfileRecord class ProfileRecord { let data: NdbProfileRecord - init(data: NdbProfileRecord) { + init(data: NdbProfileRecord, key: ProfileKey) { self.data = data + self.profileKey = key } + let profileKey: ProfileKey var profile: Profile? { return data.profile } var receivedAt: UInt64 { data.receivedAt } var noteKey: UInt64 { data.noteKey } diff --git a/damus/Nostr/NostrResponse.swift b/damus/Nostr/NostrResponse.swift index 21829342..06efed8f 100644 --- a/damus/Nostr/NostrResponse.swift +++ b/damus/Nostr/NostrResponse.swift @@ -84,7 +84,7 @@ enum NostrResponse { return nil } let new_note = note_data.assumingMemoryBound(to: ndb_note.self) - let note = NdbNote(note: new_note, owned_size: Int(len)) + let note = NdbNote(note: new_note, owned_size: Int(len), key: nil) guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else { free(data) diff --git a/damus/Nostr/Profiles.swift b/damus/Nostr/Profiles.swift index ae9c0123..adb0a61e 100644 --- a/damus/Nostr/Profiles.swift +++ b/damus/Nostr/Profiles.swift @@ -76,18 +76,25 @@ class Profiles { profile_data(pubkey).zapper } - func lookup_with_timestamp(_ pubkey: Pubkey) -> ProfileRecord? { + func lookup_with_timestamp(_ pubkey: Pubkey) -> NdbTxn { return ndb.lookup_profile(pubkey) } - func lookup(id: Pubkey) -> Profile? { - return ndb.lookup_profile(id)?.profile + func lookup_by_key(key: ProfileKey) -> NdbTxn { + return ndb.lookup_profile_by_key(key: key) + } + + func lookup(id: Pubkey) -> NdbTxn { + return ndb.lookup_profile(id).map({ pr in pr?.profile }) + } + + func lookup_key_by_pubkey(_ pubkey: Pubkey) -> ProfileKey? { + return ndb.lookup_profile_key(pubkey) } func has_fresh_profile(id: Pubkey) -> Bool { - var profile: Profile? - guard let profile = lookup_with_timestamp(id) else { return false } - return Date.now.timeIntervalSince(Date(timeIntervalSince1970: Double(profile.receivedAt))) < Profiles.db_freshness_threshold + guard let recv = lookup_with_timestamp(id).unsafeUnownedValue?.receivedAt else { return false } + return Date.now.timeIntervalSince(Date(timeIntervalSince1970: Double(recv))) < Profiles.db_freshness_threshold } } diff --git a/damus/Notify/ProfileUpdatedNotify.swift b/damus/Notify/ProfileUpdatedNotify.swift index b05421bf..4f26c9b8 100644 --- a/damus/Notify/ProfileUpdatedNotify.swift +++ b/damus/Notify/ProfileUpdatedNotify.swift @@ -19,7 +19,7 @@ extension NotifyHandler { } extension Notifications { - static func profile_updated(pubkey: Pubkey, profile: Profile) -> Notifications { - .init(.init(payload: ProfileUpdate(pubkey: pubkey, profile: profile))) + static func profile_updated(_ update: ProfileUpdate) -> Notifications { + .init(.init(payload: update)) } } diff --git a/damus/Util/DisplayName.swift b/damus/Util/DisplayName.swift index 4c4ec693..680cd077 100644 --- a/damus/Util/DisplayName.swift +++ b/damus/Util/DisplayName.swift @@ -7,7 +7,7 @@ import Foundation -enum DisplayName { +enum DisplayName: Equatable { case both(username: String, displayName: String) case one(String) diff --git a/damus/Util/EventCache.swift b/damus/Util/EventCache.swift index 6d0e638e..82bb6466 100644 --- a/damus/Util/EventCache.swift +++ b/damus/Util/EventCache.swift @@ -137,6 +137,7 @@ class EventData { } class EventCache { + // TODO: remove me and change code to use ndb directly private let ndb: Ndb private var events: [NoteId: NostrEvent] = [:] private var replies = ReplyMap() @@ -253,9 +254,11 @@ class EventCache { return ev } + /* func lookup_by_key(_ key: UInt64) -> NostrEvent? { ndb.lookup_note_by_key(key) } + */ func lookup(_ evid: NoteId) -> NostrEvent? { return events[evid] diff --git a/damus/Util/NIP05.swift b/damus/Util/NIP05.swift index 9432a2bb..1c41e4a5 100644 --- a/damus/Util/NIP05.swift +++ b/damus/Util/NIP05.swift @@ -7,7 +7,7 @@ import Foundation -struct NIP05 { +struct NIP05: Equatable { let username: String let host: String diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift index 79d2e304..b08e6d16 100644 --- a/damus/Views/ActionBar/EventActionBar.swift +++ b/damus/Views/ActionBar/EventActionBar.swift @@ -28,7 +28,7 @@ struct EventActionBar: View { } var lnurl: String? { - damus_state.profiles.lookup_with_timestamp(event.pubkey)?.lnurl + damus_state.profiles.lookup_with_timestamp(event.pubkey).map({ pr in pr?.lnurl }).value } var show_like: Bool { diff --git a/damus/Views/BannerImageView.swift b/damus/Views/BannerImageView.swift index 5c1d2e8e..dee4e11e 100644 --- a/damus/Views/BannerImageView.swift +++ b/damus/Views/BannerImageView.swift @@ -81,8 +81,10 @@ struct BannerImageView: View { guard updated.pubkey == self.pubkey else { return } - - if let bannerImage = updated.profile.banner { + + let profile_txn = profiles.lookup(id: updated.pubkey) + let profile = profile_txn.unsafeUnownedValue + if let bannerImage = profile?.banner, bannerImage != self.banner { self.banner = bannerImage } } @@ -90,7 +92,7 @@ struct BannerImageView: View { } func get_banner_url(banner: String?, pubkey: Pubkey, profiles: Profiles) -> URL? { - let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.banner ?? "" + let bannerUrlString = banner ?? profiles.lookup(id: pubkey).map({ p in p?.banner }).value ?? "" if let url = URL(string: bannerUrlString) { return url } diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift index 18bf288f..79a15617 100644 --- a/damus/Views/DMChatView.swift +++ b/damus/Views/DMChatView.swift @@ -60,12 +60,11 @@ struct DMChatView: View, KeyboardReadable { } var Header: some View { - let profile = damus_state.profiles.lookup(id: pubkey) return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) { HStack { ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) - ProfileName(pubkey: pubkey, profile: profile, damus: damus_state) + ProfileName(pubkey: pubkey, damus: damus_state) } } .buttonStyle(PlainButtonStyle()) diff --git a/damus/Views/Events/Components/EventTop.swift b/damus/Views/Events/Components/EventTop.swift index 1edfeece..f2b5bad1 100644 --- a/damus/Views/Events/Components/EventTop.swift +++ b/damus/Views/Events/Components/EventTop.swift @@ -22,9 +22,8 @@ struct EventTop: View { } func ProfileName(is_anon: Bool) -> some View { - let profile = state.profiles.lookup(id: self.pubkey) let pk = is_anon ? ANON_PUBKEY : self.pubkey - return EventProfileName(pubkey: pk, profile: profile, damus: state, size: .normal) + return EventProfileName(pubkey: pk, damus: state, size: .normal) } var body: some View { diff --git a/damus/Views/Events/Components/ReplyDescription.swift b/damus/Views/Events/Components/ReplyDescription.swift index 44a98146..e6afa36f 100644 --- a/damus/Views/Events/Components/ReplyDescription.swift +++ b/damus/Views/Events/Components/ReplyDescription.swift @@ -11,10 +11,10 @@ import SwiftUI struct ReplyDescription: View { let event: NostrEvent let replying_to: NostrEvent? - let profiles: Profiles - + let ndb: Ndb + var body: some View { - Text(verbatim: "\(reply_desc(profiles: profiles, event: event, replying_to: replying_to))") + Text(verbatim: "\(reply_desc(ndb: ndb, event: event, replying_to: replying_to))") .font(.footnote) .foregroundColor(.gray) .frame(maxWidth: .infinity, alignment: .leading) @@ -23,11 +23,11 @@ struct ReplyDescription: View { struct ReplyDescription_Previews: PreviewProvider { static var previews: some View { - ReplyDescription(event: test_note, replying_to: test_note, profiles: test_damus_state().profiles) + ReplyDescription(event: test_note, replying_to: test_note, ndb: test_damus_state().ndb) } } -func reply_desc(profiles: Profiles, event: NostrEvent, replying_to: NostrEvent?, locale: Locale = Locale.current) -> String { +func reply_desc(ndb: Ndb, event: NostrEvent, replying_to: NostrEvent?, locale: Locale = Locale.current) -> String { let desc = make_reply_description(event, replying_to: replying_to) let pubkeys = desc.pubkeys let n = desc.others @@ -38,9 +38,12 @@ func reply_desc(profiles: Profiles, event: NostrEvent, replying_to: NostrEvent?, return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.") } + let profile_txn = NdbTxn(ndb: ndb) + let names: [String] = pubkeys.map { pk in - let prof = profiles.lookup(id: pk) - return Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50) + let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn) + + return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50) } let uniqueNames = NSOrderedSet(array: names).array as! [String] diff --git a/damus/Views/Events/Components/ReplyPart.swift b/damus/Views/Events/Components/ReplyPart.swift index 5fc66680..a97387e4 100644 --- a/damus/Views/Events/Components/ReplyPart.swift +++ b/damus/Views/Events/Components/ReplyPart.swift @@ -11,7 +11,7 @@ struct ReplyPart: View { let events: EventCache let event: NostrEvent let keypair: Keypair - let profiles: Profiles + let ndb: Ndb var replying_to: NostrEvent? { guard let note_ref = event.event_refs(keypair).first(where: { evref in evref.is_direct_reply != nil })?.is_direct_reply else { @@ -24,7 +24,7 @@ struct ReplyPart: View { var body: some View { Group { if event_is_reply(event.event_refs(keypair)) { - ReplyDescription(event: event, replying_to: replying_to, profiles: profiles) + ReplyDescription(event: event, replying_to: replying_to, ndb: ndb) } else { EmptyView() } @@ -34,6 +34,6 @@ struct ReplyPart: View { struct ReplyPart_Previews: PreviewProvider { static var previews: some View { - ReplyPart(events: test_damus_state().events, event: test_note, keypair: Keypair(pubkey: .empty, privkey: nil), profiles: test_damus_state().profiles) + ReplyPart(events: test_damus_state().events, event: test_note, keypair: Keypair(pubkey: .empty, privkey: nil), ndb: test_damus_state().ndb) } } diff --git a/damus/Views/Events/EventMenu.swift b/damus/Views/Events/EventMenu.swift index ea707728..0c2aead7 100644 --- a/damus/Views/Events/EventMenu.swift +++ b/damus/Views/Events/EventMenu.swift @@ -52,8 +52,9 @@ struct MenuItems: View { let target_pubkey: Pubkey let bookmarks: BookmarksManager let muted_threads: MutedThreadsManager + @ObservedObject var settings: UserSettingsStore - + @State private var isBookmarked: Bool = false @State private var isMutedThread: Bool = false diff --git a/damus/Views/Events/EventProfile.swift b/damus/Views/Events/EventProfile.swift index 18533ee9..c92bbf07 100644 --- a/damus/Views/Events/EventProfile.swift +++ b/damus/Views/Events/EventProfile.swift @@ -25,7 +25,6 @@ func eventview_pfp_size(_ size: EventViewKind) -> CGFloat { struct EventProfile: View { let damus_state: DamusState let pubkey: Pubkey - let profile: Profile? let size: EventViewKind var pfp_size: CGFloat { @@ -44,7 +43,7 @@ struct EventProfile: View { } VStack(alignment: .leading, spacing: 0) { - EventProfileName(pubkey: pubkey, profile: profile, damus: damus_state, size: size) + EventProfileName(pubkey: pubkey, damus: damus_state, size: size) UserStatusView(status: damus_state.profiles.profile_data(pubkey).status, show_general: damus_state.settings.show_general_statuses, show_music: damus_state.settings.show_music_statuses) } @@ -54,6 +53,6 @@ struct EventProfile: View { struct EventProfile_Previews: PreviewProvider { static var previews: some View { - EventProfile(damus_state: test_damus_state(), pubkey: test_note.pubkey, profile: nil, size: .normal) + EventProfile(damus_state: test_damus_state(), pubkey: test_note.pubkey, size: .normal) } } diff --git a/damus/Views/Events/EventShell.swift b/damus/Views/Events/EventShell.swift index 55d116e4..01f1d345 100644 --- a/damus/Views/Events/EventShell.swift +++ b/damus/Views/Events/EventShell.swift @@ -72,7 +72,7 @@ struct EventShell: View { UserStatusView(status: state.profiles.profile_data(pubkey).status, show_general: state.settings.show_general_statuses, show_music: state.settings.show_music_statuses) if !options.contains(.no_replying_to) { - ReplyPart(events: state.events, event: event, keypair: state.keypair, profiles: state.profiles) + ReplyPart(events: state.events, event: event, keypair: state.keypair, ndb: state.ndb) } content @@ -99,7 +99,7 @@ struct EventShell: View { VStack(alignment: .leading, spacing: 2) { EventTop(state: state, event: event, pubkey: pubkey, is_anon: is_anon) UserStatusView(status: state.profiles.profile_data(pubkey).status, show_general: state.settings.show_general_statuses, show_music: state.settings.show_music_statuses) - ReplyPart(events: state.events, event: event, keypair: state.keypair, profiles: state.profiles) + ReplyPart(events: state.events, event: event, keypair: state.keypair, ndb: state.ndb) } } .padding(.horizontal) diff --git a/damus/Views/Events/SelectedEventView.swift b/damus/Views/Events/SelectedEventView.swift index 31f73428..75ad2eb6 100644 --- a/damus/Views/Events/SelectedEventView.swift +++ b/damus/Views/Events/SelectedEventView.swift @@ -35,11 +35,9 @@ struct SelectedEventView: View { var body: some View { HStack(alignment: .top) { - let profile = damus.profiles.lookup(id: pubkey) - VStack(alignment: .leading) { HStack { - EventProfile(damus_state: damus, pubkey: pubkey, profile: profile, size: .normal) + EventProfile(damus_state: damus, pubkey: pubkey, size: .normal) Spacer() @@ -51,7 +49,7 @@ struct SelectedEventView: View { .lineLimit(1) if event_is_reply(event.event_refs(damus.keypair)) { - ReplyDescription(event: event, replying_to: replying_to, profiles: damus.profiles) + ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb) .padding(.horizontal) } diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index 00cb7e91..0c221703 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -273,7 +273,8 @@ func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText switch m.ref { case .pubkey(let pk): let npub = bech32_pubkey(pk) - let profile = profiles.lookup(id: pk) + let profile_txn = profiles.lookup(id: pk) + let profile = profile_txn.unsafeUnownedValue let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50) var attributedString = AttributedString(stringLiteral: "@\(disp)") attributedString.link = URL(string: "damus:nostr:\(npub)") diff --git a/damus/Views/Notifications/EventGroupView.swift b/damus/Views/Notifications/EventGroupView.swift index 91f4d7a9..b0f536a5 100644 --- a/damus/Views/Notifications/EventGroupView.swift +++ b/damus/Views/Notifications/EventGroupView.swift @@ -69,8 +69,8 @@ func determine_reacting_to(our_pubkey: Pubkey, ev: NostrEvent?) -> ReactingTo { } func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String { - let alice_prof = profiles.lookup(id: pubkey) - return Profile.displayName(profile: alice_prof, pubkey: pubkey).username.truncate(maxLength: 50) + let alice_prof_txn = profiles.lookup(id: pubkey).unsafeUnownedValue + return Profile.displayName(profile: alice_prof_txn, pubkey: pubkey).username.truncate(maxLength: 50) } func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [Pubkey] { diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift index 76b5500e..d33a5290 100644 --- a/damus/Views/Notifications/NotificationsView.swift +++ b/damus/Views/Notifications/NotificationsView.swift @@ -86,8 +86,10 @@ struct NotificationsView: View { @Environment(\.colorScheme) var colorScheme var mystery: some View { - VStack(spacing: 20) { - Text("Wake up, \(Profile.displayName(profile: state.profiles.lookup(id: state.pubkey), pubkey: state.pubkey).displayName.truncate(maxLength: 50))", comment: "Text telling the user to wake up, where the argument is their display name.") + let profile_txn = state.profiles.lookup(id: state.pubkey) + let profile = profile_txn.unsafeUnownedValue + return VStack(spacing: 20) { + Text("Wake up, \(Profile.displayName(profile: profile, pubkey: state.pubkey).displayName.truncate(maxLength: 50))", comment: "Text telling the user to wake up, where the argument is their display name.") Text("You are dreaming...", comment: "Text telling the user that they are dreaming.") } .id("what") diff --git a/damus/Views/Onboarding/SuggestedUserView.swift b/damus/Views/Onboarding/SuggestedUserView.swift index b30d134b..10c89fc7 100644 --- a/damus/Views/Onboarding/SuggestedUserView.swift +++ b/damus/Views/Onboarding/SuggestedUserView.swift @@ -12,14 +12,10 @@ struct SuggestedUser { let name: String let about: String let pfp: URL - let profile: Profile - init?(profile: Profile, pubkey: Pubkey) { - - guard let name = profile.name, - let about = profile.about, - let picture = profile.picture, - let pfpURL = URL(string: picture) else { + init?(name: String?, about: String?, picture: String?, pubkey: Pubkey) { + guard let name, let about, let picture, + let pfpURL = URL(string: picture) else { return nil } @@ -27,12 +23,10 @@ struct SuggestedUser { self.name = name self.about = about self.pfp = pfpURL - self.profile = profile } } struct SuggestedUserView: View { - let user: SuggestedUser let damus_state: DamusState @@ -47,7 +41,7 @@ struct SuggestedUserView: View { disable_animation: false) VStack(alignment: .leading, spacing: 4) { HStack { - ProfileName(pubkey: user.pubkey, profile: user.profile, damus: damus_state) + ProfileName(pubkey: user.pubkey, damus: damus_state) } Text(user.about) .lineLimit(3) @@ -62,9 +56,10 @@ struct SuggestedUserView: View { struct SuggestedUserView_Previews: PreviewProvider { static var previews: some View { - let profile = Profile(name: "klabo", about: "A person who likes nostr a lot and I like to tell people about myself in very long-winded ways that push the limits of UI and almost break things", picture: "https://primal.b-cdn.net/media-cache?s=m&a=1&u=https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F1599994711430742017%2F33zLk9Wi_400x400.jpg") + let pfp = "https://primal.b-cdn.net/media-cache?s=m&a=1&u=https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F1599994711430742017%2F33zLk9Wi_400x400.jpg" + let profile = Profile(name: "klabo", about: "A person who likes nostr a lot and I like to tell people about myself in very long-winded ways that push the limits of UI and almost break things", picture: pfp) - let user = SuggestedUser(profile: profile, pubkey: test_pubkey)! + let user = SuggestedUser(name: "klabo", about: "name", picture: "about", pubkey: test_pubkey)! List { SuggestedUserView(user: user, damus_state: test_damus_state()) } diff --git a/damus/Views/Onboarding/SuggestedUsersViewModel.swift b/damus/Views/Onboarding/SuggestedUsersViewModel.swift index 9c7af000..b4d91c82 100644 --- a/damus/Views/Onboarding/SuggestedUsersViewModel.swift +++ b/damus/Views/Onboarding/SuggestedUsersViewModel.swift @@ -35,8 +35,9 @@ class SuggestedUsersViewModel: ObservableObject { } func suggestedUser(pubkey: Pubkey) -> SuggestedUser? { - if let profile = damus_state.profiles.lookup(id: pubkey), - let user = SuggestedUser(profile: profile, pubkey: pubkey) { + let profile_txn = damus_state.profiles.lookup(id: pubkey) + if let profile = profile_txn.unsafeUnownedValue, + let user = SuggestedUser(name: profile.name, about: profile.about, picture: profile.picture, pubkey: pubkey) { return user } return nil diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index f82a3ffb..1066bec8 100644 --- a/damus/Views/PostView.swift +++ b/damus/Views/PostView.swift @@ -173,7 +173,8 @@ struct PostView: View { return .init(string: "") } - let profile = damus_state.profiles.lookup(id: pubkey) + let profile_txn = damus_state.profiles.lookup(id: pubkey) + let profile = profile_txn.unsafeUnownedValue return user_tag_attr_string(profile: profile, pubkey: pubkey) } diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift index 54908a31..66d2be41 100644 --- a/damus/Views/Posting/UserSearch.swift +++ b/damus/Views/Posting/UserSearch.swift @@ -7,15 +7,6 @@ import SwiftUI -struct SearchedUser: Identifiable { - let profile: Profile? - let pubkey: Pubkey - - var id: Pubkey { - return pubkey - } -} - struct UserSearch: View { let damus_state: DamusState let search: String @@ -25,13 +16,14 @@ struct UserSearch: View { @Binding var post: NSMutableAttributedString @EnvironmentObject var tagModel: TagModel - var users: [SearchedUser] { + var users: [Pubkey] { return search_profiles(profiles: damus_state.profiles, search: search) } - func on_user_tapped(user: SearchedUser) { - let pk = user.pubkey - let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk) + func on_user_tapped(pk: Pubkey) { + let profile_txn = damus_state.profiles.lookup(id: pk) + let profile = profile_txn.unsafeUnownedValue + let user_tag = user_tag_attr_string(profile: profile, pubkey: pk) appendUserTag(withTag: user_tag) } @@ -57,11 +49,11 @@ struct UserSearch: View { if users.count == 0 { EmptyUserSearchView() } else { - ForEach(users) { user in - UserView(damus_state: damus_state, pubkey: user.pubkey) + ForEach(users) { pk in + UserView(damus_state: damus_state, pubkey: pk) .contentShape(Rectangle()) .onTapGesture { - on_user_tapped(user: user) + on_user_tapped(pk: pk) } } } diff --git a/damus/Views/Profile/EditMetadataView.swift b/damus/Views/Profile/EditMetadataView.swift index a7c8186f..ceceb1c4 100644 --- a/damus/Views/Profile/EditMetadataView.swift +++ b/damus/Views/Profile/EditMetadataView.swift @@ -20,8 +20,7 @@ struct EditMetadataView: View { @State var name: String @State var ln: String @State var website: String - let profile: Profile? - + @Environment(\.dismiss) var dismiss @State var confirm_ln_address: Bool = false @@ -31,9 +30,8 @@ struct EditMetadataView: View { init(damus_state: DamusState) { self.damus_state = damus_state - let data = damus_state.profiles.lookup(id: damus_state.pubkey) - self.profile = data - + let data = damus_state.profiles.lookup(id: damus_state.pubkey).unsafeUnownedValue + _name = State(initialValue: data?.name ?? "") _display_name = State(initialValue: data?.display_name ?? "") _about = State(initialValue: data?.about ?? "") diff --git a/damus/Views/Profile/EventProfileName.swift b/damus/Views/Profile/EventProfileName.swift index 9d478898..a1298f39 100644 --- a/damus/Views/Profile/EventProfileName.swift +++ b/damus/Views/Profile/EventProfileName.swift @@ -12,20 +12,19 @@ import SwiftUI struct EventProfileName: View { let damus_state: DamusState let pubkey: Pubkey - let profile: Profile? - + @State var display_name: DisplayName? @State var nip05: NIP05? @State var donation: Int? let size: EventViewKind - init(pubkey: Pubkey, profile: Profile?, damus: DamusState, size: EventViewKind = .normal) { + init(pubkey: Pubkey, damus: DamusState, size: EventViewKind = .normal) { self.damus_state = damus self.pubkey = pubkey - self.profile = profile self.size = size - self._donation = State(wrappedValue: profile?.damus_donation) + let donation = damus.ndb.lookup_profile(pubkey).map({ p in p?.profile?.damus_donation }).value + self._donation = State(wrappedValue: donation) } var friend_type: FriendType? { @@ -36,11 +35,11 @@ struct EventProfileName: View { nip05 ?? damus_state.profiles.is_validated(pubkey) } - var current_display_name: DisplayName { + func current_display_name(_ profile: Profile?) -> DisplayName { return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey) } - var onlyzapper: Bool { + func onlyzapper(_ profile: Profile?) -> Bool { guard let profile else { return false } @@ -58,8 +57,10 @@ struct EventProfileName: View { } var body: some View { + let profile_txn = damus_state.profiles.lookup(id: pubkey) + let profile = profile_txn.unsafeUnownedValue HStack(spacing: 2) { - switch current_display_name { + switch current_display_name(profile) { case .one(let one): Text(one) .font(.body.weight(.bold)) @@ -84,7 +85,7 @@ struct EventProfileName: View { FriendIcon(friend: frend) } - if onlyzapper { + if onlyzapper(profile) { Image("zap-hashtag") .frame(width: 14, height: 14) } @@ -97,9 +98,24 @@ struct EventProfileName: View { if update.pubkey != pubkey { return } - display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) - nip05 = damus_state.profiles.is_validated(pubkey) - donation = update.profile.damus_donation + + let profile_txn = damus_state.profiles.lookup(id: update.pubkey) + guard let profile = profile_txn.unsafeUnownedValue else { return } + + let display_name = Profile.displayName(profile: profile, pubkey: pubkey) + if display_name != self.display_name { + self.display_name = display_name + } + + let nip05 = damus_state.profiles.is_validated(pubkey) + + if self.nip05 != nip05 { + self.nip05 = nip05 + } + + if self.donation != profile.damus_donation { + donation = profile.damus_donation + } } } } @@ -107,6 +123,6 @@ struct EventProfileName: View { struct EventProfileName_Previews: PreviewProvider { static var previews: some View { - EventProfileName(pubkey: test_note.pubkey, profile: nil, damus: test_damus_state()) + EventProfileName(pubkey: test_note.pubkey, damus: test_damus_state()) } } diff --git a/damus/Views/Profile/ProfileName.swift b/damus/Views/Profile/ProfileName.swift index 18a87b5b..41d6a8f0 100644 --- a/damus/Views/Profile/ProfileName.swift +++ b/damus/Views/Profile/ProfileName.swift @@ -27,7 +27,6 @@ func get_friend_type(contacts: Contacts, pubkey: Pubkey) -> FriendType? { struct ProfileName: View { let damus_state: DamusState let pubkey: Pubkey - let profile: Profile? let prefix: String let show_nip5_domain: Bool @@ -36,9 +35,8 @@ struct ProfileName: View { @State var nip05: NIP05? @State var donation: Int? - init(pubkey: Pubkey, profile: Profile?, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true) { + init(pubkey: Pubkey, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true) { self.pubkey = pubkey - self.profile = profile self.prefix = prefix self.damus_state = damus self.show_nip5_domain = show_nip5_domain @@ -53,15 +51,15 @@ struct ProfileName: View { nip05 ?? damus_state.profiles.is_validated(pubkey) } - var current_display_name: DisplayName { + func current_display_name(profile: Profile?) -> DisplayName { return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey) } - var name_choice: String { - return prefix == "@" ? current_display_name.username.truncate(maxLength: 50) : current_display_name.displayName.truncate(maxLength: 50) + func name_choice(profile: Profile?) -> String { + return prefix == "@" ? current_display_name(profile: profile).username.truncate(maxLength: 50) : current_display_name(profile: profile).displayName.truncate(maxLength: 50) } - var onlyzapper: Bool { + func onlyzapper(profile: Profile?) -> Bool { guard let profile else { return false } @@ -69,7 +67,7 @@ struct ProfileName: View { return profile.reactions == false } - var supporter: Int? { + func supporter(profile: Profile?) -> Int? { guard let profile, let donation = profile.damus_donation, donation > 0 @@ -81,21 +79,28 @@ struct ProfileName: View { } var body: some View { + let profile_txn = damus_state.profiles.lookup(id: pubkey) + let profile = profile_txn.unsafeUnownedValue + HStack(spacing: 2) { - Text(verbatim: "\(prefix)\(name_choice)") + Text(verbatim: "\(prefix)\(name_choice(profile: profile))") .font(.body) .fontWeight(prefix == "@" ? .none : .bold) + if let nip05 = current_nip05 { NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, profiles: damus_state.profiles) } + if let friend = friend_type, current_nip05 == nil { FriendIcon(friend: friend) } - if onlyzapper { + + if onlyzapper(profile: profile) { Image("zap-hashtag") .frame(width: 14, height: 14) } - if let supporter { + + if let supporter = supporter(profile: profile) { SupporterBadge(percent: supporter) } } @@ -103,16 +108,38 @@ struct ProfileName: View { if update.pubkey != pubkey { return } - display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) - nip05 = damus_state.profiles.is_validated(pubkey) - donation = profile?.damus_donation + + var profile: Profile! + var profile_txn: NdbTxn! + + switch update { + case .remote(let pubkey): + profile_txn = damus_state.profiles.lookup(id: pubkey) + guard let prof = profile_txn.unsafeUnownedValue else { return } + profile = prof + case .manual(_, let prof): + profile = prof + } + + let display_name = Profile.displayName(profile: profile, pubkey: pubkey) + if self.display_name != display_name { + self.display_name = display_name + } + + let nip05 = damus_state.profiles.is_validated(pubkey) + if nip05 != self.nip05 { + self.nip05 = nip05 + } + + if donation != profile.damus_donation { + donation = profile.damus_donation + } } } } struct ProfileName_Previews: PreviewProvider { static var previews: some View { - ProfileName(pubkey: - test_damus_state().pubkey, profile: make_test_profile(), damus: test_damus_state()) + ProfileName(pubkey: test_damus_state().pubkey, damus: test_damus_state()) } } diff --git a/damus/Views/Profile/ProfileNameView.swift b/damus/Views/Profile/ProfileNameView.swift index 0d1c523c..9d6efd0e 100644 --- a/damus/Views/Profile/ProfileNameView.swift +++ b/damus/Views/Profile/ProfileNameView.swift @@ -87,7 +87,6 @@ fileprivate struct KeyView: View { struct ProfileNameView: View { let pubkey: Pubkey - let profile: Profile? let damus: DamusState var spacing: CGFloat { 10.0 } @@ -95,10 +94,13 @@ struct ProfileNameView: View { var body: some View { Group { VStack(alignment: .leading) { + let profile_txn = self.damus.profiles.lookup(id: pubkey) + let profile = profile_txn.unsafeUnownedValue + switch Profile.displayName(profile: profile, pubkey: pubkey) { case .one: HStack(alignment: .center, spacing: spacing) { - ProfileName(pubkey: pubkey, profile: profile, damus: damus) + ProfileName(pubkey: pubkey, damus: damus) .font(.title3.weight(.bold)) } case .both(username: _, displayName: let displayName): @@ -106,7 +108,7 @@ struct ProfileNameView: View { .font(.title3.weight(.bold)) HStack(alignment: .center, spacing: spacing) { - ProfileName(pubkey: pubkey, profile: profile, prefix: "@", damus: damus) + ProfileName(pubkey: pubkey, prefix: "@", damus: damus) .font(.callout) .foregroundColor(.gray) } @@ -124,9 +126,9 @@ struct ProfileNameView: View { struct ProfileNameView_Previews: PreviewProvider { static var previews: some View { VStack { - ProfileNameView(pubkey: test_note.pubkey, profile: nil, damus: test_damus_state()) + ProfileNameView(pubkey: test_note.pubkey, damus: test_damus_state()) - ProfileNameView(pubkey: test_note.pubkey, profile: nil, damus: test_damus_state()) + ProfileNameView(pubkey: test_note.pubkey, damus: test_damus_state()) } } } diff --git a/damus/Views/Profile/ProfilePicView.swift b/damus/Views/Profile/ProfilePicView.swift index 2a3c346b..00184a56 100644 --- a/damus/Views/Profile/ProfilePicView.swift +++ b/damus/Views/Profile/ProfilePicView.swift @@ -87,16 +87,25 @@ struct ProfilePicView: View { guard updated.pubkey == self.pubkey else { return } - - if let pic = updated.profile.picture { - self.picture = pic + + switch updated { + case .manual(_, let profile): + if let pic = profile.picture { + self.picture = pic + } + case .remote(pubkey: let pk): + let profile_txn = profiles.lookup(id: pk) + let profile = profile_txn.unsafeUnownedValue + if let pic = profile?.picture { + self.picture = pic + } } } } } func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> URL { - let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey) + let pic = picture ?? profiles.lookup(id: pubkey).map({ $0?.picture }).value ?? robohash(pubkey) if let url = URL(string: pic) { return url } diff --git a/damus/Views/Profile/ProfilePictureSelector.swift b/damus/Views/Profile/ProfilePictureSelector.swift index 119c3eff..77fc2d0f 100644 --- a/damus/Views/Profile/ProfilePictureSelector.swift +++ b/damus/Views/Profile/ProfilePictureSelector.swift @@ -43,7 +43,7 @@ struct EditProfilePictureView: View { if let profile_url { return profile_url } else if let state = damus_state, - let picture = state.profiles.lookup(id: pubkey)?.picture { + let picture = state.profiles.lookup(id: pubkey).map({ pr in pr?.picture }).value { return URL(string: picture) } else { return profile_url ?? URL(string: robohash(pubkey)) diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift index db928e48..2a1339ac 100644 --- a/damus/Views/Profile/ProfileView.swift +++ b/damus/Views/Profile/ProfileView.swift @@ -31,11 +31,11 @@ func follow_btn_txt(_ fs: FollowState, follows_you: Bool) -> String { } } -func followedByString(_ friend_intersection: [Pubkey], profiles: Profiles, locale: Locale = Locale.current) -> String { +func followedByString(txn: NdbTxn, _ friend_intersection: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String { let bundle = bundleForLocale(locale: locale) - let names: [String] = friend_intersection.prefix(3).map { - let profile = profiles.lookup(id: $0) - return Profile.displayName(profile: profile, pubkey: $0).username.truncate(maxLength: 20) + let names: [String] = friend_intersection.prefix(3).map { pk in + let profile = ndb.lookup_profile_with_txn(pk, txn: txn)?.profile + return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20) } switch friend_intersection.count { @@ -216,27 +216,29 @@ struct ProfileView: View { .accentColor(DamusColors.white) } - func lnButton(lnurl: String, record: ProfileRecord, profile: Profile) -> some View { - let profile = record.profile! - let button_img = profile.reactions == false ? "zap.fill" : "zap" - return Button(action: { + func lnButton(lnurl: String, unownedProfile: Profile?, pubkey: Pubkey) -> some View { + let reactions = unownedProfile?.reactions ?? true + let button_img = reactions ? "zap.fill" : "zap" + let lud16 = unownedProfile?.lud16 + + return Button(action: { [lnurl] in present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl)) }) { Image(button_img) .foregroundColor(button_img == "zap.fill" ? .orange : Color.primary) .profile_button_style(scheme: colorScheme) - .contextMenu { - if profile.reactions == false { + .contextMenu { [lud16, reactions, lnurl] in + if reactions == false { Text("OnlyZaps Enabled", comment: "Non-tappable text in context menu that shows up when the zap button on profile is long pressed to indicate that the user has enabled OnlyZaps, meaning that they would like to be only zapped and not accept reactions to their notes.") } - if let addr = profile.lud16 { + if let lud16 { Button { - UIPasteboard.general.string = addr + UIPasteboard.general.string = lud16 } label: { - Label(addr, image: "copy2") + Label(lud16, image: "copy2") } - } else if let lnurl = record.lnurl { + } else { Button { UIPasteboard.general.string = lnurl } label: { @@ -269,14 +271,14 @@ struct ProfileView: View { .font(.footnote) } - func actionSection(record: ProfileRecord?) -> some View { + func actionSection(record: ProfileRecord?, pubkey: Pubkey) -> some View { return Group { if let record, let profile = record.profile, let lnurl = record.lnurl, lnurl != "" { - lnButton(lnurl: lnurl, record: record, profile: profile) + lnButton(lnurl: lnurl, unownedProfile: profile, pubkey: pubkey) } dmButton @@ -311,6 +313,7 @@ struct ProfileView: View { func nameSection(profile_data: ProfileRecord?) -> some View { return Group { let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey) + HStack(alignment: .center) { ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) .padding(.top, -(pfp_size / 2.0)) @@ -329,10 +332,10 @@ struct ProfileView: View { followsYouBadge } - actionSection(record: profile_data) + actionSection(record: profile_data, pubkey: profile.pubkey) } - ProfileNameView(pubkey: profile.pubkey, profile: profile_data?.profile, damus: damus_state) + ProfileNameView(pubkey: profile.pubkey, damus: damus_state) } } @@ -355,7 +358,8 @@ struct ProfileView: View { var aboutSection: some View { VStack(alignment: .leading, spacing: 8.0) { - let profile_data = damus_state.profiles.lookup_with_timestamp(profile.pubkey) + let profile_txn = damus_state.profiles.lookup_with_timestamp(profile.pubkey) + let profile_data = profile_txn.unsafeUnownedValue nameSection(profile_data: profile_data) @@ -422,7 +426,7 @@ struct ProfileView: View { NavigationLink(value: Route.FollowersYouKnow(friendedFollowers: friended_followers, followers: followers)) { HStack { CondensedProfilePicturesView(state: damus_state, pubkeys: friended_followers, maxPictures: 3) - let followedByString = followedByString(friended_followers, profiles: damus_state.profiles) + let followedByString = followedByString(txn: profile_txn, friended_followers, ndb: damus_state.ndb) Text(followedByString) .font(.subheadline).foregroundColor(.gray) .multilineTextAlignment(.leading) @@ -516,7 +520,9 @@ extension View { @MainActor func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) { - guard let profile = profiles.lookup(id: pubkey), + let profile_txn = profiles.lookup(id: pubkey) + + guard let profile = profile_txn.unsafeUnownedValue, let nip05 = profile.nip05, profiles.is_validated(pubkey) == nil else { @@ -532,7 +538,7 @@ func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) { Task { @MainActor in profiles.set_validated(pubkey, nip05: validated) profiles.nip05_pubkey[nip05] = pubkey - notify(.profile_updated(pubkey: pubkey, profile: profile)) + notify(.profile_updated(.remote(pubkey: pubkey))) } } } diff --git a/damus/Views/QRCodeView.swift b/damus/Views/QRCodeView.swift index a1bdf22d..54410bcf 100644 --- a/damus/Views/QRCodeView.swift +++ b/damus/Views/QRCodeView.swift @@ -118,9 +118,11 @@ struct QRCodeView: View { var QRView: some View { VStack(alignment: .center) { - let profile = damus_state.profiles.lookup(id: pubkey) - - if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil { + let profile_txn = damus_state.profiles.lookup(id: pubkey) + let profile = profile_txn.unsafeUnownedValue + let our_profile = damus_state.ndb.lookup_profile_with_txn(damus_state.pubkey, txn: profile_txn) + + if our_profile?.profile?.picture != nil { ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) .padding(.top, 50) } else { diff --git a/damus/Views/ReplyView.swift b/damus/Views/ReplyView.swift index 916b79c5..188993bd 100644 --- a/damus/Views/ReplyView.swift +++ b/damus/Views/ReplyView.swift @@ -24,10 +24,11 @@ struct ReplyView: View { var ReplyingToSection: some View { HStack { Group { + let txn = NdbTxn(ndb: damus.ndb) let names = references .map { pubkey in let pk = pubkey - let prof = damus.profiles.lookup(id: pk) + let prof = damus.ndb.lookup_profile_with_txn(pk, txn: txn)?.profile return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50) } .joined(separator: " ") diff --git a/damus/Views/Reposts/RepostedEvent.swift b/damus/Views/Reposts/RepostedEvent.swift index edf92385..7dda0c34 100644 --- a/damus/Views/Reposts/RepostedEvent.swift +++ b/damus/Views/Reposts/RepostedEvent.swift @@ -15,10 +15,8 @@ struct RepostedEvent: View { var body: some View { VStack(alignment: .leading) { - let prof = damus.profiles.lookup(id: event.pubkey) - NavigationLink(value: Route.ProfileByKey(pubkey: event.pubkey)) { - Reposted(damus: damus, pubkey: event.pubkey, profile: prof) + Reposted(damus: damus, pubkey: event.pubkey) .padding(.horizontal) } .buttonStyle(PlainButtonStyle()) diff --git a/damus/Views/Search/SearchingEventView.swift b/damus/Views/Search/SearchingEventView.swift index 038479de..9b79eec4 100644 --- a/damus/Views/Search/SearchingEventView.swift +++ b/damus/Views/Search/SearchingEventView.swift @@ -44,23 +44,25 @@ struct SearchingEventView: View { switch search { case .nip05(let nip05): if let pk = state.profiles.nip05_pubkey[nip05] { - if state.profiles.lookup(id: pk) != nil { + if state.profiles.lookup_key_by_pubkey(pk) != nil { self.search_state = .found_profile(pk) } } else { Task { guard let nip05 = NIP05.parse(nip05) else { - self.search_state = .not_found + Task { @MainActor in + self.search_state = .not_found + } return } guard let nip05_resp = await fetch_nip05(nip05: nip05) else { - DispatchQueue.main.async { + Task { @MainActor in self.search_state = .not_found } return } - DispatchQueue.main.async { + Task { @MainActor in guard let pk = nip05_resp.names[nip05.username] else { self.search_state = .not_found return @@ -81,11 +83,11 @@ struct SearchingEventView: View { } case .profile(let pubkey): find_event(state: state, query: .profile(pubkey: pubkey)) { res in - guard case .profile(_, let ev) = res else { + guard case .profile(let pubkey) = res else { self.search_state = .not_found return } - self.search_state = .found_profile(ev.pubkey) + self.search_state = .found_profile(pubkey) } } } diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift index 06c7659a..85db46a2 100644 --- a/damus/Views/SearchResultsView.swift +++ b/damus/Views/SearchResultsView.swift @@ -9,11 +9,11 @@ import SwiftUI struct MultiSearch { let hashtag: String - let profiles: [SearchedUser] + let profiles: [Pubkey] } enum Search: Identifiable { - case profiles([SearchedUser]) + case profiles([Pubkey]) case hashtag(String) case profile(Pubkey) case note(NoteId) @@ -49,10 +49,10 @@ struct InnerSearchResults: View { } } - func ProfilesSearch(_ results: [SearchedUser]) -> some View { + func ProfilesSearch(_ results: [Pubkey]) -> some View { return LazyVStack { - ForEach(results) { prof in - ProfileSearchResult(pk: prof.pubkey) + ForEach(results, id: \.id) { pk in + ProfileSearchResult(pk: pk) } } } @@ -171,27 +171,23 @@ func make_hashtagable(_ str: String) -> String { return String(new.filter{$0 != " "}) } -func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] { +func search_profiles(profiles: Profiles, search: String) -> [Pubkey] { // Search by hex pubkey. if let pubkey = hex_decode_pubkey(search), - let profile = profiles.lookup(id: pubkey) + profiles.lookup_key_by_pubkey(pubkey) != nil { - return [SearchedUser(profile: profile, pubkey: pubkey)] + return [pubkey] } // Search by npub pubkey. if search.starts(with: "npub"), let bech32_key = decode_bech32_key(search), case Bech32Key.pub(let pk) = bech32_key, - let profile = profiles.lookup(id: pk) + profiles.lookup_key_by_pubkey(pk) != nil { - return [SearchedUser(profile: profile, pubkey: pk)] + return [pk] } let new = search.lowercased() - let matched_pubkeys = profiles.user_search_cache.search(key: new) - - return matched_pubkeys - .map { SearchedUser(profile: profiles.lookup(id: $0), pubkey: $0) } - .filter { $0.profile != nil } + return profiles.user_search_cache.search(key: new) } diff --git a/damus/Views/SideMenuView.swift b/damus/Views/SideMenuView.swift index d6fcf630..7b9e53d4 100644 --- a/damus/Views/SideMenuView.swift +++ b/damus/Views/SideMenuView.swift @@ -83,7 +83,8 @@ struct SideMenuView: View { } var TopProfile: some View { - let profile = damus_state.profiles.lookup(id: damus_state.pubkey) + let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) + let profile = profile_txn.unsafeUnownedValue return VStack(alignment: .leading, spacing: verticalSpacing) { HStack { ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift index 3e8bc976..e00a2f36 100644 --- a/damus/Views/Wallet/WalletView.swift +++ b/damus/Views/Wallet/WalletView.swift @@ -145,7 +145,7 @@ struct WalletView: View { Spacer() } - EventProfile(damus_state: damus_state, pubkey: damus_state.pubkey, profile: damus_state.profiles.lookup(id: damus_state.pubkey), size: .small) + EventProfile(damus_state: damus_state, pubkey: damus_state.pubkey, size: .small) } .padding(25) } @@ -164,17 +164,20 @@ struct WalletView: View { model.initial_percent = settings.donation_percent } .onChange(of: settings.donation_percent) { p in - guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else { + let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) + guard let profile = profile_txn.unsafeUnownedValue else { return } let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: profile.lud16, nip05: profile.nip05, damus_donation: p, reactions: profile.reactions) - notify(.profile_updated(pubkey: damus_state.pubkey, profile: prof)) + notify(.profile_updated(.manual(pubkey: self.damus_state.pubkey, profile: prof))) } .onDisappear { + let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) + guard let keypair = damus_state.keypair.to_full(), - let profile = damus_state.profiles.lookup(id: damus_state.pubkey), + let profile = profile_txn.unsafeUnownedValue, model.initial_percent != profile.damus_donation else { return diff --git a/damus/Views/Zaps/ZapTypePicker.swift b/damus/Views/Zaps/ZapTypePicker.swift index a85b7e91..1a128b71 100644 --- a/damus/Views/Zaps/ZapTypePicker.swift +++ b/damus/Views/Zaps/ZapTypePicker.swift @@ -117,7 +117,8 @@ func zap_type_desc(type: ZapType, profiles: Profiles, pubkey: Pubkey) -> String case .anon: return NSLocalizedString("No one will see that you zapped", comment: "Description of anonymous zap type where the zap is sent anonymously and does not identify the user who sent it.") case .priv: - let prof = profiles.lookup(id: pubkey) + let prof_txn = profiles.lookup(id: pubkey) + let prof = prof_txn.unsafeUnownedValue let name = Profile.displayName(profile: prof, pubkey: pubkey).username.truncate(maxLength: 50) return String.localizedStringWithFormat(NSLocalizedString("private_zap_description", value: "Only '%@' will see that you zapped them", comment: "Description of private zap type where the zap is sent privately and does not identify the user to the public."), name) case .non_zap: diff --git a/nostrdb/Ndb.swift b/nostrdb/Ndb.swift index 4e236e4e..791c7535 100644 --- a/nostrdb/Ndb.swift +++ b/nostrdb/Ndb.swift @@ -49,44 +49,130 @@ class Ndb { self.ndb = ndb } - func lookup_note_by_key(_ key: UInt64) -> NdbNote? { - guard let note_p = ndb_get_note_by_key(ndb.ndb, key, nil) else { + func lookup_note_by_key_with_txn(_ key: NoteKey, txn: NdbTxn) -> NdbNote? { + guard let note_p = ndb_get_note_by_key(&txn.txn, key, nil) else { return nil } - return NdbNote(note: note_p, owned_size: nil) + return NdbNote(note: note_p, owned_size: nil, key: key) } - func lookup_note(_ id: NoteId) -> NdbNote? { - id.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NdbNote? in - guard let baseAddress = ptr.baseAddress, - let note_p = ndb_get_note_by_id(ndb.ndb, baseAddress, nil) else { - return nil - } - return NdbNote(note: note_p, owned_size: nil) + func lookup_note_by_key(_ key: NoteKey) -> NdbTxn { + return NdbTxn(ndb: self) { txn in + lookup_note_by_key_with_txn(key, txn: txn) } } - func lookup_profile(_ pubkey: Pubkey) -> ProfileRecord? { + private func lookup_profile_by_key_inner(_ key: ProfileKey, txn: NdbTxn) -> ProfileRecord? { + var size: Int = 0 + guard let profile_p = ndb_get_profile_by_key(&txn.txn, key, &size) else { + return nil + } + + return profile_flatbuf_to_record(ptr: profile_p, size: size, key: key) + } + + private func profile_flatbuf_to_record(ptr: UnsafeMutableRawPointer, size: Int, key: UInt64) -> ProfileRecord? { + do { + var buf = ByteBuffer(assumingMemoryBound: ptr, capacity: size) + let rec: NdbProfileRecord = try getDebugCheckedRoot(byteBuffer: &buf) + return ProfileRecord(data: rec, key: key) + } catch { + // Handle error appropriately + print("UNUSUAL: \(error)") + return nil + } + } + + private func lookup_note_with_txn_inner(id: NoteId, txn: NdbTxn) -> NdbNote? { + return id.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NdbNote? in + var key: UInt64 = 0 + guard let baseAddress = ptr.baseAddress, + let note_p = ndb_get_note_by_id(&txn.txn, baseAddress, nil, &key) else { + return nil + } + return NdbNote(note: note_p, owned_size: nil, key: key) + } + } + + private func lookup_profile_with_txn_inner(pubkey: Pubkey, txn: NdbTxn) -> ProfileRecord? { return pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> ProfileRecord? in var size: Int = 0 + var key: UInt64 = 0 guard let baseAddress = ptr.baseAddress, - let profile_p = ndb_get_profile_by_pubkey(ndb.ndb, baseAddress, &size) + let profile_p = ndb_get_profile_by_pubkey(&txn.txn, baseAddress, &size, &key) else { return nil } - do { - var buf = ByteBuffer(assumingMemoryBound: profile_p, capacity: size) - let rec: NdbProfileRecord = try getDebugCheckedRoot(byteBuffer: &buf) - return ProfileRecord(data: rec) - } catch { - // Handle error appropriately - print("UNUSUAL: \(error)") - return nil - } + return profile_flatbuf_to_record(ptr: profile_p, size: size, key: key) } } + + func lookup_profile_by_key_with_txn(key: ProfileKey, txn: NdbTxn) -> ProfileRecord? { + lookup_profile_by_key_inner(key, txn: txn) + } + + func lookup_profile_by_key(key: ProfileKey) -> NdbTxn { + return NdbTxn(ndb: self) { txn in + lookup_profile_by_key_inner(key, txn: txn) + } + } + + func lookup_note_with_txn(id: NoteId, txn: NdbTxn) -> NdbNote? { + lookup_note_with_txn_inner(id: id, txn: txn) + } + + func lookup_profile_key(_ pubkey: Pubkey) -> ProfileKey? { + return NdbTxn(ndb: self) { txn in + lookup_profile_key_with_txn(pubkey, txn: txn) + }.value + } + + func lookup_profile_key_with_txn(_ pubkey: Pubkey, txn: NdbTxn) -> ProfileKey? { + return pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NoteKey? in + guard let p = ptr.baseAddress else { return nil } + let r = ndb_get_profilekey_by_pubkey(&txn.txn, p) + if r == 0 { + return nil + } + return r + } + } + + func lookup_note_key_with_txn(_ id: NoteId, txn: NdbTxn) -> NoteKey? { + return id.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NoteKey? in + guard let p = ptr.baseAddress else { + return nil + } + let r = ndb_get_notekey_by_id(&txn.txn, p) + if r == 0 { + return nil + } + return r + } + } + + func lookup_note_key(_ id: NoteId) -> NoteKey? { + NdbTxn(ndb: self, with: { txn in lookup_note_key_with_txn(id, txn: txn) }).value + } + + func lookup_note(_ id: NoteId) -> NdbTxn { + return NdbTxn(ndb: self) { txn in + lookup_note_with_txn_inner(id: id, txn: txn) + } + } + + func lookup_profile(_ pubkey: Pubkey) -> NdbTxn { + return NdbTxn(ndb: self) { txn in + lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn) + } + } + + func lookup_profile_with_txn(_ pubkey: Pubkey, txn: NdbTxn) -> ProfileRecord? { + lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn) + } + func process_event(_ str: String) -> Bool { return str.withCString { cstr in return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0 @@ -106,7 +192,7 @@ class Ndb { #if DEBUG func getDebugCheckedRoot(byteBuffer: inout ByteBuffer) throws -> T { - return try getCheckedRoot(byteBuffer: &byteBuffer) + return try getRoot(byteBuffer: &byteBuffer) } #else func getDebugCheckedRoot(byteBuffer: inout ByteBuffer) throws -> T { diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift index 5f479a23..b46deca3 100644 --- a/nostrdb/NdbNote.swift +++ b/nostrdb/NdbNote.swift @@ -38,6 +38,7 @@ class NdbNote: Encodable, Equatable, Hashable { // we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional private let owned: Bool let count: Int + let key: NoteKey? let note: UnsafeMutablePointer // cached stuff (TODO: remove these) @@ -48,10 +49,11 @@ class NdbNote: Encodable, Equatable, Hashable { return NdbNote.owned_from_json_cstr(json: content_raw, json_len: content_len) }() - init(note: UnsafeMutablePointer, owned_size: Int?) { + init(note: UnsafeMutablePointer, owned_size: Int?, key: NoteKey?) { self.note = note self.owned = owned_size != nil self.count = owned_size ?? 0 + self.key = key #if DEBUG_NOTE_SIZE if let owned_size { @@ -218,6 +220,7 @@ class NdbNote: Encodable, Equatable, Hashable { } self.note = r.assumingMemoryBound(to: ndb_note.self) + self.key = nil } static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? { @@ -245,7 +248,7 @@ class NdbNote: Encodable, Equatable, Hashable { guard let note_data = realloc(data, Int(len)) else { return nil } let new_note = note_data.assumingMemoryBound(to: ndb_note.self) - return NdbNote(note: new_note, owned_size: Int(len)) + return NdbNote(note: new_note, owned_size: Int(len), key: nil) } } diff --git a/nostrdb/NdbTagsIterator.swift b/nostrdb/NdbTagsIterator.swift index e6ecd60d..933100b6 100644 --- a/nostrdb/NdbTagsIterator.swift +++ b/nostrdb/NdbTagsIterator.swift @@ -70,7 +70,7 @@ struct TagsSequence: Encodable, Sequence { } precondition(false, "sequence subscript oob") // it seems like the compiler needs this or it gets bitchy - return .init(note: .init(note: .allocate(capacity: 1), owned_size: nil), tag: .allocate(capacity: 1)) + return .init(note: .init(note: .allocate(capacity: 1), owned_size: nil, key: nil), tag: .allocate(capacity: 1)) } func makeIterator() -> TagsIterator { diff --git a/nostrdb/NdbTxn.swift b/nostrdb/NdbTxn.swift index b053cd91..876fa80a 100644 --- a/nostrdb/NdbTxn.swift +++ b/nostrdb/NdbTxn.swift @@ -17,7 +17,7 @@ class NdbTxn { private var val: T! var moved: Bool - init(ndb: Ndb, with: (NdbTxn) -> T) { + init(ndb: Ndb, with: (NdbTxn) -> T = { _ in () }) { self.txn = ndb_txn() #if TXNDEBUG txn_count += 1 diff --git a/nostrdb/nostrdb.c b/nostrdb/nostrdb.c index 4fce06f1..7e895828 100644 --- a/nostrdb/nostrdb.c +++ b/nostrdb/nostrdb.c @@ -264,7 +264,9 @@ int ndb_get_tsid(MDB_txn *txn, struct ndb_lmdb *lmdb, enum ndb_dbs db, int success = 0; struct ndb_tsid tsid; + // position at the most recent ndb_tsid_high(&tsid, id); + k.mv_data = &tsid; k.mv_size = sizeof(tsid); @@ -341,32 +343,44 @@ static void *ndb_lookup_tsid(struct ndb_txn *txn, enum ndb_dbs ind, return res; } -void *ndb_get_profile_by_pubkey(struct ndb_txn *txn, const unsigned char *pk, size_t *len, uint32_t *key) +void *ndb_get_profile_by_pubkey(struct ndb_txn *txn, const unsigned char *pk, size_t *len, uint64_t *key) { return ndb_lookup_tsid(txn, NDB_DB_PROFILE_PK, NDB_DB_PROFILE, pk, len, key); } -struct ndb_note *ndb_get_note_by_id(struct ndb_txn *txn, const unsigned char *id, size_t *len, uint32_t *key) +struct ndb_note *ndb_get_note_by_id(struct ndb_txn *txn, const unsigned char *id, size_t *len, uint64_t *key) { return ndb_lookup_tsid(txn, NDB_DB_NOTE_ID, NDB_DB_NOTE, id, len, key); } -uint32_t ndb_get_notekey_by_id(struct ndb_txn *txn, const unsigned char *id) +static inline uint64_t ndb_get_indexkey_by_id(struct ndb_txn *txn, + enum ndb_dbs db, + const unsigned char *id) { MDB_val k; - if (!ndb_get_tsid(txn->mdb_txn, &txn->ndb->lmdb, NDB_DB_NOTE_ID, id, &k)) + if (!ndb_get_tsid(txn->mdb_txn, &txn->ndb->lmdb, db, id, &k)) return 0; return *(uint32_t*)k.mv_data; } -struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint32_t key, size_t *len) +uint64_t ndb_get_notekey_by_id(struct ndb_txn *txn, const unsigned char *id) +{ + return ndb_get_indexkey_by_id(txn, NDB_DB_NOTE_ID, id); +} + +uint64_t ndb_get_profilekey_by_pubkey(struct ndb_txn *txn, const unsigned char *id) +{ + return ndb_get_indexkey_by_id(txn, NDB_DB_PROFILE_PK, id); +} + +struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint64_t key, size_t *len) { return ndb_lookup_by_key(txn, key, NDB_DB_NOTE, len); } -void *ndb_get_profile_by_key(struct ndb_txn *txn, uint32_t key, size_t *len) +void *ndb_get_profile_by_key(struct ndb_txn *txn, uint64_t key, size_t *len) { return ndb_lookup_by_key(txn, key, NDB_DB_PROFILE, len); } diff --git a/nostrdb/nostrdb.h b/nostrdb/nostrdb.h index f4606d59..7ee2c2c5 100644 --- a/nostrdb/nostrdb.h +++ b/nostrdb/nostrdb.h @@ -166,6 +166,7 @@ void ndb_end_query(struct ndb_txn *); void *ndb_get_profile_by_pubkey(struct ndb_txn *txn, const unsigned char *pubkey, size_t *len, uint64_t *primkey); void *ndb_get_profile_by_key(struct ndb_txn *txn, uint64_t key, size_t *len); uint64_t ndb_get_notekey_by_id(struct ndb_txn *txn, const unsigned char *id); +uint64_t ndb_get_profilekey_by_pubkey(struct ndb_txn *txn, const unsigned char *id); struct ndb_note *ndb_get_note_by_id(struct ndb_txn *txn, const unsigned char *id, size_t *len, uint64_t *primkey); struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint64_t key, size_t *len); void ndb_destroy(struct ndb *);