From bfad2ab42d47ea91e042ee458a716051a243ff8b Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 12 Dec 2023 12:45:56 -0800 Subject: [PATCH] ndb/txn: make transactions failable Since there may be situations where we close and re-open the database, we need to make sure transactions fail when the database is not open. Make NdbTxn an init?() constructor and check for ndb.closed. If it's closed, then fail transaction construction. This fixes crashes during high database activity when switching from background to foreground and vice-versa. Fixes: da2bdad18d1d ("nostrdb: close database when backgrounded") --- .../NotificationFormatter.swift | 6 ++-- .../NotificationService.swift | 2 +- damus/Components/NIP05Badge.swift | 2 +- damus/ContentView.swift | 33 ++++++++----------- damus/Models/EventsModel.swift | 4 ++- damus/Models/FollowersModel.swift | 2 +- damus/Models/HomeModel.swift | 6 ++-- damus/Models/NoteContent.swift | 2 +- damus/Models/NotificationsManager.swift | 17 ++++++---- damus/Models/ProfileModel.swift | 2 +- damus/Models/SearchHomeModel.swift | 2 +- damus/Models/SearchModel.swift | 2 +- damus/Models/ThreadModel.swift | 2 +- damus/Models/ZapsModel.swift | 2 +- damus/Nostr/Profiles.swift | 19 ++++++----- damus/Views/ActionBar/EventActionBar.swift | 4 ++- damus/Views/BannerImageView.swift | 7 ++-- .../Events/Components/ReplyDescription.swift | 4 ++- damus/Views/FollowingView.swift | 2 +- damus/Views/NoteContentView.swift | 2 +- .../Notifications/NotificationsView.swift | 2 +- .../Onboarding/SuggestedUsersViewModel.swift | 2 +- damus/Views/PostView.swift | 2 +- damus/Views/Posting/UserSearch.swift | 4 +-- damus/Views/Profile/EditMetadataView.swift | 3 +- damus/Views/Profile/EventProfileName.swift | 6 ++-- damus/Views/Profile/ProfileName.swift | 2 +- damus/Views/Profile/ProfileNameView.swift | 2 +- damus/Views/Profile/ProfilePicView.swift | 6 ++-- .../Profile/ProfilePictureSelector.swift | 2 +- damus/Views/Profile/ProfileView.swift | 10 +++--- damus/Views/ProfileActionSheetView.swift | 2 +- damus/Views/QRCodeView.swift | 7 ++-- damus/Views/ReplyView.swift | 3 +- damus/Views/Search/PullDownSearch.swift | 2 +- damus/Views/SearchResultsView.swift | 4 +-- damus/Views/SideMenuView.swift | 2 +- damus/Views/Wallet/WalletView.swift | 4 +-- damus/Views/Zaps/ProfileZapLinkView.swift | 2 +- damus/Views/Zaps/ZapTypePicker.swift | 2 +- nostrdb/Ndb.swift | 31 +++++++++++------ nostrdb/NdbTxn.swift | 11 +++---- 42 files changed, 126 insertions(+), 107 deletions(-) diff --git a/DamusNotificationService/NotificationFormatter.swift b/DamusNotificationService/NotificationFormatter.swift index 8c9bec54..615eaacc 100644 --- a/DamusNotificationService/NotificationFormatter.swift +++ b/DamusNotificationService/NotificationFormatter.swift @@ -118,9 +118,9 @@ struct NotificationFormatter { let src = zap.request.ev let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey - let name = profiles.lookup(id: pk).map { profile in - Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50) - }.value + let profile_txn = profiles.lookup(id: pk) + let profile = profile_txn?.unsafeUnownedValue + let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50) let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0)) let formattedSats = format_msats_abbrev(zap.invoice.amount) diff --git a/DamusNotificationService/NotificationService.swift b/DamusNotificationService/NotificationService.swift index 352fd1b6..4d3e8c46 100644 --- a/DamusNotificationService/NotificationService.swift +++ b/DamusNotificationService/NotificationService.swift @@ -28,7 +28,7 @@ class NotificationService: UNNotificationServiceExtension { Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex()) guard let state = NotificationExtensionState(), - let display_name = state.ndb.lookup_profile(nostr_event.pubkey).unsafeUnownedValue?.profile?.display_name // We are not holding the txn here. + let display_name = state.ndb.lookup_profile(nostr_event.pubkey)?.unsafeUnownedValue?.profile?.display_name // We are not holding the txn here. else { // Something failed to initialize so let's go for the next best thing guard let improved_content = NotificationFormatter.shared.format_message(event: nostr_event) else { diff --git a/damus/Components/NIP05Badge.swift b/damus/Components/NIP05Badge.swift index db7956af..9d8d9280 100644 --- a/damus/Components/NIP05Badge.swift +++ b/damus/Components/NIP05Badge.swift @@ -45,7 +45,7 @@ struct NIP05Badge: View { } var username_matches_nip05: Bool { - guard let name = profiles.lookup(id: pubkey).map({ p in p?.name }).value + guard let name = profiles.lookup(id: pubkey)?.map({ p in p?.name }).value else { return false } diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 98a5f1bf..e7e0a61c 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -370,14 +370,9 @@ struct ContentView: View { // wallet with an associated guard let ds = self.damus_state, let lud16 = nwc.lud16, - let keypair = ds.keypair.to_full() - else { - return - } - - let profile_txn = ds.profiles.lookup(id: ds.pubkey) - - guard let profile = profile_txn.unsafeUnownedValue, + let keypair = ds.keypair.to_full(), + let profile_txn = ds.profiles.lookup(id: ds.pubkey), + let profile = profile_txn.unsafeUnownedValue, lud16 != profile.lud16 else { return } @@ -514,10 +509,9 @@ struct ContentView: View { .onReceive(handle_notify(.onlyzaps_mode)) { hide in home.filter_events() - guard let ds = damus_state else { return } - let profile_txn = ds.profiles.lookup(id: ds.pubkey) - - guard let profile = profile_txn.unsafeUnownedValue, + guard let ds = damus_state, + let profile_txn = ds.profiles.lookup(id: ds.pubkey), + let profile = profile_txn.unsafeUnownedValue, let keypair = ds.keypair.to_full() else { return @@ -534,9 +528,9 @@ struct ContentView: View { } }, message: { if let pubkey = self.muting { - let name = damus_state!.profiles.lookup(id: pubkey).map { profile in - Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) - }.value + let profile_txn = damus_state!.profiles.lookup(id: pubkey) + let profile = profile_txn?.unsafeUnownedValue + let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) 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 muted.") @@ -595,9 +589,9 @@ struct ContentView: View { } }, message: { if let pubkey = muting { - let name = damus_state?.profiles.lookup(id: pubkey).map({ profile in - Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) - }).value ?? "unknown" + let profile_txn = damus_state?.profiles.lookup(id: pubkey) + let profile = profile_txn?.unsafeUnownedValue + let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) 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.") @@ -865,7 +859,8 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St switch query { case .profile(let pubkey): - if let record = state.ndb.lookup_profile(pubkey).unsafeUnownedValue, + if let profile_txn = state.ndb.lookup_profile(pubkey), + let record = profile_txn.unsafeUnownedValue, record.profile != nil { callback(.profile(pubkey)) diff --git a/damus/Models/EventsModel.swift b/damus/Models/EventsModel.swift index 4923fd17..7ce906db 100644 --- a/damus/Models/EventsModel.swift +++ b/damus/Models/EventsModel.swift @@ -66,7 +66,9 @@ class EventsModel: ObservableObject { case .auth: break case .eose: - let txn = NdbTxn(ndb: self.state.ndb) + guard let txn = NdbTxn(ndb: self.state.ndb) else { + return + } load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn) } } diff --git a/damus/Models/FollowersModel.swift b/damus/Models/FollowersModel.swift index ab78c4d2..b74a08b8 100644 --- a/damus/Models/FollowersModel.swift +++ b/damus/Models/FollowersModel.swift @@ -83,7 +83,7 @@ class FollowersModel: ObservableObject { case .eose(let sub_id): if sub_id == self.sub_id { - let txn = NdbTxn(ndb: self.damus_state.ndb) + guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return } load_profiles(relay_id: relay_id, txn: txn) } else if sub_id == self.profiles_id { damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id]) diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index 2e794fe5..4210ce3e 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -414,8 +414,10 @@ class HomeModel { print(msg) case .eose(let sub_id): - - let txn = NdbTxn(ndb: damus_state.ndb) + guard let txn = NdbTxn(ndb: damus_state.ndb) else { + return + } + if sub_id == dms_subid { var dms = dms.dms.flatMap { $0.events } dms.append(contentsOf: incoming_dms) diff --git a/damus/Models/NoteContent.swift b/damus/Models/NoteContent.swift index 889a4ba6..4ccaa681 100644 --- a/damus/Models/NoteContent.swift +++ b/damus/Models/NoteContent.swift @@ -187,7 +187,7 @@ func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText case .pubkey(let pk): let npub = bech32_pubkey(pk) let profile_txn = profiles.lookup(id: pk) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50) var attributedString = AttributedString(stringLiteral: "@\(disp)") attributedString.link = URL(string: "damus:nostr:\(npub)") diff --git a/damus/Models/NotificationsManager.swift b/damus/Models/NotificationsManager.swift index 3618cd3e..66a5d3a0 100644 --- a/damus/Models/NotificationsManager.swift +++ b/damus/Models/NotificationsManager.swift @@ -21,6 +21,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) guard let local_notification = generate_local_notification_object(from: ev, state: state) else { return } + create_local_notification(profiles: state.profiles, notify: local_notification) } @@ -76,7 +77,8 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu } else if type == .like, state.settings.like_notification, let evid = ev.referenced_ids.last, - let liked_event = state.ndb.lookup_note(evid).unsafeUnownedValue // We are only accessing it temporarily to generate notification content + let txn = state.ndb.lookup_note(evid), + let liked_event = txn.unsafeUnownedValue // We are only accessing it temporarily to generate notification content { let content_preview = render_notification_content_preview(ev: liked_event, profiles: state.profiles, keypair: state.keypair) return LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview) @@ -135,9 +137,9 @@ func render_notification_content_preview(ev: NostrEvent, profiles: Profiles, key } func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String { - return profiles.lookup(id: pubkey).map({ profile in - Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) - }).value + let profile_txn = profiles.lookup(id: pubkey) + let profile = profile_txn?.unsafeUnownedValue + return Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) } @MainActor @@ -173,8 +175,8 @@ func process_zap_event(state: HeadlessDamusState, ev: NostrEvent, completion: @e return } - guard let lnurl = state.profiles.lookup_with_timestamp(ptag) - .map({ pr in pr?.lnurl }).value else { + guard let txn = state.profiles.lookup_with_timestamp(ptag), + let lnurl = txn.map({ pr in pr?.lnurl }).value else { completion(.failed) return } @@ -221,7 +223,8 @@ func get_zap_target_pubkey(ev: NostrEvent, ndb: Ndb) -> Pubkey? { } // we can't trust the p tag on note zaps because they can be faked - guard let pk = ndb.lookup_note(etag).unsafeUnownedValue?.pubkey else { + guard let txn = ndb.lookup_note(etag), + let pk = txn.unsafeUnownedValue?.pubkey else { // We don't have the event in cache so we can't check the pubkey. // We could return this as an invalid zap but that wouldn't be correct diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift index 76e294a9..d0392659 100644 --- a/damus/Models/ProfileModel.swift +++ b/damus/Models/ProfileModel.swift @@ -128,7 +128,7 @@ class ProfileModel: ObservableObject, Equatable { break //notify(.notice, notice) case .eose: - let txn = NdbTxn(ndb: damus.ndb) + guard let txn = NdbTxn(ndb: damus.ndb) else { return } if resp.subid == sub_id { load_profiles(context: "profile", profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus, txn: txn) } diff --git a/damus/Models/SearchHomeModel.swift b/damus/Models/SearchHomeModel.swift index 07344f66..84ae5911 100644 --- a/damus/Models/SearchHomeModel.swift +++ b/damus/Models/SearchHomeModel.swift @@ -83,7 +83,7 @@ class SearchHomeModel: ObservableObject { // global events are not realtime unsubscribe(to: relay_id) - let txn = NdbTxn(ndb: damus_state.ndb) + guard let txn = NdbTxn(ndb: damus_state.ndb) else { return } load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn) } diff --git a/damus/Models/SearchModel.swift b/damus/Models/SearchModel.swift index 9dc49723..b61231af 100644 --- a/damus/Models/SearchModel.swift +++ b/damus/Models/SearchModel.swift @@ -80,7 +80,7 @@ class SearchModel: ObservableObject { self.loading = false if sub_id == self.sub_id { - let txn = NdbTxn(ndb: state.ndb) + guard let txn = NdbTxn(ndb: state.ndb) else { return } load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn) } } diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift index 06f63c7b..dbc86929 100644 --- a/damus/Models/ThreadModel.swift +++ b/damus/Models/ThreadModel.swift @@ -120,7 +120,7 @@ class ThreadModel: ObservableObject { } if sub_id == self.base_subid { - let txn = NdbTxn(ndb: damus_state.ndb) + guard let txn = NdbTxn(ndb: damus_state.ndb) else { return } load_profiles(context: "thread", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map)), damus_state: damus_state, txn: txn) } } diff --git a/damus/Models/ZapsModel.swift b/damus/Models/ZapsModel.swift index b20f3957..49870f2a 100644 --- a/damus/Models/ZapsModel.swift +++ b/damus/Models/ZapsModel.swift @@ -55,7 +55,7 @@ class ZapsModel: ObservableObject { break case .eose: let events = state.events.lookup_zaps(target: target).map { $0.request.ev } - let txn = NdbTxn(ndb: state.ndb) + guard let txn = NdbTxn(ndb: state.ndb) else { return } load_profiles(context: "zaps_model", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn) case .event(_, let ev): guard ev.kind == 9735, diff --git a/damus/Nostr/Profiles.swift b/damus/Nostr/Profiles.swift index ad9a5dde..71c5327b 100644 --- a/damus/Nostr/Profiles.swift +++ b/damus/Nostr/Profiles.swift @@ -73,24 +73,27 @@ class Profiles { profile_data(pubkey).zapper } - func lookup_with_timestamp(_ pubkey: Pubkey) -> NdbTxn { - return ndb.lookup_profile(pubkey) + func lookup_with_timestamp(_ pubkey: Pubkey) -> NdbTxn? { + ndb.lookup_profile(pubkey) } - func lookup_by_key(key: ProfileKey) -> NdbTxn { - return ndb.lookup_profile_by_key(key: key) + func lookup_by_key(key: ProfileKey) -> NdbTxn? { + ndb.lookup_profile_by_key(key: key) } func search(_ query: String, limit: Int, txn: NdbTxn) -> [Pubkey] { - return ndb.search_profile(query, limit: limit, txn: txn) + ndb.search_profile(query, limit: limit, txn: txn) } - func lookup(id: Pubkey) -> NdbTxn { - return ndb.lookup_profile(id).map({ pr in pr?.profile }) + func lookup(id: Pubkey) -> NdbTxn? { + guard let txn = ndb.lookup_profile(id) else { + return nil + } + return txn.map({ pr in pr?.profile }) } func lookup_key_by_pubkey(_ pubkey: Pubkey) -> ProfileKey? { - return ndb.lookup_profile_key(pubkey) + ndb.lookup_profile_key(pubkey) } func has_fresh_profile(id: Pubkey, txn: NdbTxn) -> Bool { diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift index e0aa1ca1..3775f99d 100644 --- a/damus/Views/ActionBar/EventActionBar.swift +++ b/damus/Views/ActionBar/EventActionBar.swift @@ -28,7 +28,9 @@ struct EventActionBar: View { } var lnurl: String? { - damus_state.profiles.lookup_with_timestamp(event.pubkey).map({ pr in pr?.lnurl }).value + damus_state.profiles.lookup_with_timestamp(event.pubkey)?.map({ pr in + pr?.lnurl + }).value } var show_like: Bool { diff --git a/damus/Views/BannerImageView.swift b/damus/Views/BannerImageView.swift index dee4e11e..9cdd2b18 100644 --- a/damus/Views/BannerImageView.swift +++ b/damus/Views/BannerImageView.swift @@ -78,11 +78,12 @@ struct BannerImageView: View { var body: some View { InnerBannerImageView(disable_animation: disable_animation, url: get_banner_url(banner: banner, pubkey: pubkey, profiles: profiles)) .onReceive(handle_notify(.profile_updated)) { updated in - guard updated.pubkey == self.pubkey else { + guard updated.pubkey == self.pubkey, + let profile_txn = profiles.lookup(id: updated.pubkey) + else { return } - let profile_txn = profiles.lookup(id: updated.pubkey) let profile = profile_txn.unsafeUnownedValue if let bannerImage = profile?.banner, bannerImage != self.banner { self.banner = bannerImage @@ -92,7 +93,7 @@ struct BannerImageView: View { } func get_banner_url(banner: String?, pubkey: Pubkey, profiles: Profiles) -> URL? { - let bannerUrlString = banner ?? profiles.lookup(id: pubkey).map({ p in p?.banner }).value ?? "" + let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.map({ p in p?.banner }).value ?? "" if let url = URL(string: bannerUrlString) { return url } diff --git a/damus/Views/Events/Components/ReplyDescription.swift b/damus/Views/Events/Components/ReplyDescription.swift index 90a64f2a..1a1c529c 100644 --- a/damus/Views/Events/Components/ReplyDescription.swift +++ b/damus/Views/Events/Components/ReplyDescription.swift @@ -38,7 +38,9 @@ func reply_desc(ndb: Ndb, event: NostrEvent, replying_to: NostrEvent?, locale: L return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.") } - let profile_txn = NdbTxn(ndb: ndb) + guard let profile_txn = NdbTxn(ndb: ndb) else { + return "" + } let names: [String] = pubkeys.map { pk in let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn) diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift index 16dab49c..434ff7ff 100644 --- a/damus/Views/FollowingView.swift +++ b/damus/Views/FollowingView.swift @@ -151,7 +151,7 @@ struct FollowingView: View { } .tabViewStyle(.page(indexDisplayMode: .never)) .onAppear { - let txn = NdbTxn(ndb: self.damus_state.ndb) + guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return } following.subscribe(txn: txn) } .onDisappear { diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index 47fceb7f..fc69b9ca 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -303,7 +303,7 @@ struct NoteContentView: View { class NoteArtifactsParts { var parts: [ArtifactPart] var words: Int - + init(parts: [ArtifactPart], words: Int) { self.parts = parts self.words = words diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift index b68d78b8..3ff89e28 100644 --- a/damus/Views/Notifications/NotificationsView.swift +++ b/damus/Views/Notifications/NotificationsView.swift @@ -61,7 +61,7 @@ struct NotificationsView: View { var mystery: some View { let profile_txn = state.profiles.lookup(id: state.pubkey) - let profile = profile_txn.unsafeUnownedValue + 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.") diff --git a/damus/Views/Onboarding/SuggestedUsersViewModel.swift b/damus/Views/Onboarding/SuggestedUsersViewModel.swift index f78ae7b6..a711e32f 100644 --- a/damus/Views/Onboarding/SuggestedUsersViewModel.swift +++ b/damus/Views/Onboarding/SuggestedUsersViewModel.swift @@ -36,7 +36,7 @@ class SuggestedUsersViewModel: ObservableObject { func suggestedUser(pubkey: Pubkey) -> SuggestedUser? { let profile_txn = damus_state.profiles.lookup(id: pubkey) - if let profile = profile_txn.unsafeUnownedValue, + if let profile = profile_txn?.unsafeUnownedValue, let user = SuggestedUser(name: profile.name, about: profile.about, picture: profile.picture, pubkey: pubkey) { return user } diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index 5803e3db..8fabf0fe 100644 --- a/damus/Views/PostView.swift +++ b/damus/Views/PostView.swift @@ -181,7 +181,7 @@ struct PostView: View { } let profile_txn = damus_state.profiles.lookup(id: pubkey) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue return user_tag_attr_string(profile: profile, pubkey: pubkey) } diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift index e783eff2..c4136e35 100644 --- a/damus/Views/Posting/UserSearch.swift +++ b/damus/Views/Posting/UserSearch.swift @@ -17,7 +17,7 @@ struct UserSearch: View { @EnvironmentObject var tagModel: TagModel var users: [Pubkey] { - let txn = NdbTxn(ndb: damus_state.ndb) + guard let txn = NdbTxn(ndb: damus_state.ndb) else { return [] } return search_profiles(profiles: damus_state.profiles, search: search, txn: txn).sorted { a, b in let aFriendTypePriority = get_friend_type(contacts: damus_state.contacts, pubkey: a)?.priority ?? 0 let bFriendTypePriority = get_friend_type(contacts: damus_state.contacts, pubkey: b)?.priority ?? 0 @@ -33,7 +33,7 @@ struct UserSearch: View { func on_user_tapped(pk: Pubkey) { let profile_txn = damus_state.profiles.lookup(id: pk) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue let user_tag = user_tag_attr_string(profile: profile, pubkey: pk) appendUserTag(withTag: user_tag) diff --git a/damus/Views/Profile/EditMetadataView.swift b/damus/Views/Profile/EditMetadataView.swift index 07e3e5ce..99eb473d 100644 --- a/damus/Views/Profile/EditMetadataView.swift +++ b/damus/Views/Profile/EditMetadataView.swift @@ -30,7 +30,8 @@ struct EditMetadataView: View { init(damus_state: DamusState) { self.damus_state = damus_state - let data = damus_state.profiles.lookup(id: damus_state.pubkey).unsafeUnownedValue + let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) + let data = profile_txn?.unsafeUnownedValue _name = State(initialValue: data?.name ?? "") _display_name = State(initialValue: data?.display_name ?? "") diff --git a/damus/Views/Profile/EventProfileName.swift b/damus/Views/Profile/EventProfileName.swift index 19435e22..5a31822b 100644 --- a/damus/Views/Profile/EventProfileName.swift +++ b/damus/Views/Profile/EventProfileName.swift @@ -24,7 +24,7 @@ struct EventProfileName: View { self.damus_state = damus self.pubkey = pubkey self.size = size - let donation = damus.ndb.lookup_profile(pubkey).map({ p in p?.profile?.damus_donation }).value + let donation = damus.ndb.lookup_profile(pubkey)?.map({ p in p?.profile?.damus_donation }).value self._donation = State(wrappedValue: donation) is_purple_user = nil } @@ -65,7 +65,7 @@ struct EventProfileName: View { var body: some View { let profile_txn = damus_state.profiles.lookup(id: pubkey) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue HStack(spacing: 2) { switch current_display_name(profile) { case .one(let one): @@ -109,7 +109,7 @@ struct EventProfileName: View { } let profile_txn = damus_state.profiles.lookup(id: update.pubkey) - guard let profile = profile_txn.unsafeUnownedValue else { return } + guard let profile = profile_txn?.unsafeUnownedValue else { return } let display_name = Profile.displayName(profile: profile, pubkey: pubkey) if display_name != self.display_name { diff --git a/damus/Views/Profile/ProfileName.swift b/damus/Views/Profile/ProfileName.swift index b7911b01..92b8cdbe 100644 --- a/damus/Views/Profile/ProfileName.swift +++ b/damus/Views/Profile/ProfileName.swift @@ -87,7 +87,7 @@ struct ProfileName: View { var body: some View { let profile_txn = damus_state.profiles.lookup(id: pubkey) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue HStack(spacing: 2) { Text(verbatim: "\(prefix)\(name_choice(profile: profile))") diff --git a/damus/Views/Profile/ProfileNameView.swift b/damus/Views/Profile/ProfileNameView.swift index 44c585b4..f3feed2b 100644 --- a/damus/Views/Profile/ProfileNameView.swift +++ b/damus/Views/Profile/ProfileNameView.swift @@ -17,7 +17,7 @@ struct ProfileNameView: View { Group { VStack(alignment: .leading) { let profile_txn = self.damus.profiles.lookup(id: pubkey) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue switch Profile.displayName(profile: profile, pubkey: pubkey) { case .one: diff --git a/damus/Views/Profile/ProfilePicView.swift b/damus/Views/Profile/ProfilePicView.swift index cc3773ed..20efd043 100644 --- a/damus/Views/Profile/ProfilePicView.swift +++ b/damus/Views/Profile/ProfilePicView.swift @@ -84,7 +84,7 @@ struct ProfilePicView: View { } func get_lnurl() -> String? { - return profiles.lookup_with_timestamp(pubkey).unsafeUnownedValue?.lnurl + return profiles.lookup_with_timestamp(pubkey)?.unsafeUnownedValue?.lnurl } var body: some View { @@ -102,7 +102,7 @@ struct ProfilePicView: View { } case .remote(pubkey: let pk): let profile_txn = profiles.lookup(id: pk) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue if let pic = profile?.picture { self.picture = pic } @@ -126,7 +126,7 @@ struct ProfilePicView: View { } func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> URL { - let pic = picture ?? profiles.lookup(id: pubkey).map({ $0?.picture }).value ?? robohash(pubkey) + let pic = picture ?? profiles.lookup(id: pubkey)?.map({ $0?.picture }).value ?? robohash(pubkey) if let url = URL(string: pic) { return url } diff --git a/damus/Views/Profile/ProfilePictureSelector.swift b/damus/Views/Profile/ProfilePictureSelector.swift index 77fc2d0f..5559746c 100644 --- a/damus/Views/Profile/ProfilePictureSelector.swift +++ b/damus/Views/Profile/ProfilePictureSelector.swift @@ -43,7 +43,7 @@ struct EditProfilePictureView: View { if let profile_url { return profile_url } else if let state = damus_state, - let picture = state.profiles.lookup(id: pubkey).map({ pr in pr?.picture }).value { + let picture = state.profiles.lookup(id: pubkey)?.map({ pr in pr?.picture }).value { return URL(string: picture) } else { return profile_url ?? URL(string: robohash(pubkey)) diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift index 0b9e1a44..47dc0fe8 100644 --- a/damus/Views/Profile/ProfileView.swift +++ b/damus/Views/Profile/ProfileView.swift @@ -24,10 +24,10 @@ func follow_btn_txt(_ fs: FollowState, follows_you: Bool) -> String { } } -func followedByString(txn: NdbTxn, _ friend_intersection: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String { +func followedByString(_ friend_intersection: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String { let bundle = bundleForLocale(locale: locale) let names: [String] = friend_intersection.prefix(3).map { pk in - let profile = ndb.lookup_profile_with_txn(pk, txn: txn)?.profile + let profile = ndb.lookup_profile(pk)?.unsafeUnownedValue?.profile return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20) } @@ -331,7 +331,7 @@ struct ProfileView: View { var aboutSection: some View { VStack(alignment: .leading, spacing: 8.0) { let profile_txn = damus_state.profiles.lookup_with_timestamp(profile.pubkey) - let profile_data = profile_txn.unsafeUnownedValue + let profile_data = profile_txn?.unsafeUnownedValue nameSection(profile_data: profile_data) @@ -398,7 +398,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(txn: profile_txn, friended_followers, ndb: damus_state.ndb) + let followedByString = followedByString(friended_followers, ndb: damus_state.ndb) Text(followedByString) .font(.subheadline).foregroundColor(.gray) .multilineTextAlignment(.leading) @@ -499,7 +499,7 @@ extension View { func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) { let profile_txn = profiles.lookup(id: pubkey) - guard let profile = profile_txn.unsafeUnownedValue, + guard let profile = profile_txn?.unsafeUnownedValue, let nip05 = profile.nip05, profiles.is_validated(pubkey) == nil else { diff --git a/damus/Views/ProfileActionSheetView.swift b/damus/Views/ProfileActionSheetView.swift index fedbd486..bcec3929 100644 --- a/damus/Views/ProfileActionSheetView.swift +++ b/damus/Views/ProfileActionSheetView.swift @@ -30,7 +30,7 @@ struct ProfileActionSheetView: View { func profile_data() -> ProfileRecord? { let profile_txn = damus_state.profiles.lookup_with_timestamp(profile.pubkey) - return profile_txn.unsafeUnownedValue + return profile_txn?.unsafeUnownedValue } func get_profile() -> Profile? { diff --git a/damus/Views/QRCodeView.swift b/damus/Views/QRCodeView.swift index af91d2d6..772b3756 100644 --- a/damus/Views/QRCodeView.swift +++ b/damus/Views/QRCodeView.swift @@ -119,10 +119,11 @@ struct QRCodeView: View { var QRView: some View { VStack(alignment: .center) { 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) + let profile = profile_txn?.unsafeUnownedValue + let our_profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) + let our_profile = our_profile_txn?.unsafeUnownedValue - if our_profile?.profile?.picture != nil { + if our_profile?.picture != nil { ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) .padding(.top, 50) } else { diff --git a/damus/Views/ReplyView.swift b/damus/Views/ReplyView.swift index 27042166..7686017b 100644 --- a/damus/Views/ReplyView.swift +++ b/damus/Views/ReplyView.swift @@ -24,11 +24,10 @@ 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.ndb.lookup_profile_with_txn(pk, txn: txn)?.profile + let prof = damus.ndb.lookup_profile(pk)?.unsafeUnownedValue?.profile return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50) } .joined(separator: " ") diff --git a/damus/Views/Search/PullDownSearch.swift b/damus/Views/Search/PullDownSearch.swift index e23ba1dd..21e98559 100644 --- a/damus/Views/Search/PullDownSearch.swift +++ b/damus/Views/Search/PullDownSearch.swift @@ -31,7 +31,7 @@ struct PullDownSearchView: View { } do { - let txn = NdbTxn(ndb: state.ndb) + guard let txn = NdbTxn(ndb: state.ndb) else { return } for note_key in note_keys { guard let note = state.ndb.lookup_note_by_key_with_txn(note_key, txn: txn) else { continue diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift index b2748880..18f6e221 100644 --- a/damus/Views/SearchResultsView.swift +++ b/damus/Views/SearchResultsView.swift @@ -108,11 +108,11 @@ struct SearchResultsView: View { } .frame(maxHeight: .infinity) .onAppear { - let txn = NdbTxn.init(ndb: damus_state.ndb) + guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return } self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn) } .onChange(of: search) { new in - let txn = NdbTxn.init(ndb: damus_state.ndb) + guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return } self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn) } } diff --git a/damus/Views/SideMenuView.swift b/damus/Views/SideMenuView.swift index 4b2bbfc0..ca5968f4 100644 --- a/damus/Views/SideMenuView.swift +++ b/damus/Views/SideMenuView.swift @@ -90,7 +90,7 @@ struct SideMenuView: View { var TopProfile: some View { let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) - let profile = profile_txn.unsafeUnownedValue + let profile = profile_txn?.unsafeUnownedValue return VStack(alignment: .leading, spacing: verticalSpacing) { HStack { ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift index 4eaf6449..ff93a745 100644 --- a/damus/Views/Wallet/WalletView.swift +++ b/damus/Views/Wallet/WalletView.swift @@ -165,7 +165,7 @@ struct WalletView: View { } .onChange(of: settings.donation_percent) { p in let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) - guard let profile = profile_txn.unsafeUnownedValue else { + guard let profile = profile_txn?.unsafeUnownedValue else { return } @@ -177,7 +177,7 @@ struct WalletView: View { let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey) guard let keypair = damus_state.keypair.to_full(), - let profile = profile_txn.unsafeUnownedValue, + let profile = profile_txn?.unsafeUnownedValue, model.initial_percent != profile.damus_donation else { return diff --git a/damus/Views/Zaps/ProfileZapLinkView.swift b/damus/Views/Zaps/ProfileZapLinkView.swift index 89e480ee..ad130327 100644 --- a/damus/Views/Zaps/ProfileZapLinkView.swift +++ b/damus/Views/Zaps/ProfileZapLinkView.swift @@ -34,7 +34,7 @@ struct ProfileZapLinkView: View { self.action = action let profile_txn = damus_state.profiles.lookup_with_timestamp(pubkey) - let record = profile_txn.unsafeUnownedValue + let record = profile_txn?.unsafeUnownedValue self.reactions_enabled = record?.profile?.reactions ?? true self.lud16 = record?.profile?.lud06 self.lnurl = record?.lnurl diff --git a/damus/Views/Zaps/ZapTypePicker.swift b/damus/Views/Zaps/ZapTypePicker.swift index a9416d4d..a7e3b3db 100644 --- a/damus/Views/Zaps/ZapTypePicker.swift +++ b/damus/Views/Zaps/ZapTypePicker.swift @@ -98,7 +98,7 @@ func zap_type_desc(type: ZapType, profiles: Profiles, pubkey: Pubkey) -> String 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_txn = profiles.lookup(id: pubkey) - let prof = prof_txn.unsafeUnownedValue + let prof = prof_txn?.unsafeUnownedValue let name = Profile.displayName(profile: prof, pubkey: pubkey).username.truncate(maxLength: 50) return String.localizedStringWithFormat(NSLocalizedString("private_zap_description", value: "Only '%@' will see that you zapped them", comment: "Description of private zap type where the zap is sent privately and does not identify the user to the public."), name) case .non_zap: diff --git a/nostrdb/Ndb.swift b/nostrdb/Ndb.swift index 3fdea3d2..b9956460 100644 --- a/nostrdb/Ndb.swift +++ b/nostrdb/Ndb.swift @@ -186,6 +186,7 @@ class Ndb { throw DatabaseError.failed_open } + self.closed = false self.ndb = db } @@ -198,7 +199,7 @@ class Ndb { } func text_search(query: String, limit: Int = 32, order: NdbSearchOrder = .newest_first) -> [NoteKey] { - let txn = NdbTxn(ndb: self) + guard let txn = NdbTxn(ndb: self) else { return [] } var results = ndb_text_search_results() let res = query.withCString { q in let order = order == .newest_first ? NDB_ORDER_DESCENDING : NDB_ORDER_ASCENDING @@ -243,7 +244,7 @@ class Ndb { return note_ids } - func lookup_note_by_key(_ key: NoteKey) -> NdbTxn { + func lookup_note_by_key(_ key: NoteKey) -> NdbTxn? { return NdbTxn(ndb: self) { txn in lookup_note_by_key_with_txn(key, txn: txn) } @@ -301,7 +302,7 @@ class Ndb { lookup_profile_by_key_inner(key, txn: txn) } - func lookup_profile_by_key(key: ProfileKey) -> NdbTxn { + func lookup_profile_by_key(key: ProfileKey) -> NdbTxn? { return NdbTxn(ndb: self) { txn in lookup_profile_by_key_inner(key, txn: txn) } @@ -312,9 +313,13 @@ class Ndb { } func lookup_profile_key(_ pubkey: Pubkey) -> ProfileKey? { - return NdbTxn(ndb: self) { txn in + guard let txn = NdbTxn(ndb: self, with: { txn in lookup_profile_key_with_txn(pubkey, txn: txn) - }.value + }) else { + return nil + } + + return txn.value } func lookup_profile_key_with_txn(_ pubkey: Pubkey, txn: NdbTxn) -> ProfileKey? { @@ -342,17 +347,23 @@ class Ndb { } func lookup_note_key(_ id: NoteId) -> NoteKey? { - NdbTxn(ndb: self, with: { txn in lookup_note_key_with_txn(id, txn: txn) }).value + guard let txn = NdbTxn(ndb: self, with: { txn in + lookup_note_key_with_txn(id, txn: txn) + }) else { + return nil + } + + return txn.value } - func lookup_note(_ id: NoteId) -> NdbTxn { - return NdbTxn(ndb: self) { txn in + func lookup_note(_ id: NoteId) -> NdbTxn? { + NdbTxn(ndb: self) { txn in lookup_note_with_txn_inner(id: id, txn: txn) } } - func lookup_profile(_ pubkey: Pubkey) -> NdbTxn { - return NdbTxn(ndb: self) { txn in + func lookup_profile(_ pubkey: Pubkey) -> NdbTxn? { + NdbTxn(ndb: self) { txn in lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn) } } diff --git a/nostrdb/NdbTxn.swift b/nostrdb/NdbTxn.swift index a7628902..76e9ccaf 100644 --- a/nostrdb/NdbTxn.swift +++ b/nostrdb/NdbTxn.swift @@ -18,8 +18,9 @@ class NdbTxn { var moved: Bool var inherited: Bool - init(ndb: Ndb, with: (NdbTxn) -> T = { _ in () }) { - #if TXNDEBUG + init?(ndb: Ndb, with: (NdbTxn) -> T = { _ in () }) { + guard !ndb.closed else { return nil } +#if TXNDEBUG txn_count += 1 print("opening transaction \(txn_count)") #endif @@ -31,11 +32,7 @@ class NdbTxn { self.txn = ndb_txn() let ok = ndb_begin_query(ndb.ndb.ndb, &self.txn) != 0 if !ok { - self.moved = false - self.txn = ndb_txn() - self.inherited = true - self.val = with(self) - return + return nil } Thread.current.threadDictionary["ndb_txn"] = self.txn self.inherited = false