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 {
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
}

View File

@ -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)
}
}

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)
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)

View File

@ -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))

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 {
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
}

View File

@ -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
}
}
}

View File

@ -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 }

View File

@ -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)

View File

@ -76,18 +76,25 @@ class Profiles {
profile_data(pubkey).zapper
}
func lookup_with_timestamp(_ pubkey: Pubkey) -> ProfileRecord? {
func lookup_with_timestamp(_ pubkey: Pubkey) -> NdbTxn<ProfileRecord?> {
return ndb.lookup_profile(pubkey)
}
func lookup(id: Pubkey) -> Profile? {
return ndb.lookup_profile(id)?.profile
func lookup_by_key(key: ProfileKey) -> NdbTxn<ProfileRecord?> {
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 {
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
}
}

View File

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

View File

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

View File

@ -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]

View File

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

View File

@ -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 {

View File

@ -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
}

View File

@ -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())

View File

@ -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 {

View File

@ -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]

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
}
}

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)
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<Content: View>: 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)

View File

@ -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)
}

View File

@ -273,7 +273,8 @@ func mention_str(_ m: Mention<MentionRef>, 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)")

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 {
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] {

View File

@ -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")

View File

@ -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())
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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 ?? "")

View File

@ -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())
}
}

View File

@ -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<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 {
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())
}
}

View File

@ -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())
}
}
}

View File

@ -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
}

View File

@ -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))

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 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)))
}
}
}

View File

@ -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 {

View File

@ -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: " ")

View File

@ -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())

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> 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<NdbNote?> {
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<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
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<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 {
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<T: FlatBufferObject & Verifiable>(byteBuffer: inout ByteBuffer) throws -> T {
return try getCheckedRoot(byteBuffer: &byteBuffer)
return try getRoot(byteBuffer: &byteBuffer)
}
#else
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
private let owned: Bool
let count: Int
let key: NoteKey?
let note: UnsafeMutablePointer<ndb_note>
// 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<ndb_note>, owned_size: Int?) {
init(note: UnsafeMutablePointer<ndb_note>, 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)
}
}

View File

@ -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 {

View File

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

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_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 *);