ndb: switch profile queries to use transactions

this should ensure no crashing occurs when querying profiles
This commit is contained in:
William Casarin 2023-09-10 14:51:55 -07:00
parent 622a436589
commit fc9b9f2940
51 changed files with 435 additions and 252 deletions

View File

@ -45,8 +45,7 @@ struct NIP05Badge: View {
} }
var username_matches_nip05: Bool { var username_matches_nip05: Bool {
guard let profile = profiles.lookup(id: pubkey), guard let name = profiles.lookup(id: pubkey).map({ p in p?.name }).value
let name = profile.name
else { else {
return false return false
} }

View File

@ -10,13 +10,12 @@ import SwiftUI
struct Reposted: View { struct Reposted: View {
let damus: DamusState let damus: DamusState
let pubkey: Pubkey let pubkey: Pubkey
let profile: Profile?
var body: some View { var body: some View {
HStack(alignment: .center) { HStack(alignment: .center) {
Image("repost") Image("repost")
.foregroundColor(Color.gray) .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) .foregroundColor(Color.gray)
Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).") Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
@ -27,6 +26,6 @@ struct Reposted: View {
struct Reposted_Previews: PreviewProvider { struct Reposted_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let test_state = test_damus_state() 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)
} }
} }

View File

@ -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) ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
VStack(alignment: .leading) { VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey) ProfileName(pubkey: pubkey, damus: damus_state, show_nip5_domain: false)
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
if let about_text { if let about_text {
about_text about_text
.lineLimit(3) .lineLimit(3)

View File

@ -359,13 +359,18 @@ struct ContentView: View {
// wallet with an associated // wallet with an associated
guard let ds = self.damus_state, guard let ds = self.damus_state,
let lud16 = nwc.lud16, let lud16 = nwc.lud16,
let keypair = ds.keypair.to_full(), let keypair = ds.keypair.to_full()
let profile = ds.profiles.lookup(id: ds.pubkey),
lud16 != profile.lud16
else { else {
return 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 // clear zapper cache for old lud16
if profile.lud16 != nil { if profile.lud16 != nil {
// TODO: should this be somewhere else, where we process profile events!? // TODO: should this be somewhere else, where we process profile events!?
@ -378,15 +383,9 @@ struct ContentView: View {
ds.postbox.send(ev) ds.postbox.send(ev)
} }
.onReceive(handle_notify(.broadcast)) { ev in .onReceive(handle_notify(.broadcast)) { ev in
guard let ds = self.damus_state else { guard let ds = self.damus_state else { return }
return
}
ds.postbox.send(ev) 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 .onReceive(handle_notify(.unfollow)) { target in
guard let state = self.damus_state else { return } guard let state = self.damus_state else { return }
@ -488,10 +487,12 @@ struct ContentView: View {
} }
.onReceive(handle_notify(.onlyzaps_mode)) { hide in .onReceive(handle_notify(.onlyzaps_mode)) { hide in
home.filter_events() home.filter_events()
guard let damus_state, guard let ds = damus_state else { return }
let profile = damus_state.profiles.lookup(id: damus_state.pubkey), let profile_txn = ds.profiles.lookup(id: ds.pubkey)
let keypair = damus_state.keypair.to_full()
guard let profile = profile_txn.unsafeUnownedValue,
let keypair = ds.keypair.to_full()
else { else {
return 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) 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 } 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: { .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.")) { 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: { }, message: {
if let pubkey = self.muting { if let pubkey = self.muting {
let profile = damus_state!.profiles.lookup(id: pubkey) let name = damus_state!.profiles.lookup(id: pubkey).map { profile in
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) 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.") Text("\(name) has been muted", comment: "Alert message that informs a user was muted.")
} else { } 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: { .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: { }, message: {
if let pubkey = muting { if let pubkey = muting {
let profile = damus_state?.profiles.lookup(id: pubkey) let name = damus_state?.profiles.lookup(id: pubkey).map({ profile in
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) 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.") Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.")
} else { } else {
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.") 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 { enum FoundEvent {
case profile(Profile, NostrEvent) case profile(Pubkey)
case invalid_profile(NostrEvent) case invalid_profile(NostrEvent)
case event(NostrEvent) case event(NostrEvent)
} }
@ -816,11 +819,10 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
switch query { switch query {
case .profile(let pubkey): case .profile(let pubkey):
if let record = state.profiles.lookup_with_timestamp(pubkey), if let record = state.ndb.lookup_profile(pubkey).unsafeUnownedValue,
let profile = record.profile, record.profile != nil
let event = state.events.lookup_by_key(record.noteKey)
{ {
callback(.profile(profile, event)) callback(.profile(pubkey))
return return
} }
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey]) 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 { switch query {
case .profile: case .profile:
if ev.known_kind == .metadata { 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)) callback(.invalid_profile(ev))
return return
} }
callback(.profile(profile, ev)) callback(.profile(ev.pubkey))
} }
case .event: case .event:
callback(.event(ev)) callback(.event(ev))

View File

@ -1108,10 +1108,13 @@ func zap_notification_title(_ zap: Zap) -> String {
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String { func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
let src = zap.request.ev let src = zap.request.ev
let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey 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 sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
let formattedSats = format_msats_abbrev(zap.invoice.amount) let formattedSats = format_msats_abbrev(zap.invoice.amount)
let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50)
if src.content.isEmpty { if src.content.isEmpty {
let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale) 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 return
} }
guard let record = damus_state.profiles.lookup_with_timestamp(ptag), guard let lnurl = damus_state.profiles.lookup_with_timestamp(ptag)
let lnurl = record.lnurl else { .map({ pr in pr?.lnurl }).value else {
completion(.failed) completion(.failed)
return return
} }

View File

@ -8,7 +8,16 @@
import Foundation import Foundation
struct ProfileUpdate { enum ProfileUpdate {
let pubkey: Pubkey case manual(pubkey: Pubkey, profile: Profile)
let profile: Profile case remote(pubkey: Pubkey)
var pubkey: Pubkey {
switch self {
case .manual(let pubkey, _):
return pubkey
case .remote(let pubkey):
return pubkey
}
}
} }

View File

@ -8,15 +8,18 @@
import Foundation import Foundation
typealias Profile = NdbProfile typealias Profile = NdbProfile
typealias ProfileKey = UInt64
//typealias ProfileRecord = NdbProfileRecord //typealias ProfileRecord = NdbProfileRecord
class ProfileRecord { class ProfileRecord {
let data: NdbProfileRecord let data: NdbProfileRecord
init(data: NdbProfileRecord) { init(data: NdbProfileRecord, key: ProfileKey) {
self.data = data self.data = data
self.profileKey = key
} }
let profileKey: ProfileKey
var profile: Profile? { return data.profile } var profile: Profile? { return data.profile }
var receivedAt: UInt64 { data.receivedAt } var receivedAt: UInt64 { data.receivedAt }
var noteKey: UInt64 { data.noteKey } var noteKey: UInt64 { data.noteKey }

View File

@ -84,7 +84,7 @@ enum NostrResponse {
return nil return nil
} }
let new_note = note_data.assumingMemoryBound(to: ndb_note.self) 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 { guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
free(data) free(data)

View File

@ -76,18 +76,25 @@ class Profiles {
profile_data(pubkey).zapper profile_data(pubkey).zapper
} }
func lookup_with_timestamp(_ pubkey: Pubkey) -> ProfileRecord? { func lookup_with_timestamp(_ pubkey: Pubkey) -> NdbTxn<ProfileRecord?> {
return ndb.lookup_profile(pubkey) return ndb.lookup_profile(pubkey)
} }
func lookup(id: Pubkey) -> Profile? { func lookup_by_key(key: ProfileKey) -> NdbTxn<ProfileRecord?> {
return ndb.lookup_profile(id)?.profile return ndb.lookup_profile_by_key(key: key)
}
func lookup(id: Pubkey) -> NdbTxn<Profile?> {
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 { func has_fresh_profile(id: Pubkey) -> Bool {
var profile: Profile? guard let recv = lookup_with_timestamp(id).unsafeUnownedValue?.receivedAt else { return false }
guard let profile = lookup_with_timestamp(id) else { return false } return Date.now.timeIntervalSince(Date(timeIntervalSince1970: Double(recv))) < Profiles.db_freshness_threshold
return Date.now.timeIntervalSince(Date(timeIntervalSince1970: Double(profile.receivedAt))) < Profiles.db_freshness_threshold
} }
} }

View File

@ -19,7 +19,7 @@ extension NotifyHandler {
} }
extension Notifications { extension Notifications {
static func profile_updated(pubkey: Pubkey, profile: Profile) -> Notifications<ProfileUpdatedNotify> { static func profile_updated(_ update: ProfileUpdate) -> Notifications<ProfileUpdatedNotify> {
.init(.init(payload: ProfileUpdate(pubkey: pubkey, profile: profile))) .init(.init(payload: update))
} }
} }

View File

@ -7,7 +7,7 @@
import Foundation import Foundation
enum DisplayName { enum DisplayName: Equatable {
case both(username: String, displayName: String) case both(username: String, displayName: String)
case one(String) case one(String)

View File

@ -137,6 +137,7 @@ class EventData {
} }
class EventCache { class EventCache {
// TODO: remove me and change code to use ndb directly
private let ndb: Ndb private let ndb: Ndb
private var events: [NoteId: NostrEvent] = [:] private var events: [NoteId: NostrEvent] = [:]
private var replies = ReplyMap() private var replies = ReplyMap()
@ -253,9 +254,11 @@ class EventCache {
return ev return ev
} }
/*
func lookup_by_key(_ key: UInt64) -> NostrEvent? { func lookup_by_key(_ key: UInt64) -> NostrEvent? {
ndb.lookup_note_by_key(key) ndb.lookup_note_by_key(key)
} }
*/
func lookup(_ evid: NoteId) -> NostrEvent? { func lookup(_ evid: NoteId) -> NostrEvent? {
return events[evid] return events[evid]

View File

@ -7,7 +7,7 @@
import Foundation import Foundation
struct NIP05 { struct NIP05: Equatable {
let username: String let username: String
let host: String let host: String

View File

@ -28,7 +28,7 @@ struct EventActionBar: View {
} }
var lnurl: String? { 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 { var show_like: Bool {

View File

@ -81,8 +81,10 @@ struct BannerImageView: View {
guard updated.pubkey == self.pubkey else { guard updated.pubkey == self.pubkey else {
return 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 self.banner = bannerImage
} }
} }
@ -90,7 +92,7 @@ struct BannerImageView: View {
} }
func get_banner_url(banner: String?, pubkey: Pubkey, profiles: Profiles) -> URL? { 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) { if let url = URL(string: bannerUrlString) {
return url return url
} }

View File

@ -60,12 +60,11 @@ struct DMChatView: View, KeyboardReadable {
} }
var Header: some View { var Header: some View {
let profile = damus_state.profiles.lookup(id: pubkey)
return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) { return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
HStack { HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) 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()) .buttonStyle(PlainButtonStyle())

View File

@ -22,9 +22,8 @@ struct EventTop: View {
} }
func ProfileName(is_anon: Bool) -> some View { func ProfileName(is_anon: Bool) -> some View {
let profile = state.profiles.lookup(id: self.pubkey)
let pk = is_anon ? ANON_PUBKEY : 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 { var body: some View {

View File

@ -11,10 +11,10 @@ import SwiftUI
struct ReplyDescription: View { struct ReplyDescription: View {
let event: NostrEvent let event: NostrEvent
let replying_to: NostrEvent? let replying_to: NostrEvent?
let profiles: Profiles let ndb: Ndb
var body: some View { 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) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@ -23,11 +23,11 @@ struct ReplyDescription: View {
struct ReplyDescription_Previews: PreviewProvider { struct ReplyDescription_Previews: PreviewProvider {
static var previews: some View { 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 desc = make_reply_description(event, replying_to: replying_to)
let pubkeys = desc.pubkeys let pubkeys = desc.pubkeys
let n = desc.others 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.") 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 names: [String] = pubkeys.map { pk in
let prof = profiles.lookup(id: pk) let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn)
return Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50)
} }
let uniqueNames = NSOrderedSet(array: names).array as! [String] let uniqueNames = NSOrderedSet(array: names).array as! [String]

View File

@ -11,7 +11,7 @@ struct ReplyPart: View {
let events: EventCache let events: EventCache
let event: NostrEvent let event: NostrEvent
let keypair: Keypair let keypair: Keypair
let profiles: Profiles let ndb: Ndb
var replying_to: NostrEvent? { 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 { 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 { var body: some View {
Group { Group {
if event_is_reply(event.event_refs(keypair)) { 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 { } else {
EmptyView() EmptyView()
} }
@ -34,6 +34,6 @@ struct ReplyPart: View {
struct ReplyPart_Previews: PreviewProvider { struct ReplyPart_Previews: PreviewProvider {
static var previews: some View { 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)
} }
} }

View File

@ -52,8 +52,9 @@ struct MenuItems: View {
let target_pubkey: Pubkey let target_pubkey: Pubkey
let bookmarks: BookmarksManager let bookmarks: BookmarksManager
let muted_threads: MutedThreadsManager let muted_threads: MutedThreadsManager
@ObservedObject var settings: UserSettingsStore @ObservedObject var settings: UserSettingsStore
@State private var isBookmarked: Bool = false @State private var isBookmarked: Bool = false
@State private var isMutedThread: Bool = false @State private var isMutedThread: Bool = false

View File

@ -25,7 +25,6 @@ func eventview_pfp_size(_ size: EventViewKind) -> CGFloat {
struct EventProfile: View { struct EventProfile: View {
let damus_state: DamusState let damus_state: DamusState
let pubkey: Pubkey let pubkey: Pubkey
let profile: Profile?
let size: EventViewKind let size: EventViewKind
var pfp_size: CGFloat { var pfp_size: CGFloat {
@ -44,7 +43,7 @@ struct EventProfile: View {
} }
VStack(alignment: .leading, spacing: 0) { 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) 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 { struct EventProfile_Previews: PreviewProvider {
static var previews: some View { 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)
} }
} }

View File

@ -72,7 +72,7 @@ struct EventShell<Content: View>: View {
UserStatusView(status: state.profiles.profile_data(pubkey).status, show_general: state.settings.show_general_statuses, show_music: state.settings.show_music_statuses) 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) { 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 content
@ -99,7 +99,7 @@ struct EventShell<Content: View>: View {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
EventTop(state: state, event: event, pubkey: pubkey, is_anon: is_anon) 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) 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) .padding(.horizontal)

View File

@ -35,11 +35,9 @@ struct SelectedEventView: View {
var body: some View { var body: some View {
HStack(alignment: .top) { HStack(alignment: .top) {
let profile = damus.profiles.lookup(id: pubkey)
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
EventProfile(damus_state: damus, pubkey: pubkey, profile: profile, size: .normal) EventProfile(damus_state: damus, pubkey: pubkey, size: .normal)
Spacer() Spacer()
@ -51,7 +49,7 @@ struct SelectedEventView: View {
.lineLimit(1) .lineLimit(1)
if event_is_reply(event.event_refs(damus.keypair)) { 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) .padding(.horizontal)
} }

View File

@ -273,7 +273,8 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
switch m.ref { switch m.ref {
case .pubkey(let pk): case .pubkey(let pk):
let npub = bech32_pubkey(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) let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
var attributedString = AttributedString(stringLiteral: "@\(disp)") var attributedString = AttributedString(stringLiteral: "@\(disp)")
attributedString.link = URL(string: "damus:nostr:\(npub)") attributedString.link = URL(string: "damus:nostr:\(npub)")

View File

@ -69,8 +69,8 @@ func determine_reacting_to(our_pubkey: Pubkey, ev: NostrEvent?) -> ReactingTo {
} }
func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String { func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String {
let alice_prof = profiles.lookup(id: pubkey) let alice_prof_txn = profiles.lookup(id: pubkey).unsafeUnownedValue
return Profile.displayName(profile: alice_prof, pubkey: pubkey).username.truncate(maxLength: 50) return Profile.displayName(profile: alice_prof_txn, pubkey: pubkey).username.truncate(maxLength: 50)
} }
func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [Pubkey] { func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [Pubkey] {

View File

@ -86,8 +86,10 @@ struct NotificationsView: View {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
var mystery: some View { var mystery: some View {
VStack(spacing: 20) { let profile_txn = state.profiles.lookup(id: state.pubkey)
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 = 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.") Text("You are dreaming...", comment: "Text telling the user that they are dreaming.")
} }
.id("what") .id("what")

View File

@ -12,14 +12,10 @@ struct SuggestedUser {
let name: String let name: String
let about: String let about: String
let pfp: URL let pfp: URL
let profile: Profile
init?(profile: Profile, pubkey: Pubkey) { init?(name: String?, about: String?, picture: String?, pubkey: Pubkey) {
guard let name, let about, let picture,
guard let name = profile.name, let pfpURL = URL(string: picture) else {
let about = profile.about,
let picture = profile.picture,
let pfpURL = URL(string: picture) else {
return nil return nil
} }
@ -27,12 +23,10 @@ struct SuggestedUser {
self.name = name self.name = name
self.about = about self.about = about
self.pfp = pfpURL self.pfp = pfpURL
self.profile = profile
} }
} }
struct SuggestedUserView: View { struct SuggestedUserView: View {
let user: SuggestedUser let user: SuggestedUser
let damus_state: DamusState let damus_state: DamusState
@ -47,7 +41,7 @@ struct SuggestedUserView: View {
disable_animation: false) disable_animation: false)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
ProfileName(pubkey: user.pubkey, profile: user.profile, damus: damus_state) ProfileName(pubkey: user.pubkey, damus: damus_state)
} }
Text(user.about) Text(user.about)
.lineLimit(3) .lineLimit(3)
@ -62,9 +56,10 @@ struct SuggestedUserView: View {
struct SuggestedUserView_Previews: PreviewProvider { struct SuggestedUserView_Previews: PreviewProvider {
static var previews: some View { 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 { List {
SuggestedUserView(user: user, damus_state: test_damus_state()) SuggestedUserView(user: user, damus_state: test_damus_state())
} }

View File

@ -35,8 +35,9 @@ class SuggestedUsersViewModel: ObservableObject {
} }
func suggestedUser(pubkey: Pubkey) -> SuggestedUser? { func suggestedUser(pubkey: Pubkey) -> SuggestedUser? {
if let profile = damus_state.profiles.lookup(id: pubkey), let profile_txn = damus_state.profiles.lookup(id: pubkey)
let user = SuggestedUser(profile: profile, pubkey: pubkey) { if let profile = profile_txn.unsafeUnownedValue,
let user = SuggestedUser(name: profile.name, about: profile.about, picture: profile.picture, pubkey: pubkey) {
return user return user
} }
return nil return nil

View File

@ -173,7 +173,8 @@ struct PostView: View {
return .init(string: "") 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) return user_tag_attr_string(profile: profile, pubkey: pubkey)
} }

View File

@ -7,15 +7,6 @@
import SwiftUI import SwiftUI
struct SearchedUser: Identifiable {
let profile: Profile?
let pubkey: Pubkey
var id: Pubkey {
return pubkey
}
}
struct UserSearch: View { struct UserSearch: View {
let damus_state: DamusState let damus_state: DamusState
let search: String let search: String
@ -25,13 +16,14 @@ struct UserSearch: View {
@Binding var post: NSMutableAttributedString @Binding var post: NSMutableAttributedString
@EnvironmentObject var tagModel: TagModel @EnvironmentObject var tagModel: TagModel
var users: [SearchedUser] { var users: [Pubkey] {
return search_profiles(profiles: damus_state.profiles, search: search) return search_profiles(profiles: damus_state.profiles, search: search)
} }
func on_user_tapped(user: SearchedUser) { func on_user_tapped(pk: Pubkey) {
let pk = user.pubkey let profile_txn = damus_state.profiles.lookup(id: pk)
let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk) let profile = profile_txn.unsafeUnownedValue
let user_tag = user_tag_attr_string(profile: profile, pubkey: pk)
appendUserTag(withTag: user_tag) appendUserTag(withTag: user_tag)
} }
@ -57,11 +49,11 @@ struct UserSearch: View {
if users.count == 0 { if users.count == 0 {
EmptyUserSearchView() EmptyUserSearchView()
} else { } else {
ForEach(users) { user in ForEach(users) { pk in
UserView(damus_state: damus_state, pubkey: user.pubkey) UserView(damus_state: damus_state, pubkey: pk)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { .onTapGesture {
on_user_tapped(user: user) on_user_tapped(pk: pk)
} }
} }
} }

View File

@ -20,8 +20,7 @@ struct EditMetadataView: View {
@State var name: String @State var name: String
@State var ln: String @State var ln: String
@State var website: String @State var website: String
let profile: Profile?
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@State var confirm_ln_address: Bool = false @State var confirm_ln_address: Bool = false
@ -31,9 +30,8 @@ struct EditMetadataView: View {
init(damus_state: DamusState) { init(damus_state: DamusState) {
self.damus_state = damus_state self.damus_state = damus_state
let data = damus_state.profiles.lookup(id: damus_state.pubkey) let data = damus_state.profiles.lookup(id: damus_state.pubkey).unsafeUnownedValue
self.profile = data
_name = State(initialValue: data?.name ?? "") _name = State(initialValue: data?.name ?? "")
_display_name = State(initialValue: data?.display_name ?? "") _display_name = State(initialValue: data?.display_name ?? "")
_about = State(initialValue: data?.about ?? "") _about = State(initialValue: data?.about ?? "")

View File

@ -12,20 +12,19 @@ import SwiftUI
struct EventProfileName: View { struct EventProfileName: View {
let damus_state: DamusState let damus_state: DamusState
let pubkey: Pubkey let pubkey: Pubkey
let profile: Profile?
@State var display_name: DisplayName? @State var display_name: DisplayName?
@State var nip05: NIP05? @State var nip05: NIP05?
@State var donation: Int? @State var donation: Int?
let size: EventViewKind 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.damus_state = damus
self.pubkey = pubkey self.pubkey = pubkey
self.profile = profile
self.size = size 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? { var friend_type: FriendType? {
@ -36,11 +35,11 @@ struct EventProfileName: View {
nip05 ?? damus_state.profiles.is_validated(pubkey) 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) return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
} }
var onlyzapper: Bool { func onlyzapper(_ profile: Profile?) -> Bool {
guard let profile else { guard let profile else {
return false return false
} }
@ -58,8 +57,10 @@ struct EventProfileName: View {
} }
var body: some View { var body: some View {
let profile_txn = damus_state.profiles.lookup(id: pubkey)
let profile = profile_txn.unsafeUnownedValue
HStack(spacing: 2) { HStack(spacing: 2) {
switch current_display_name { switch current_display_name(profile) {
case .one(let one): case .one(let one):
Text(one) Text(one)
.font(.body.weight(.bold)) .font(.body.weight(.bold))
@ -84,7 +85,7 @@ struct EventProfileName: View {
FriendIcon(friend: frend) FriendIcon(friend: frend)
} }
if onlyzapper { if onlyzapper(profile) {
Image("zap-hashtag") Image("zap-hashtag")
.frame(width: 14, height: 14) .frame(width: 14, height: 14)
} }
@ -97,9 +98,24 @@ struct EventProfileName: View {
if update.pubkey != pubkey { if update.pubkey != pubkey {
return return
} }
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey) let profile_txn = damus_state.profiles.lookup(id: update.pubkey)
donation = update.profile.damus_donation 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 { struct EventProfileName_Previews: PreviewProvider {
static var previews: some View { 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())
} }
} }

View File

@ -27,7 +27,6 @@ func get_friend_type(contacts: Contacts, pubkey: Pubkey) -> FriendType? {
struct ProfileName: View { struct ProfileName: View {
let damus_state: DamusState let damus_state: DamusState
let pubkey: Pubkey let pubkey: Pubkey
let profile: Profile?
let prefix: String let prefix: String
let show_nip5_domain: Bool let show_nip5_domain: Bool
@ -36,9 +35,8 @@ struct ProfileName: View {
@State var nip05: NIP05? @State var nip05: NIP05?
@State var donation: Int? @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.pubkey = pubkey
self.profile = profile
self.prefix = prefix self.prefix = prefix
self.damus_state = damus self.damus_state = damus
self.show_nip5_domain = show_nip5_domain self.show_nip5_domain = show_nip5_domain
@ -53,15 +51,15 @@ struct ProfileName: View {
nip05 ?? damus_state.profiles.is_validated(pubkey) 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) return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
} }
var name_choice: String { func name_choice(profile: Profile?) -> String {
return prefix == "@" ? current_display_name.username.truncate(maxLength: 50) : current_display_name.displayName.truncate(maxLength: 50) 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 { guard let profile else {
return false return false
} }
@ -69,7 +67,7 @@ struct ProfileName: View {
return profile.reactions == false return profile.reactions == false
} }
var supporter: Int? { func supporter(profile: Profile?) -> Int? {
guard let profile, guard let profile,
let donation = profile.damus_donation, let donation = profile.damus_donation,
donation > 0 donation > 0
@ -81,21 +79,28 @@ struct ProfileName: View {
} }
var body: some View { var body: some View {
let profile_txn = damus_state.profiles.lookup(id: pubkey)
let profile = profile_txn.unsafeUnownedValue
HStack(spacing: 2) { HStack(spacing: 2) {
Text(verbatim: "\(prefix)\(name_choice)") Text(verbatim: "\(prefix)\(name_choice(profile: profile))")
.font(.body) .font(.body)
.fontWeight(prefix == "@" ? .none : .bold) .fontWeight(prefix == "@" ? .none : .bold)
if let nip05 = current_nip05 { if let nip05 = current_nip05 {
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, profiles: damus_state.profiles) 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 { if let friend = friend_type, current_nip05 == nil {
FriendIcon(friend: friend) FriendIcon(friend: friend)
} }
if onlyzapper {
if onlyzapper(profile: profile) {
Image("zap-hashtag") Image("zap-hashtag")
.frame(width: 14, height: 14) .frame(width: 14, height: 14)
} }
if let supporter {
if let supporter = supporter(profile: profile) {
SupporterBadge(percent: supporter) SupporterBadge(percent: supporter)
} }
} }
@ -103,16 +108,38 @@ struct ProfileName: View {
if update.pubkey != pubkey { if update.pubkey != pubkey {
return return
} }
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey) var profile: Profile!
donation = profile?.damus_donation var profile_txn: NdbTxn<Profile?>!
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 { struct ProfileName_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ProfileName(pubkey: ProfileName(pubkey: test_damus_state().pubkey, damus: test_damus_state())
test_damus_state().pubkey, profile: make_test_profile(), damus: test_damus_state())
} }
} }

View File

@ -87,7 +87,6 @@ fileprivate struct KeyView: View {
struct ProfileNameView: View { struct ProfileNameView: View {
let pubkey: Pubkey let pubkey: Pubkey
let profile: Profile?
let damus: DamusState let damus: DamusState
var spacing: CGFloat { 10.0 } var spacing: CGFloat { 10.0 }
@ -95,10 +94,13 @@ struct ProfileNameView: View {
var body: some View { var body: some View {
Group { Group {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let profile_txn = self.damus.profiles.lookup(id: pubkey)
let profile = profile_txn.unsafeUnownedValue
switch Profile.displayName(profile: profile, pubkey: pubkey) { switch Profile.displayName(profile: profile, pubkey: pubkey) {
case .one: case .one:
HStack(alignment: .center, spacing: spacing) { HStack(alignment: .center, spacing: spacing) {
ProfileName(pubkey: pubkey, profile: profile, damus: damus) ProfileName(pubkey: pubkey, damus: damus)
.font(.title3.weight(.bold)) .font(.title3.weight(.bold))
} }
case .both(username: _, displayName: let displayName): case .both(username: _, displayName: let displayName):
@ -106,7 +108,7 @@ struct ProfileNameView: View {
.font(.title3.weight(.bold)) .font(.title3.weight(.bold))
HStack(alignment: .center, spacing: spacing) { HStack(alignment: .center, spacing: spacing) {
ProfileName(pubkey: pubkey, profile: profile, prefix: "@", damus: damus) ProfileName(pubkey: pubkey, prefix: "@", damus: damus)
.font(.callout) .font(.callout)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
@ -124,9 +126,9 @@ struct ProfileNameView: View {
struct ProfileNameView_Previews: PreviewProvider { struct ProfileNameView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
VStack { 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())
} }
} }
} }

View File

@ -87,16 +87,25 @@ struct ProfilePicView: View {
guard updated.pubkey == self.pubkey else { guard updated.pubkey == self.pubkey else {
return return
} }
if let pic = updated.profile.picture { switch updated {
self.picture = pic 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 { 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) { if let url = URL(string: pic) {
return url return url
} }

View File

@ -43,7 +43,7 @@ struct EditProfilePictureView: View {
if let profile_url { if let profile_url {
return profile_url return profile_url
} else if let state = damus_state, } 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) return URL(string: picture)
} else { } else {
return profile_url ?? URL(string: robohash(pubkey)) return profile_url ?? URL(string: robohash(pubkey))

View File

@ -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<Y>(txn: NdbTxn<Y>, _ friend_intersection: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale) let bundle = bundleForLocale(locale: locale)
let names: [String] = friend_intersection.prefix(3).map { let names: [String] = friend_intersection.prefix(3).map { pk in
let profile = profiles.lookup(id: $0) let profile = ndb.lookup_profile_with_txn(pk, txn: txn)?.profile
return Profile.displayName(profile: profile, pubkey: $0).username.truncate(maxLength: 20) return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20)
} }
switch friend_intersection.count { switch friend_intersection.count {
@ -216,27 +216,29 @@ struct ProfileView: View {
.accentColor(DamusColors.white) .accentColor(DamusColors.white)
} }
func lnButton(lnurl: String, record: ProfileRecord, profile: Profile) -> some View { func lnButton(lnurl: String, unownedProfile: Profile?, pubkey: Pubkey) -> some View {
let profile = record.profile! let reactions = unownedProfile?.reactions ?? true
let button_img = profile.reactions == false ? "zap.fill" : "zap" let button_img = reactions ? "zap.fill" : "zap"
return Button(action: { let lud16 = unownedProfile?.lud16
return Button(action: { [lnurl] in
present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl)) present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl))
}) { }) {
Image(button_img) Image(button_img)
.foregroundColor(button_img == "zap.fill" ? .orange : Color.primary) .foregroundColor(button_img == "zap.fill" ? .orange : Color.primary)
.profile_button_style(scheme: colorScheme) .profile_button_style(scheme: colorScheme)
.contextMenu { .contextMenu { [lud16, reactions, lnurl] in
if profile.reactions == false { 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.") 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 { Button {
UIPasteboard.general.string = addr UIPasteboard.general.string = lud16
} label: { } label: {
Label(addr, image: "copy2") Label(lud16, image: "copy2")
} }
} else if let lnurl = record.lnurl { } else {
Button { Button {
UIPasteboard.general.string = lnurl UIPasteboard.general.string = lnurl
} label: { } label: {
@ -269,14 +271,14 @@ struct ProfileView: View {
.font(.footnote) .font(.footnote)
} }
func actionSection(record: ProfileRecord?) -> some View { func actionSection(record: ProfileRecord?, pubkey: Pubkey) -> some View {
return Group { return Group {
if let record, if let record,
let profile = record.profile, let profile = record.profile,
let lnurl = record.lnurl, let lnurl = record.lnurl,
lnurl != "" lnurl != ""
{ {
lnButton(lnurl: lnurl, record: record, profile: profile) lnButton(lnurl: lnurl, unownedProfile: profile, pubkey: pubkey)
} }
dmButton dmButton
@ -311,6 +313,7 @@ struct ProfileView: View {
func nameSection(profile_data: ProfileRecord?) -> some View { func nameSection(profile_data: ProfileRecord?) -> some View {
return Group { return Group {
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey) let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
HStack(alignment: .center) { 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) 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)) .padding(.top, -(pfp_size / 2.0))
@ -329,10 +332,10 @@ struct ProfileView: View {
followsYouBadge 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 { var aboutSection: some View {
VStack(alignment: .leading, spacing: 8.0) { 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) nameSection(profile_data: profile_data)
@ -422,7 +426,7 @@ struct ProfileView: View {
NavigationLink(value: Route.FollowersYouKnow(friendedFollowers: friended_followers, followers: followers)) { NavigationLink(value: Route.FollowersYouKnow(friendedFollowers: friended_followers, followers: followers)) {
HStack { HStack {
CondensedProfilePicturesView(state: damus_state, pubkeys: friended_followers, maxPictures: 3) 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) Text(followedByString)
.font(.subheadline).foregroundColor(.gray) .font(.subheadline).foregroundColor(.gray)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
@ -516,7 +520,9 @@ extension View {
@MainActor @MainActor
func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) { 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, let nip05 = profile.nip05,
profiles.is_validated(pubkey) == nil profiles.is_validated(pubkey) == nil
else { else {
@ -532,7 +538,7 @@ func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) {
Task { @MainActor in Task { @MainActor in
profiles.set_validated(pubkey, nip05: validated) profiles.set_validated(pubkey, nip05: validated)
profiles.nip05_pubkey[nip05] = pubkey profiles.nip05_pubkey[nip05] = pubkey
notify(.profile_updated(pubkey: pubkey, profile: profile)) notify(.profile_updated(.remote(pubkey: pubkey)))
} }
} }
} }

View File

@ -118,9 +118,11 @@ struct QRCodeView: View {
var QRView: some View { var QRView: some View {
VStack(alignment: .center) { VStack(alignment: .center) {
let profile = damus_state.profiles.lookup(id: pubkey) let profile_txn = damus_state.profiles.lookup(id: pubkey)
let profile = profile_txn.unsafeUnownedValue
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil { 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) 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) .padding(.top, 50)
} else { } else {

View File

@ -24,10 +24,11 @@ struct ReplyView: View {
var ReplyingToSection: some View { var ReplyingToSection: some View {
HStack { HStack {
Group { Group {
let txn = NdbTxn(ndb: damus.ndb)
let names = references let names = references
.map { pubkey in .map { pubkey in
let pk = pubkey 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) return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
} }
.joined(separator: " ") .joined(separator: " ")

View File

@ -15,10 +15,8 @@ struct RepostedEvent: View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let prof = damus.profiles.lookup(id: event.pubkey)
NavigationLink(value: Route.ProfileByKey(pubkey: 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) .padding(.horizontal)
} }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())

View File

@ -44,23 +44,25 @@ struct SearchingEventView: View {
switch search { switch search {
case .nip05(let nip05): case .nip05(let nip05):
if let pk = state.profiles.nip05_pubkey[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) self.search_state = .found_profile(pk)
} }
} else { } else {
Task { Task {
guard let nip05 = NIP05.parse(nip05) else { guard let nip05 = NIP05.parse(nip05) else {
self.search_state = .not_found Task { @MainActor in
self.search_state = .not_found
}
return return
} }
guard let nip05_resp = await fetch_nip05(nip05: nip05) else { guard let nip05_resp = await fetch_nip05(nip05: nip05) else {
DispatchQueue.main.async { Task { @MainActor in
self.search_state = .not_found self.search_state = .not_found
} }
return return
} }
DispatchQueue.main.async { Task { @MainActor in
guard let pk = nip05_resp.names[nip05.username] else { guard let pk = nip05_resp.names[nip05.username] else {
self.search_state = .not_found self.search_state = .not_found
return return
@ -81,11 +83,11 @@ struct SearchingEventView: View {
} }
case .profile(let pubkey): case .profile(let pubkey):
find_event(state: state, query: .profile(pubkey: pubkey)) { res in 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 self.search_state = .not_found
return return
} }
self.search_state = .found_profile(ev.pubkey) self.search_state = .found_profile(pubkey)
} }
} }
} }

View File

@ -9,11 +9,11 @@ import SwiftUI
struct MultiSearch { struct MultiSearch {
let hashtag: String let hashtag: String
let profiles: [SearchedUser] let profiles: [Pubkey]
} }
enum Search: Identifiable { enum Search: Identifiable {
case profiles([SearchedUser]) case profiles([Pubkey])
case hashtag(String) case hashtag(String)
case profile(Pubkey) case profile(Pubkey)
case note(NoteId) case note(NoteId)
@ -49,10 +49,10 @@ struct InnerSearchResults: View {
} }
} }
func ProfilesSearch(_ results: [SearchedUser]) -> some View { func ProfilesSearch(_ results: [Pubkey]) -> some View {
return LazyVStack { return LazyVStack {
ForEach(results) { prof in ForEach(results, id: \.id) { pk in
ProfileSearchResult(pk: prof.pubkey) ProfileSearchResult(pk: pk)
} }
} }
} }
@ -171,27 +171,23 @@ func make_hashtagable(_ str: String) -> String {
return String(new.filter{$0 != " "}) 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. // Search by hex pubkey.
if let pubkey = hex_decode_pubkey(search), 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. // Search by npub pubkey.
if search.starts(with: "npub"), if search.starts(with: "npub"),
let bech32_key = decode_bech32_key(search), let bech32_key = decode_bech32_key(search),
case Bech32Key.pub(let pk) = bech32_key, 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 new = search.lowercased()
let matched_pubkeys = profiles.user_search_cache.search(key: new) return profiles.user_search_cache.search(key: new)
return matched_pubkeys
.map { SearchedUser(profile: profiles.lookup(id: $0), pubkey: $0) }
.filter { $0.profile != nil }
} }

View File

@ -83,7 +83,8 @@ struct SideMenuView: View {
} }
var TopProfile: some 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) { return VStack(alignment: .leading, spacing: verticalSpacing) {
HStack { HStack {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)

View File

@ -145,7 +145,7 @@ struct WalletView: View {
Spacer() 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) .padding(25)
} }
@ -164,17 +164,20 @@ struct WalletView: View {
model.initial_percent = settings.donation_percent model.initial_percent = settings.donation_percent
} }
.onChange(of: settings.donation_percent) { p in .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 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) 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 { .onDisappear {
let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey)
guard let keypair = damus_state.keypair.to_full(), 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 model.initial_percent != profile.damus_donation
else { else {
return return

View File

@ -117,7 +117,8 @@ func zap_type_desc(type: ZapType, profiles: Profiles, pubkey: Pubkey) -> String
case .anon: 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.") 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: 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) 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) 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: case .non_zap:

View File

@ -49,44 +49,130 @@ class Ndb {
self.ndb = ndb self.ndb = ndb
} }
func lookup_note_by_key(_ key: UInt64) -> NdbNote? { func lookup_note_by_key_with_txn<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> NdbNote? {
guard let note_p = ndb_get_note_by_key(ndb.ndb, key, nil) else { guard let note_p = ndb_get_note_by_key(&txn.txn, key, nil) else {
return nil 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? { func lookup_note_by_key(_ key: NoteKey) -> NdbTxn<NdbNote?> {
id.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NdbNote? in return NdbTxn(ndb: self) { txn in
guard let baseAddress = ptr.baseAddress, lookup_note_by_key_with_txn(key, txn: txn)
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_profile(_ pubkey: Pubkey) -> ProfileRecord? { private func lookup_profile_by_key_inner<Y>(_ key: ProfileKey, txn: NdbTxn<Y>) -> 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<Y>(id: NoteId, txn: NdbTxn<Y>) -> 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<Y>(pubkey: Pubkey, txn: NdbTxn<Y>) -> ProfileRecord? {
return pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> ProfileRecord? in return pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> ProfileRecord? in
var size: Int = 0 var size: Int = 0
var key: UInt64 = 0
guard let baseAddress = ptr.baseAddress, 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 { else {
return nil return nil
} }
do { return profile_flatbuf_to_record(ptr: profile_p, size: size, key: key)
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
}
} }
} }
func lookup_profile_by_key_with_txn<Y>(key: ProfileKey, txn: NdbTxn<Y>) -> ProfileRecord? {
lookup_profile_by_key_inner(key, txn: txn)
}
func lookup_profile_by_key(key: ProfileKey) -> NdbTxn<ProfileRecord?> {
return NdbTxn(ndb: self) { txn in
lookup_profile_by_key_inner(key, txn: txn)
}
}
func lookup_note_with_txn<Y>(id: NoteId, txn: NdbTxn<Y>) -> 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<Y>(_ pubkey: Pubkey, txn: NdbTxn<Y>) -> 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<Y>(_ id: NoteId, txn: NdbTxn<Y>) -> 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<NdbNote?> {
return NdbTxn(ndb: self) { txn in
lookup_note_with_txn_inner(id: id, txn: txn)
}
}
func lookup_profile(_ pubkey: Pubkey) -> NdbTxn<ProfileRecord?> {
return NdbTxn(ndb: self) { txn in
lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn)
}
}
func lookup_profile_with_txn<Y>(_ pubkey: Pubkey, txn: NdbTxn<Y>) -> ProfileRecord? {
lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn)
}
func process_event(_ str: String) -> Bool { func process_event(_ str: String) -> Bool {
return str.withCString { cstr in return str.withCString { cstr in
return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0 return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
@ -106,7 +192,7 @@ class Ndb {
#if DEBUG #if DEBUG
func getDebugCheckedRoot<T: FlatBufferObject & Verifiable>(byteBuffer: inout ByteBuffer) throws -> T { func getDebugCheckedRoot<T: FlatBufferObject & Verifiable>(byteBuffer: inout ByteBuffer) throws -> T {
return try getCheckedRoot(byteBuffer: &byteBuffer) return try getRoot(byteBuffer: &byteBuffer)
} }
#else #else
func getDebugCheckedRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) throws -> T { func getDebugCheckedRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) throws -> T {

View File

@ -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 // we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
private let owned: Bool private let owned: Bool
let count: Int let count: Int
let key: NoteKey?
let note: UnsafeMutablePointer<ndb_note> let note: UnsafeMutablePointer<ndb_note>
// cached stuff (TODO: remove these) // 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) return NdbNote.owned_from_json_cstr(json: content_raw, json_len: content_len)
}() }()
init(note: UnsafeMutablePointer<ndb_note>, owned_size: Int?) { init(note: UnsafeMutablePointer<ndb_note>, owned_size: Int?, key: NoteKey?) {
self.note = note self.note = note
self.owned = owned_size != nil self.owned = owned_size != nil
self.count = owned_size ?? 0 self.count = owned_size ?? 0
self.key = key
#if DEBUG_NOTE_SIZE #if DEBUG_NOTE_SIZE
if let owned_size { if let owned_size {
@ -218,6 +220,7 @@ class NdbNote: Encodable, Equatable, Hashable {
} }
self.note = r.assumingMemoryBound(to: ndb_note.self) self.note = r.assumingMemoryBound(to: ndb_note.self)
self.key = nil
} }
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? { 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 } guard let note_data = realloc(data, Int(len)) else { return nil }
let new_note = note_data.assumingMemoryBound(to: ndb_note.self) 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)
} }
} }

View File

@ -70,7 +70,7 @@ struct TagsSequence: Encodable, Sequence {
} }
precondition(false, "sequence subscript oob") precondition(false, "sequence subscript oob")
// it seems like the compiler needs this or it gets bitchy // 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 { func makeIterator() -> TagsIterator {

View File

@ -17,7 +17,7 @@ class NdbTxn<T> {
private var val: T! private var val: T!
var moved: Bool var moved: Bool
init(ndb: Ndb, with: (NdbTxn<T>) -> T) { init(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }) {
self.txn = ndb_txn() self.txn = ndb_txn()
#if TXNDEBUG #if TXNDEBUG
txn_count += 1 txn_count += 1

View File

@ -264,7 +264,9 @@ int ndb_get_tsid(MDB_txn *txn, struct ndb_lmdb *lmdb, enum ndb_dbs db,
int success = 0; int success = 0;
struct ndb_tsid tsid; struct ndb_tsid tsid;
// position at the most recent
ndb_tsid_high(&tsid, id); ndb_tsid_high(&tsid, id);
k.mv_data = &tsid; k.mv_data = &tsid;
k.mv_size = sizeof(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; 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); 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); 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; 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 0;
return *(uint32_t*)k.mv_data; 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); 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); return ndb_lookup_by_key(txn, key, NDB_DB_PROFILE, len);
} }

View File

@ -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_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); 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_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_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); struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint64_t key, size_t *len);
void ndb_destroy(struct ndb *); void ndb_destroy(struct ndb *);