Add sync mechanism to prevent background crashes and fix ndb reopen order
This adds a sync mechanism in Ndb.swift to coordinate certain usage of nostrdb.c calls and the need to close nostrdb due to app lifecycle requirements. Furthermore, it fixes the order of operations when re-opening NostrDB, to avoid race conditions where a query uses an older Ndb generation. This sync mechanism allows multiple queries to happen simultaneously (from the Swift-side), while preventing ndb from simultaneously closing during such usages. It also does that while keeping the Ndb interface sync and nonisolated, which keeps the API easy to use from Swift/SwiftUI and allows for parallel operations to occur. If Swift Actors were to be used (e.g. creating an NdbActor), the Ndb.swift interface would change in such a way that it would propagate the need for several changes throughout the codebase, including loading logic in some ViewModels. Furthermore, it would likely decrease performance by forcing Ndb.swift operations to run sequentially when they could run in parallel. Changelog-Fixed: Fixed crashes that happened when the app went into background mode Closes: https://github.com/damus-io/damus/issues/3245 Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -125,7 +125,7 @@ struct NotificationFormatter {
|
|||||||
let src = zap.request.ev
|
let src = zap.request.ev
|
||||||
let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey
|
let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey
|
||||||
|
|
||||||
let profile = profiles.lookup(id: pk)
|
let profile = try? profiles.lookup(id: pk)
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50)
|
let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50)
|
||||||
|
|
||||||
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
|
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sender_profile = {
|
let sender_profile = {
|
||||||
let profile = state.profiles.lookup(id: nostr_event.pubkey)
|
let profile = try? state.profiles.lookup(id: nostr_event.pubkey)
|
||||||
let picture = ((profile?.picture.map { URL(string: $0) }) ?? URL(string: robohash(nostr_event.pubkey)))!
|
let picture = ((profile?.picture.map { URL(string: $0) }) ?? URL(string: robohash(nostr_event.pubkey)))!
|
||||||
return ProfileBuf(picture: picture,
|
return ProfileBuf(picture: picture,
|
||||||
name: profile?.name,
|
name: profile?.name,
|
||||||
@@ -185,7 +185,7 @@ func message_intent_from_note(ndb: Ndb, sender_profile: ProfileBuf, content: Str
|
|||||||
|
|
||||||
// gather recipients
|
// gather recipients
|
||||||
if let recipient_note_id = note.direct_replies() {
|
if let recipient_note_id = note.direct_replies() {
|
||||||
let replying_to_pk = ndb.lookup_note(recipient_note_id, borrow: { replying_to_note -> Pubkey? in
|
let replying_to_pk = try? ndb.lookup_note(recipient_note_id, borrow: { replying_to_note -> Pubkey? in
|
||||||
switch replying_to_note {
|
switch replying_to_note {
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
case .some(let note): return note.pubkey
|
case .some(let note): return note.pubkey
|
||||||
@@ -251,7 +251,7 @@ func message_intent_from_note(ndb: Ndb, sender_profile: ProfileBuf, content: Str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pubkey_to_inperson(ndb: Ndb, pubkey: Pubkey, our_pubkey: Pubkey) async -> INPerson {
|
func pubkey_to_inperson(ndb: Ndb, pubkey: Pubkey, our_pubkey: Pubkey) async -> INPerson {
|
||||||
let profile = ndb.lookup_profile(pubkey, borrow: { profileRecord in
|
let profile = try? ndb.lookup_profile(pubkey, borrow: { profileRecord in
|
||||||
switch profileRecord {
|
switch profileRecord {
|
||||||
case .some(let pr): return pr.profile
|
case .some(let pr): return pr.profile
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
|
|||||||
@@ -1670,6 +1670,10 @@
|
|||||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
|
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
|
||||||
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
|
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
|
||||||
D751FA992EF62C8100E10F1B /* ProfilesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */; };
|
D751FA992EF62C8100E10F1B /* ProfilesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */; };
|
||||||
|
D75154BF2EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */; };
|
||||||
|
D75154C02EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */; };
|
||||||
|
D75154C12EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */; };
|
||||||
|
D75154C22EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */; };
|
||||||
D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; };
|
D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; };
|
||||||
D755B28D2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
D755B28D2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
||||||
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
||||||
@@ -1711,6 +1715,7 @@
|
|||||||
D78F08182D7F7F7500FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
D78F08182D7F7F7500FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
||||||
D78F08192D7F7F7500FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
D78F08192D7F7F7500FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
||||||
D78F081A2D7F803100FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
D78F081A2D7F803100FC6C75 /* NIP04.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78F08162D7F7F6C00FC6C75 /* NIP04.swift */; };
|
||||||
|
D795356B2EBD28A800AACF98 /* AppLifecycleHandlingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D795356A2EBD289D00AACF98 /* AppLifecycleHandlingTests.swift */; };
|
||||||
D798D21A2B0856CC00234419 /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
D798D21A2B0856CC00234419 /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||||
D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; };
|
D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; };
|
||||||
D798D21C2B0857E400234419 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
|
D798D21C2B0857E400234419 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
|
||||||
@@ -2770,6 +2775,7 @@
|
|||||||
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
||||||
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
|
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
|
||||||
D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilesManagerTests.swift; sourceTree = "<group>"; };
|
D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilesManagerTests.swift; sourceTree = "<group>"; };
|
||||||
|
D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbUseLock.swift; sourceTree = "<group>"; };
|
||||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = "<group>"; };
|
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = "<group>"; };
|
||||||
D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP37Draft.swift; sourceTree = "<group>"; };
|
D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP37Draft.swift; sourceTree = "<group>"; };
|
||||||
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
|
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
|
||||||
@@ -2790,6 +2796,7 @@
|
|||||||
D78F080B2D7F78EB00FC6C75 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
D78F080B2D7F78EB00FC6C75 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||||
D78F08102D7F78F600FC6C75 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
D78F08102D7F78F600FC6C75 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
||||||
D78F08162D7F7F6C00FC6C75 /* NIP04.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP04.swift; sourceTree = "<group>"; };
|
D78F08162D7F7F6C00FC6C75 /* NIP04.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP04.swift; sourceTree = "<group>"; };
|
||||||
|
D795356A2EBD289D00AACF98 /* AppLifecycleHandlingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLifecycleHandlingTests.swift; sourceTree = "<group>"; };
|
||||||
D798D21D2B0858BB00234419 /* MigratedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratedTypes.swift; sourceTree = "<group>"; };
|
D798D21D2B0858BB00234419 /* MigratedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratedTypes.swift; sourceTree = "<group>"; };
|
||||||
D798D2272B085CDA00234419 /* NdbNote+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NdbNote+.swift"; sourceTree = "<group>"; };
|
D798D2272B085CDA00234419 /* NdbNote+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NdbNote+.swift"; sourceTree = "<group>"; };
|
||||||
D798D22B2B086C7400234419 /* NostrEvent+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NostrEvent+.swift"; sourceTree = "<group>"; };
|
D798D22B2B086C7400234419 /* NostrEvent+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NostrEvent+.swift"; sourceTree = "<group>"; };
|
||||||
@@ -3301,6 +3308,7 @@
|
|||||||
4C9054862A6AEB4500811EEC /* nostrdb */ = {
|
4C9054862A6AEB4500811EEC /* nostrdb */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D75154BE2EC5910600BF2CB2 /* NdbUseLock.swift */,
|
||||||
D74EC84E2E1856AF0091DC51 /* NonCopyableLinkedList.swift */,
|
D74EC84E2E1856AF0091DC51 /* NonCopyableLinkedList.swift */,
|
||||||
D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */,
|
D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */,
|
||||||
D7F5630F2DEE71BB008509DE /* NdbFilter.swift */,
|
D7F5630F2DEE71BB008509DE /* NdbFilter.swift */,
|
||||||
@@ -5281,6 +5289,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */,
|
D751FA982EF62C8100E10F1B /* ProfilesManagerTests.swift */,
|
||||||
|
D795356A2EBD289D00AACF98 /* AppLifecycleHandlingTests.swift */,
|
||||||
D7EBF8BD2E594708004EAE29 /* test_notes.jsonl */,
|
D7EBF8BD2E594708004EAE29 /* test_notes.jsonl */,
|
||||||
D7EBF8BA2E5901F7004EAE29 /* NostrNetworkManagerTests.swift */,
|
D7EBF8BA2E5901F7004EAE29 /* NostrNetworkManagerTests.swift */,
|
||||||
D7EBF8BF2E5D39D1004EAE29 /* ThreadModelTests.swift */,
|
D7EBF8BF2E5D39D1004EAE29 /* ThreadModelTests.swift */,
|
||||||
@@ -5743,6 +5752,7 @@
|
|||||||
4CC6AA792CAB688500989CEF /* sha256.c in Sources */,
|
4CC6AA792CAB688500989CEF /* sha256.c in Sources */,
|
||||||
4CC6AA7B2CAB688500989CEF /* likely.c in Sources */,
|
4CC6AA7B2CAB688500989CEF /* likely.c in Sources */,
|
||||||
4CC6AA7F2CAB688500989CEF /* htable.c in Sources */,
|
4CC6AA7F2CAB688500989CEF /* htable.c in Sources */,
|
||||||
|
D75154C02EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */,
|
||||||
4CC6AA862CAB688500989CEF /* list.c in Sources */,
|
4CC6AA862CAB688500989CEF /* list.c in Sources */,
|
||||||
4CC6AA872CAB688500989CEF /* utf8.c in Sources */,
|
4CC6AA872CAB688500989CEF /* utf8.c in Sources */,
|
||||||
4CC6AA892CAB688500989CEF /* debug.c in Sources */,
|
4CC6AA892CAB688500989CEF /* debug.c in Sources */,
|
||||||
@@ -6266,6 +6276,7 @@
|
|||||||
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */,
|
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */,
|
||||||
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||||
|
D795356B2EBD28A800AACF98 /* AppLifecycleHandlingTests.swift in Sources */,
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
||||||
B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */,
|
B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */,
|
||||||
D7DB1FEE2D5AC51B00CF06DA /* NIP44v2EncryptionTests.swift in Sources */,
|
D7DB1FEE2D5AC51B00CF06DA /* NIP44v2EncryptionTests.swift in Sources */,
|
||||||
@@ -6785,6 +6796,7 @@
|
|||||||
82D6FC3A2CD99F7900C925F4 /* WideEventView.swift in Sources */,
|
82D6FC3A2CD99F7900C925F4 /* WideEventView.swift in Sources */,
|
||||||
82D6FC3B2CD99F7900C925F4 /* LongformView.swift in Sources */,
|
82D6FC3B2CD99F7900C925F4 /* LongformView.swift in Sources */,
|
||||||
82D6FC3C2CD99F7900C925F4 /* LongformPreview.swift in Sources */,
|
82D6FC3C2CD99F7900C925F4 /* LongformPreview.swift in Sources */,
|
||||||
|
D75154C22EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */,
|
||||||
82D6FC3D2CD99F7900C925F4 /* EventShell.swift in Sources */,
|
82D6FC3D2CD99F7900C925F4 /* EventShell.swift in Sources */,
|
||||||
82D6FC3E2CD99F7900C925F4 /* MentionView.swift in Sources */,
|
82D6FC3E2CD99F7900C925F4 /* MentionView.swift in Sources */,
|
||||||
82D6FC3F2CD99F7900C925F4 /* EventLoaderView.swift in Sources */,
|
82D6FC3F2CD99F7900C925F4 /* EventLoaderView.swift in Sources */,
|
||||||
@@ -7210,6 +7222,7 @@
|
|||||||
D73E5F302C6A97F4007EB227 /* EventProfile.swift in Sources */,
|
D73E5F302C6A97F4007EB227 /* EventProfile.swift in Sources */,
|
||||||
D73E5F312C6A97F4007EB227 /* EventMenu.swift in Sources */,
|
D73E5F312C6A97F4007EB227 /* EventMenu.swift in Sources */,
|
||||||
D73E5F322C6A97F4007EB227 /* EventMutingContainerView.swift in Sources */,
|
D73E5F322C6A97F4007EB227 /* EventMutingContainerView.swift in Sources */,
|
||||||
|
D75154C12EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */,
|
||||||
D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */,
|
D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */,
|
||||||
5C8F97362EB46145009399B1 /* LiveStreamView.swift in Sources */,
|
5C8F97362EB46145009399B1 /* LiveStreamView.swift in Sources */,
|
||||||
D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */,
|
D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */,
|
||||||
@@ -7474,6 +7487,7 @@
|
|||||||
4CC6AAC52CAB688500989CEF /* likely.c in Sources */,
|
4CC6AAC52CAB688500989CEF /* likely.c in Sources */,
|
||||||
4CC6AAC92CAB688500989CEF /* htable.c in Sources */,
|
4CC6AAC92CAB688500989CEF /* htable.c in Sources */,
|
||||||
4CC6AAD02CAB688500989CEF /* list.c in Sources */,
|
4CC6AAD02CAB688500989CEF /* list.c in Sources */,
|
||||||
|
D75154BF2EC5910A00BF2CB2 /* NdbUseLock.swift in Sources */,
|
||||||
4CC6AAD12CAB688500989CEF /* utf8.c in Sources */,
|
4CC6AAD12CAB688500989CEF /* utf8.c in Sources */,
|
||||||
4CC6AAD32CAB688500989CEF /* debug.c in Sources */,
|
4CC6AAD32CAB688500989CEF /* debug.c in Sources */,
|
||||||
4CC6AAD42CAB688500989CEF /* str.c in Sources */,
|
4CC6AAD42CAB688500989CEF /* str.c in Sources */,
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ struct ContentView: View {
|
|||||||
guard let ds = self.damus_state,
|
guard let ds = self.damus_state,
|
||||||
let lud16 = nwc.lud16,
|
let lud16 = nwc.lud16,
|
||||||
let keypair = ds.keypair.to_full(),
|
let keypair = ds.keypair.to_full(),
|
||||||
let profile = ds.profiles.lookup(id: ds.pubkey),
|
let profile = try? ds.profiles.lookup(id: ds.pubkey),
|
||||||
lud16 != profile.lud16 else {
|
lud16 != profile.lud16 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -560,7 +560,7 @@ struct ContentView: View {
|
|||||||
home.filter_events()
|
home.filter_events()
|
||||||
|
|
||||||
guard let ds = damus_state,
|
guard let ds = damus_state,
|
||||||
let profile = ds.profiles.lookup(id: ds.pubkey),
|
let profile = try? ds.profiles.lookup(id: ds.pubkey),
|
||||||
let keypair = ds.keypair.to_full()
|
let keypair = ds.keypair.to_full()
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
@@ -578,7 +578,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}, message: {
|
}, message: {
|
||||||
if case let .user(pubkey, _) = self.muting {
|
if case let .user(pubkey, _) = self.muting {
|
||||||
let profile = damus_state!.profiles.lookup(id: pubkey)
|
let profile = try? damus_state!.profiles.lookup(id: pubkey)
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
|
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.")
|
Text("\(name) has been muted", comment: "Alert message that informs a user was muted.")
|
||||||
} else {
|
} else {
|
||||||
@@ -644,7 +644,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}, message: {
|
}, message: {
|
||||||
if case let .user(pubkey, _) = muting {
|
if case let .user(pubkey, _) = muting {
|
||||||
let profile = damus_state?.profiles.lookup(id: pubkey)
|
let profile = try? damus_state?.profiles.lookup(id: pubkey)
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
|
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.")
|
Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
private func publishProfileUpdates(metadataEvent: borrowing UnownedNdbNote) {
|
private func publishProfileUpdates(metadataEvent: borrowing UnownedNdbNote) {
|
||||||
let now = UInt64(Date.now.timeIntervalSince1970)
|
let now = UInt64(Date.now.timeIntervalSince1970)
|
||||||
ndb.write_profile_last_fetched(pubkey: metadataEvent.pubkey, fetched_at: now)
|
try? ndb.write_profile_last_fetched(pubkey: metadataEvent.pubkey, fetched_at: now)
|
||||||
|
|
||||||
if let relevantStreams = streams[metadataEvent.pubkey] {
|
if let relevantStreams = streams[metadataEvent.pubkey] {
|
||||||
// If we have the user metadata event in ndb, then we should have the profile record as well.
|
// If we have the user metadata event in ndb, then we should have the profile record as well.
|
||||||
guard let profile = ndb.lookup_profile_and_copy(metadataEvent.pubkey) else { return }
|
guard let profile = try? ndb.lookup_profile_and_copy(metadataEvent.pubkey) else { return }
|
||||||
for relevantStream in relevantStreams.values {
|
for relevantStream in relevantStreams.values {
|
||||||
relevantStream.continuation.yield(profile)
|
relevantStream.continuation.yield(profile)
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ extension NostrNetworkManager {
|
|||||||
/// This is useful for local profile changes (e.g., nip05 validation, donation percentage updates)
|
/// This is useful for local profile changes (e.g., nip05 validation, donation percentage updates)
|
||||||
func notifyProfileUpdate(pubkey: Pubkey) {
|
func notifyProfileUpdate(pubkey: Pubkey) {
|
||||||
if let relevantStreams = streams[pubkey] {
|
if let relevantStreams = streams[pubkey] {
|
||||||
guard let profile = ndb.lookup_profile_and_copy(pubkey) else { return }
|
guard let profile = try? ndb.lookup_profile_and_copy(pubkey) else { return }
|
||||||
for relevantStream in relevantStreams.values {
|
for relevantStream in relevantStreams.values {
|
||||||
relevantStream.continuation.yield(profile)
|
relevantStream.continuation.yield(profile)
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
// Yield cached profile immediately so views don't flash placeholder content.
|
// Yield cached profile immediately so views don't flash placeholder content.
|
||||||
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
||||||
if yieldCached, let existingProfile = ndb.lookup_profile_and_copy(pubkey) {
|
if yieldCached, let existingProfile = try? ndb.lookup_profile_and_copy(pubkey) {
|
||||||
continuation.yield(existingProfile)
|
continuation.yield(existingProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ extension NostrNetworkManager {
|
|||||||
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
||||||
if yieldCached {
|
if yieldCached {
|
||||||
for pubkey in pubkeys {
|
for pubkey in pubkeys {
|
||||||
if let existingProfile = ndb.lookup_profile_and_copy(pubkey) {
|
if let existingProfile = try? ndb.lookup_profile_and_copy(pubkey) {
|
||||||
continuation.yield(existingProfile)
|
continuation.yield(existingProfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ extension NostrNetworkManager {
|
|||||||
let filter = NostrFilter(ids: [noteId], limit: 1)
|
let filter = NostrFilter(ids: [noteId], limit: 1)
|
||||||
|
|
||||||
// Since note ids point to immutable objects, we can do a simple ndb lookup first
|
// Since note ids point to immutable objects, we can do a simple ndb lookup first
|
||||||
if let noteKey = self.ndb.lookup_note_key(noteId) {
|
if let noteKey = try? self.ndb.lookup_note_key(noteId) {
|
||||||
return NdbNoteLender(ndb: self.ndb, noteKey: noteKey)
|
return NdbNoteLender(ndb: self.ndb, noteKey: noteKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,18 +413,18 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .profile(let pubkey):
|
case .profile(let pubkey):
|
||||||
let profileNotNil = self.ndb.lookup_profile(pubkey, borrow: { pr in
|
let profileNotNil = try? self.ndb.lookup_profile(pubkey, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.profile != nil
|
case .some(let pr): return pr.profile != nil
|
||||||
case .none: return true
|
case .none: return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if profileNotNil {
|
if profileNotNil ?? false {
|
||||||
return .profile(pubkey)
|
return .profile(pubkey)
|
||||||
}
|
}
|
||||||
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
||||||
case .event(let evid):
|
case .event(let evid):
|
||||||
if let event = self.ndb.lookup_note_and_copy(evid) {
|
if let event = try? self.ndb.lookup_note_and_copy(evid) {
|
||||||
return .event(event)
|
return .event(event)
|
||||||
}
|
}
|
||||||
filter = NostrFilter(ids: [evid], limit: 1)
|
filter = NostrFilter(ids: [evid], limit: 1)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ extension NostrNetworkManager {
|
|||||||
private func getLatestNIP65RelayListEvent() -> NdbNote? {
|
private func getLatestNIP65RelayListEvent() -> NdbNote? {
|
||||||
guard let latestRelayListEventId = delegate.latestRelayListEventIdHex else { return nil }
|
guard let latestRelayListEventId = delegate.latestRelayListEventIdHex else { return nil }
|
||||||
guard let latestRelayListEventId = NoteId(hex: latestRelayListEventId) else { return nil }
|
guard let latestRelayListEventId = NoteId(hex: latestRelayListEventId) else { return nil }
|
||||||
return delegate.ndb.lookup_note_and_copy(latestRelayListEventId)
|
return try? delegate.ndb.lookup_note_and_copy(latestRelayListEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the latest `kind:3` relay list from NostrDB.
|
/// Gets the latest `kind:3` relay list from NostrDB.
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ class Profiles {
|
|||||||
profile_data(pubkey).zapper
|
profile_data(pubkey).zapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_with_timestamp<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
func lookup_with_timestamp<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||||
return try ndb.lookup_profile(pubkey, borrow: lendingFunction)
|
return try ndb.lookup_profile(pubkey, borrow: lendingFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_lnurl(_ pubkey: Pubkey) -> String? {
|
func lookup_lnurl(_ pubkey: Pubkey) throws -> String? {
|
||||||
return lookup_with_timestamp(pubkey, borrow: { pr in
|
return try lookup_with_timestamp(pubkey, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.lnurl
|
case .some(let pr): return pr.lnurl
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
@@ -87,16 +87,16 @@ class Profiles {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
func lookup_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||||
return try ndb.lookup_profile_by_key(key: key, borrow: lendingFunction)
|
return try ndb.lookup_profile_by_key(key: key, borrow: lendingFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func search(_ query: String, limit: Int) -> [Pubkey] {
|
func search(_ query: String, limit: Int) throws -> [Pubkey] {
|
||||||
ndb.search_profile(query, limit: limit)
|
try ndb.search_profile(query, limit: limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(id: Pubkey) -> Profile? {
|
func lookup(id: Pubkey) throws -> Profile? {
|
||||||
return ndb.lookup_profile(id, borrow: { pr in
|
return try ndb.lookup_profile(id, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .none:
|
case .none:
|
||||||
return nil
|
return nil
|
||||||
@@ -107,12 +107,12 @@ class Profiles {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_key_by_pubkey(_ pubkey: Pubkey) -> ProfileKey? {
|
func lookup_key_by_pubkey(_ pubkey: Pubkey) throws -> ProfileKey? {
|
||||||
ndb.lookup_profile_key(pubkey)
|
try ndb.lookup_profile_key(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func has_fresh_profile(id: Pubkey) -> Bool {
|
func has_fresh_profile(id: Pubkey) throws -> Bool {
|
||||||
guard let fetched_at = ndb.read_profile_last_fetched(pubkey: id)
|
guard let fetched_at = try ndb.read_profile_last_fetched(pubkey: id)
|
||||||
else {
|
else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ struct EventActionBar: View {
|
|||||||
// Fetching an LNURL is expensive enough that it can cause a hitch. Use a special backgroundable function to fetch the value.
|
// Fetching an LNURL is expensive enough that it can cause a hitch. Use a special backgroundable function to fetch the value.
|
||||||
// Fetch on `.onAppear`
|
// Fetch on `.onAppear`
|
||||||
nonisolated func fetchLNURL() {
|
nonisolated func fetchLNURL() {
|
||||||
let lnurl = damus_state.profiles.lookup_lnurl(event.pubkey)
|
let lnurl = try? damus_state.profiles.lookup_lnurl(event.pubkey)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.lnurl = lnurl
|
self.lnurl = lnurl
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ struct ChatEventView: View {
|
|||||||
// MARK: Zapping properties
|
// MARK: Zapping properties
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
damus_state.profiles.lookup_lnurl(event.pubkey)
|
try? damus_state.profiles.lookup_lnurl(event.pubkey)
|
||||||
}
|
}
|
||||||
var zap_target: ZapTarget {
|
var zap_target: ZapTarget {
|
||||||
ZapTarget.note(id: event.id, author: event.pubkey)
|
ZapTarget.note(id: event.id, author: event.pubkey)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func reply_desc(ndb: Ndb, event: NostrEvent, replying_to: NostrEvent?, locale: L
|
|||||||
}
|
}
|
||||||
|
|
||||||
let names: [String] = pubkeys.map { pk in
|
let names: [String] = pubkeys.map { pk in
|
||||||
let profile = ndb.lookup_profile_and_copy(pk)
|
let profile = try? ndb.lookup_profile_and_copy(pk)
|
||||||
|
|
||||||
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDisplayName(pk: Pubkey, profiles: Profiles) -> String {
|
func getDisplayName(pk: Pubkey, profiles: Profiles) -> String {
|
||||||
let profile = profiles.lookup(id: pk)
|
let profile = try? profiles.lookup(id: pk)
|
||||||
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ struct FollowPackPreviewBody: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
}
|
}
|
||||||
let profile = state.profiles.lookup(id: event.event.pubkey)
|
let profile = try? state.profiles.lookup(id: event.event.pubkey)
|
||||||
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
switch displayName {
|
switch displayName {
|
||||||
case .one(let one):
|
case .one(let one):
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ struct FollowPackView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
}
|
}
|
||||||
let profile = state.profiles.lookup(id: event.event.pubkey)
|
let profile = try? state.profiles.lookup(id: event.event.pubkey)
|
||||||
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
switch displayName {
|
switch displayName {
|
||||||
case .one(let one):
|
case .one(let one):
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class FollowingModel {
|
|||||||
var f = NostrFilter(kinds: [.metadata])
|
var f = NostrFilter(kinds: [.metadata])
|
||||||
f.authors = self.contacts.reduce(into: Array<Pubkey>()) { acc, pk in
|
f.authors = self.contacts.reduce(into: Array<Pubkey>()) { acc, pk in
|
||||||
// don't fetch profiles we already have
|
// don't fetch profiles we already have
|
||||||
if damus_state.profiles.has_fresh_profile(id: pk) {
|
if (try? damus_state.profiles.has_fresh_profile(id: pk)) ?? false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
acc.append(pk)
|
acc.append(pk)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ struct HighlightEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let names: [String] = pubkeys.map { pk in
|
let names: [String] = pubkeys.map { pk in
|
||||||
let profile = ndb.lookup_profile_and_copy(pk)
|
let profile = try? ndb.lookup_profile_and_copy(pk)
|
||||||
|
|
||||||
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ struct HighlightEventRef: View {
|
|||||||
.font(.system(size: 14, weight: .bold))
|
.font(.system(size: 14, weight: .bold))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: longform_event.event.pubkey)
|
let profile = try? damus_state.profiles.lookup(id: longform_event.event.pubkey)
|
||||||
|
|
||||||
if let display_name = profile?.display_name {
|
if let display_name = profile?.display_name {
|
||||||
Text(display_name)
|
Text(display_name)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct LiveStreamProfile: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||||
}
|
}
|
||||||
let profile = state.profiles.lookup(id: pubkey)
|
let profile = try? state.profiles.lookup(id: pubkey)
|
||||||
let displayName = Profile.displayName(profile: profile, pubkey: pubkey)
|
let displayName = Profile.displayName(profile: profile, pubkey: pubkey)
|
||||||
switch displayName {
|
switch displayName {
|
||||||
case .one(let one):
|
case .one(let one):
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
|
|
||||||
var authors = Set<Pubkey>()
|
var authors = Set<Pubkey>()
|
||||||
for pubkey in state.contacts.get_friend_of_friends_list() {
|
for pubkey in state.contacts.get_friend_of_friends_list() {
|
||||||
guard let profile = state.profiles.lookup(id: pubkey),
|
guard let profile = try? state.profiles.lookup(id: pubkey),
|
||||||
let nip05_str = profile.nip05,
|
let nip05_str = profile.nip05,
|
||||||
let nip05 = NIP05.parse(nip05_str),
|
let nip05 = NIP05.parse(nip05_str),
|
||||||
nip05.host.caseInsensitiveCompare(domain) == .orderedSame else {
|
nip05.host.caseInsensitiveCompare(domain) == .orderedSame else {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ struct NIP05DomainTimelineHeaderView: View {
|
|||||||
func friendsOfFriendsString(_ friendsOfFriends: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String {
|
func friendsOfFriendsString(_ friendsOfFriends: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String {
|
||||||
let bundle = bundleForLocale(locale: locale)
|
let bundle = bundleForLocale(locale: locale)
|
||||||
let names: [String] = friendsOfFriends.prefix(3).map { pk in
|
let names: [String] = friendsOfFriends.prefix(3).map { pk in
|
||||||
let profile = ndb.lookup_profile(pk, borrow: { pr in
|
let profile = try? ndb.lookup_profile(pk, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.profile
|
case .some(let pr): return pr.profile
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ func generate_text_mention_notification(ndb: Ndb, from ev: NostrEvent, state: He
|
|||||||
return notification
|
return notification
|
||||||
}
|
}
|
||||||
|
|
||||||
if ev.referenced_ids.contains(where: { note_id in
|
if let contains = try? ev.referenced_ids.contains(where: { note_id in
|
||||||
guard let note_author: Pubkey = state.ndb.lookup_note(note_id, borrow: { note in
|
guard let note_author: Pubkey = try state.ndb.lookup_note(note_id, borrow: { note in
|
||||||
switch note {
|
switch note {
|
||||||
case .some(let note): return note.pubkey
|
case .some(let note): return note.pubkey
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
@@ -92,7 +92,7 @@ func generate_text_mention_notification(ndb: Ndb, from ev: NostrEvent, state: He
|
|||||||
}) else { return false }
|
}) else { return false }
|
||||||
guard note_author == state.keypair.pubkey else { return false }
|
guard note_author == state.keypair.pubkey else { return false }
|
||||||
return true
|
return true
|
||||||
}) {
|
}), contains {
|
||||||
// This is a reply to one of our posts
|
// This is a reply to one of our posts
|
||||||
let content_preview = render_notification_content_preview(ndb: state.ndb, ev: ev, profiles: state.profiles, keypair: state.keypair)
|
let content_preview = render_notification_content_preview(ndb: state.ndb, ev: ev, profiles: state.profiles, keypair: state.keypair)
|
||||||
return LocalNotification(type: .reply, event: ev, target: .note(ev), content: content_preview)
|
return LocalNotification(type: .reply, event: ev, target: .note(ev), content: content_preview)
|
||||||
@@ -126,7 +126,7 @@ func generate_local_notification_object(ndb: Ndb, from ev: NostrEvent, state: He
|
|||||||
let content_preview = render_notification_content_preview(ndb: ndb, ev: inner_ev, profiles: state.profiles, keypair: state.keypair)
|
let content_preview = render_notification_content_preview(ndb: ndb, ev: inner_ev, profiles: state.profiles, keypair: state.keypair)
|
||||||
return LocalNotification(type: .repost, event: ev, target: .note(inner_ev), content: content_preview)
|
return LocalNotification(type: .repost, event: ev, target: .note(inner_ev), content: content_preview)
|
||||||
} else if type == .like, state.settings.like_notification, let evid = ev.referenced_ids.last {
|
} else if type == .like, state.settings.like_notification, let evid = ev.referenced_ids.last {
|
||||||
return state.ndb.lookup_note(evid, borrow: { liked_event in
|
return try? state.ndb.lookup_note(evid, borrow: { liked_event in
|
||||||
switch liked_event {
|
switch liked_event {
|
||||||
case .none:
|
case .none:
|
||||||
return LocalNotification(type: .like, event: ev, target: .note_id(evid), content: "")
|
return LocalNotification(type: .like, event: ev, target: .note_id(evid), content: "")
|
||||||
@@ -191,7 +191,7 @@ func render_notification_content_preview(ndb: Ndb, ev: NostrEvent, profiles: Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String {
|
func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String {
|
||||||
let profile = profiles.lookup(id: pubkey)
|
let profile = try? profiles.lookup(id: pubkey)
|
||||||
return Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ func process_zap_event(state: HeadlessDamusState, ev: NostrEvent, completion: @e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let lnurl = state.profiles.lookup_with_timestamp(ptag, borrow: { record -> String? in
|
guard let lnurl = try? state.profiles.lookup_with_timestamp(ptag, borrow: { record -> String? in
|
||||||
switch record {
|
switch record {
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
case .some(let record): return record.lnurl
|
case .some(let record): return record.lnurl
|
||||||
@@ -280,7 +280,7 @@ 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
|
// we can't trust the p tag on note zaps because they can be faked
|
||||||
return ndb.lookup_note(etag, borrow: { note in
|
return try? ndb.lookup_note(etag, borrow: { note in
|
||||||
switch note {
|
switch note {
|
||||||
case .none:
|
case .none:
|
||||||
// We don't have the event in cache so we can't check the pubkey.
|
// We don't have the event in cache so we can't check the pubkey.
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
|
|
||||||
/// Gets suggested user information from a provided pubkey
|
/// Gets suggested user information from a provided pubkey
|
||||||
func suggestedUser(pubkey: Pubkey) -> SuggestedUser? {
|
func suggestedUser(pubkey: Pubkey) -> SuggestedUser? {
|
||||||
if let profile = damus_state.profiles.lookup(id: pubkey),
|
if let profile = try? damus_state.profiles.lookup(id: pubkey),
|
||||||
let user = SuggestedUser(name: profile.name, about: profile.about, picture: profile.picture, pubkey: pubkey) {
|
let user = SuggestedUser(name: profile.name, about: profile.about, picture: profile.picture, pubkey: pubkey) {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class DraftArtifacts: Equatable {
|
|||||||
case .mention(let mention):
|
case .mention(let mention):
|
||||||
if let pubkey = mention.ref.nip19.pubkey() {
|
if let pubkey = mention.ref.nip19.pubkey() {
|
||||||
// A profile reference, format things properly.
|
// A profile reference, format things properly.
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
let profile_name = DisplayName(profile: profile, pubkey: pubkey).username
|
let profile_name = DisplayName(profile: profile, pubkey: pubkey).username
|
||||||
guard let url_address = URL(string: block.asString) else {
|
guard let url_address = URL(string: block.asString) else {
|
||||||
rich_text_content.append(.init(string: block.asString))
|
rich_text_content.append(.init(string: block.asString))
|
||||||
@@ -173,7 +173,7 @@ class Drafts: ObservableObject {
|
|||||||
func load(from damus_state: DamusState) {
|
func load(from damus_state: DamusState) {
|
||||||
guard let note_ids = damus_state.settings.draft_event_ids?.compactMap({ NoteId(hex: $0) }) else { return }
|
guard let note_ids = damus_state.settings.draft_event_ids?.compactMap({ NoteId(hex: $0) }) else { return }
|
||||||
for note_id in note_ids {
|
for note_id in note_ids {
|
||||||
let note = damus_state.ndb.lookup_note(note_id, borrow: { event in
|
let note = try? damus_state.ndb.lookup_note(note_id, borrow: { event in
|
||||||
return event?.toOwned()
|
return event?.toOwned()
|
||||||
})
|
})
|
||||||
guard let note else { continue }
|
guard let note else { continue }
|
||||||
@@ -234,13 +234,13 @@ class Drafts: ObservableObject {
|
|||||||
draft_events.append(wrapped_note)
|
draft_events.append(wrapped_note)
|
||||||
}
|
}
|
||||||
for (replied_to_note_id, reply_artifacts) in self.replies {
|
for (replied_to_note_id, reply_artifacts) in self.replies {
|
||||||
guard let replied_to_note = damus_state.ndb.lookup_note_and_copy(replied_to_note_id) else { continue }
|
guard let replied_to_note = try? damus_state.ndb.lookup_note_and_copy(replied_to_note_id) else { continue }
|
||||||
let nip37_draft = try? await reply_artifacts.to_nip37_draft(action: .replying_to(replied_to_note), damus_state: damus_state)
|
let nip37_draft = try? await reply_artifacts.to_nip37_draft(action: .replying_to(replied_to_note), damus_state: damus_state)
|
||||||
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
||||||
draft_events.append(wrapped_note)
|
draft_events.append(wrapped_note)
|
||||||
}
|
}
|
||||||
for (quoted_note_id, quote_note_artifacts) in self.quotes {
|
for (quoted_note_id, quote_note_artifacts) in self.quotes {
|
||||||
guard let quoted_note = damus_state.ndb.lookup_note_and_copy(quoted_note_id) else { continue }
|
guard let quoted_note = try? damus_state.ndb.lookup_note_and_copy(quoted_note_id) else { continue }
|
||||||
let nip37_draft = try? await quote_note_artifacts.to_nip37_draft(action: .quoting(quoted_note), damus_state: damus_state)
|
let nip37_draft = try? await quote_note_artifacts.to_nip37_draft(action: .quoting(quoted_note), damus_state: damus_state)
|
||||||
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
||||||
draft_events.append(wrapped_note)
|
draft_events.append(wrapped_note)
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ struct PostView: View {
|
|||||||
return .init(string: "")
|
return .init(string: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
return user_tag_attr_string(profile: profile, pubkey: pubkey)
|
return user_tag_attr_string(profile: profile, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct ReplyView: View {
|
|||||||
let names = references
|
let names = references
|
||||||
.map { pubkey in
|
.map { pubkey in
|
||||||
let pk = pubkey
|
let pk = pubkey
|
||||||
let prof = damus.profiles.lookup(id: pk)
|
let prof = try? damus.profiles.lookup(id: pk)
|
||||||
return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
|
return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
|
||||||
}
|
}
|
||||||
.joined(separator: " ")
|
.joined(separator: " ")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct UserSearch: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func on_user_tapped(pk: Pubkey) {
|
func on_user_tapped(pk: Pubkey) {
|
||||||
let profile = damus_state.profiles.lookup(id: pk)
|
let profile = try? damus_state.profiles.lookup(id: pk)
|
||||||
let user_tag = user_tag_attr_string(profile: profile, pubkey: pk)
|
let user_tag = user_tag_attr_string(profile: profile, pubkey: pk)
|
||||||
|
|
||||||
appendUserTag(withTag: user_tag)
|
appendUserTag(withTag: user_tag)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct EditMetadataView: View {
|
|||||||
|
|
||||||
init(damus_state: DamusState) {
|
init(damus_state: DamusState) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
|
let data = try? damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||||
|
|
||||||
_name = State(initialValue: data?.name ?? "")
|
_name = State(initialValue: data?.name ?? "")
|
||||||
_display_name = State(initialValue: data?.display_name ?? "")
|
_display_name = State(initialValue: data?.display_name ?? "")
|
||||||
@@ -258,7 +258,7 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func didChange() -> Bool {
|
func didChange() -> Bool {
|
||||||
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
|
let data = try? damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||||
|
|
||||||
if data?.name ?? "" != name {
|
if data?.name ?? "" != name {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct EventProfileName: View {
|
|||||||
self.damus_state = damus
|
self.damus_state = damus
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.size = size
|
self.size = size
|
||||||
let donation = damus.profiles.lookup(id: pubkey)?.damus_donation
|
let donation = try? damus.profiles.lookup(id: pubkey)?.damus_donation
|
||||||
self._donation = State(wrappedValue: donation)
|
self._donation = State(wrappedValue: donation)
|
||||||
self.purple_account = nil
|
self.purple_account = nil
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ struct EventProfileName: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
switch current_display_name(profile) {
|
switch current_display_name(profile) {
|
||||||
case .one(let one):
|
case .one(let one):
|
||||||
|
|||||||
@@ -33,16 +33,16 @@ struct ProfileActionSheetView: View {
|
|||||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||||
}
|
}
|
||||||
|
|
||||||
func profile_data<T>(borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
func profile_data<T>(borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||||
return try damus_state.profiles.lookup_with_timestamp(profile.pubkey, borrow: lendingFunction)
|
return try damus_state.profiles.lookup_with_timestamp(profile.pubkey, borrow: lendingFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_profile() -> Profile? {
|
func get_profile() -> Profile? {
|
||||||
return damus_state.profiles.lookup(id: profile.pubkey)
|
return try? damus_state.profiles.lookup(id: profile.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_lnurl() -> String? {
|
func get_lnurl() -> String? {
|
||||||
return damus_state.profiles.lookup_lnurl(profile.pubkey)
|
return try? damus_state.profiles.lookup_lnurl(profile.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigate(route: Route) {
|
func navigate(route: Route) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ struct ProfileName: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Text(verbatim: "\(prefix)\(name_choice(profile: profile))")
|
Text(verbatim: "\(prefix)\(name_choice(profile: profile))")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct ProfileNameView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let profile = self.damus.profiles.lookup(id: pubkey)
|
let profile = try? self.damus.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
switch Profile.displayName(profile: profile, pubkey: pubkey) {
|
switch Profile.displayName(profile: profile, pubkey: pubkey) {
|
||||||
case .one:
|
case .one:
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ struct ProfilePicView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_lnurl() -> String? {
|
func get_lnurl() -> String? {
|
||||||
return profiles.lookup_with_timestamp(pubkey, borrow: { pr in
|
return try? profiles.lookup_with_timestamp(pubkey, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.lnurl
|
case .some(let pr): return pr.lnurl
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
@@ -134,7 +134,7 @@ struct ProfilePicView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> URL {
|
func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> URL {
|
||||||
let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey)
|
let pic = picture ?? (try? profiles.lookup(id: pubkey)?.picture) ?? robohash(pubkey)
|
||||||
if let url = URL(string: pic) {
|
if let url = URL(string: pic) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ struct EditProfilePictureView: View {
|
|||||||
if let profile_url {
|
if let profile_url {
|
||||||
return profile_url
|
return profile_url
|
||||||
} else if let state = damus_state,
|
} else if let state = damus_state,
|
||||||
let picture = state.profiles.lookup(id: pubkey)?.picture {
|
let picture = try? state.profiles.lookup(id: pubkey)?.picture {
|
||||||
return URL(string: picture)
|
return URL(string: picture)
|
||||||
} else {
|
} else {
|
||||||
return profile_url ?? URL(string: robohash(pubkey))
|
return profile_url ?? URL(string: robohash(pubkey))
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func follow_btn_txt(_ fs: FollowState, follows_you: Bool) -> String {
|
|||||||
func followedByString(_ 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 bundle = bundleForLocale(locale: locale)
|
||||||
let names: [String] = friend_intersection.prefix(3).map { pk in
|
let names: [String] = friend_intersection.prefix(3).map { pk in
|
||||||
let profile = ndb.lookup_profile_and_copy(pk)
|
let profile = try? ndb.lookup_profile_and_copy(pk)
|
||||||
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20)
|
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProfileInfo() -> (String, String) {
|
func getProfileInfo() -> (String, String) {
|
||||||
let ndbprofile = self.damus_state.profiles.lookup(id: profile.pubkey)
|
let ndbprofile = try? self.damus_state.profiles.lookup(id: profile.pubkey)
|
||||||
let displayName = Profile.displayName(profile: ndbprofile, pubkey: profile.pubkey).displayName.truncate(maxLength: 25)
|
let displayName = Profile.displayName(profile: ndbprofile, pubkey: profile.pubkey).displayName.truncate(maxLength: 25)
|
||||||
let userName = Profile.displayName(profile: ndbprofile, pubkey: profile.pubkey).username.truncate(maxLength: 25)
|
let userName = Profile.displayName(profile: ndbprofile, pubkey: profile.pubkey).username.truncate(maxLength: 25)
|
||||||
return (displayName, "@\(userName)")
|
return (displayName, "@\(userName)")
|
||||||
@@ -358,8 +358,8 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
var aboutSection: some View {
|
var aboutSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 8.0) {
|
VStack(alignment: .leading, spacing: 8.0) {
|
||||||
let lnurl = damus_state.profiles.lookup_lnurl(profile.pubkey)
|
let lnurl = try? damus_state.profiles.lookup_lnurl(profile.pubkey)
|
||||||
let ndbprofile = damus_state.profiles.lookup(id: profile.pubkey)
|
let ndbprofile = try? damus_state.profiles.lookup(id: profile.pubkey)
|
||||||
|
|
||||||
nameSection(ndbprofile: ndbprofile, lnurl: lnurl)
|
nameSection(ndbprofile: ndbprofile, lnurl: lnurl)
|
||||||
|
|
||||||
@@ -570,7 +570,7 @@ extension View {
|
|||||||
@MainActor
|
@MainActor
|
||||||
func check_nip05_validity(pubkey: Pubkey, damus_state: DamusState) {
|
func check_nip05_validity(pubkey: Pubkey, damus_state: DamusState) {
|
||||||
let profiles = damus_state.profiles
|
let profiles = damus_state.profiles
|
||||||
let profile = profiles.lookup(id: pubkey)
|
let profile = try? profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
guard let nip05 = profile?.nip05,
|
guard let nip05 = profile?.nip05,
|
||||||
profiles.is_validated(pubkey) == nil
|
profiles.is_validated(pubkey) == nil
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ struct DamusPurpleAccountView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func profile_display_name() -> String {
|
func profile_display_name() -> String {
|
||||||
let profile = damus_state.profiles.lookup(id: account.pubkey)
|
let profile = try? damus_state.profiles.lookup(id: account.pubkey)
|
||||||
let display_name = DisplayName(profile: profile, pubkey: account.pubkey).displayName
|
let display_name = DisplayName(profile: profile, pubkey: account.pubkey).displayName
|
||||||
return display_name
|
return display_name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,10 @@ func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [Pubkey]) -> [Pubkey] {
|
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [Pubkey]) -> [Pubkey] {
|
||||||
Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk) }))
|
Array(Set(pks.filter { pk in
|
||||||
|
let has_fresh_profile = (try? profiles.has_fresh_profile(id: pk)) ?? false
|
||||||
|
return !has_fresh_profile
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [Pubkey] {
|
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [Pubkey] {
|
||||||
@@ -128,11 +131,14 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
|
|||||||
|
|
||||||
for ev in events {
|
for ev in events {
|
||||||
// lookup profiles from boosted events
|
// lookup profiles from boosted events
|
||||||
if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey) {
|
if ev.known_kind == .boost,
|
||||||
|
let bev = ev.get_inner_event(cache: cache),
|
||||||
|
let has_fresh_profiles = try? profiles.has_fresh_profile(id: bev.pubkey),
|
||||||
|
!has_fresh_profiles {
|
||||||
pubkeys.insert(bev.pubkey)
|
pubkeys.insert(bev.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !profiles.has_fresh_profile(id: ev.pubkey) {
|
if let has_fresh_profiles = try? profiles.has_fresh_profile(id: ev.pubkey), !has_fresh_profiles {
|
||||||
pubkeys.insert(ev.pubkey)
|
pubkeys.insert(ev.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ struct PullDownSearchView: View {
|
|||||||
|
|
||||||
func do_search(query: String) {
|
func do_search(query: String) {
|
||||||
let limit = 128
|
let limit = 128
|
||||||
let note_keys = state.ndb.text_search(query: query, limit: limit, order: .newest_first)
|
let note_keys = (try? state.ndb.text_search(query: query, limit: limit, order: .newest_first)) ?? []
|
||||||
var res = [NostrEvent]()
|
var res = [NostrEvent]()
|
||||||
// TODO: fix duplicate results from search
|
// TODO: fix duplicate results from search
|
||||||
var keyset = Set<NoteKey>()
|
var keyset = Set<NoteKey>()
|
||||||
@@ -32,7 +32,7 @@ struct PullDownSearchView: View {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
for note_key in note_keys {
|
for note_key in note_keys {
|
||||||
state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
try? state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
||||||
switch maybeUnownedNote {
|
switch maybeUnownedNote {
|
||||||
case .none: return // Skip this
|
case .none: return // Skip this
|
||||||
case .some(let unownedNote):
|
case .some(let unownedNote):
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ struct SearchResultsView: View {
|
|||||||
|
|
||||||
func do_search(query: String) {
|
func do_search(query: String) {
|
||||||
let limit = 128
|
let limit = 128
|
||||||
var note_keys = damus_state.ndb.text_search(query: query, limit: limit, order: .newest_first)
|
var note_keys = (try? damus_state.ndb.text_search(query: query, limit: limit, order: .newest_first)) ?? []
|
||||||
var res = [NostrEvent]()
|
var res = [NostrEvent]()
|
||||||
// TODO: fix duplicate results from search
|
// TODO: fix duplicate results from search
|
||||||
var keyset = Set<NoteKey>()
|
var keyset = Set<NoteKey>()
|
||||||
@@ -155,7 +155,7 @@ struct SearchResultsView: View {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
for note_key in note_keys {
|
for note_key in note_keys {
|
||||||
damus_state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
try? damus_state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
||||||
switch maybeUnownedNote {
|
switch maybeUnownedNote {
|
||||||
case .none: return
|
case .none: return
|
||||||
case .some(let unownedNote):
|
case .some(let unownedNote):
|
||||||
@@ -270,7 +270,7 @@ func make_hashtagable(_ str: String) -> String {
|
|||||||
func search_profiles(profiles: Profiles, contacts: Contacts, search: String) -> [Pubkey] {
|
func search_profiles(profiles: Profiles, contacts: Contacts, search: String) -> [Pubkey] {
|
||||||
// Search by hex pubkey.
|
// Search by hex pubkey.
|
||||||
if let pubkey = hex_decode_pubkey(search),
|
if let pubkey = hex_decode_pubkey(search),
|
||||||
profiles.lookup_key_by_pubkey(pubkey) != nil
|
(try? profiles.lookup_key_by_pubkey(pubkey)) != nil
|
||||||
{
|
{
|
||||||
return [pubkey]
|
return [pubkey]
|
||||||
}
|
}
|
||||||
@@ -279,12 +279,12 @@ func search_profiles(profiles: Profiles, contacts: Contacts, search: String) ->
|
|||||||
if search.starts(with: "npub"),
|
if search.starts(with: "npub"),
|
||||||
let bech32_key = decode_bech32_key(search),
|
let bech32_key = decode_bech32_key(search),
|
||||||
case Bech32Key.pub(let pk) = bech32_key,
|
case Bech32Key.pub(let pk) = bech32_key,
|
||||||
profiles.lookup_key_by_pubkey(pk) != nil
|
(try? profiles.lookup_key_by_pubkey(pk)) != nil
|
||||||
{
|
{
|
||||||
return [pk]
|
return [pk]
|
||||||
}
|
}
|
||||||
|
|
||||||
return profiles.search(search, limit: 128).sorted { a, b in
|
return (try? profiles.search(search, limit: 128).sorted { a, b in
|
||||||
let aFriendTypePriority = get_friend_type(contacts: contacts, pubkey: a)?.priority ?? 0
|
let aFriendTypePriority = get_friend_type(contacts: contacts, pubkey: a)?.priority ?? 0
|
||||||
let bFriendTypePriority = get_friend_type(contacts: contacts, pubkey: b)?.priority ?? 0
|
let bFriendTypePriority = get_friend_type(contacts: contacts, pubkey: b)?.priority ?? 0
|
||||||
|
|
||||||
@@ -294,5 +294,5 @@ func search_profiles(profiles: Profiles, contacts: Contacts, search: String) ->
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}) ?? []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ struct SearchingEventView: View {
|
|||||||
switch search {
|
switch search {
|
||||||
case .nip05(let nip05):
|
case .nip05(let nip05):
|
||||||
if let pk = state.profiles.nip05_pubkey[nip05] {
|
if let pk = state.profiles.nip05_pubkey[nip05] {
|
||||||
if state.profiles.lookup_key_by_pubkey(pk) != nil {
|
if (try? state.profiles.lookup_key_by_pubkey(pk)) != nil {
|
||||||
self.search_state = .found_profile(pk)
|
self.search_state = .found_profile(pk)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
damus_state.contacts.delegate = self
|
damus_state.contacts.delegate = self
|
||||||
guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return }
|
guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return }
|
||||||
guard let latest_contact_event_id = NoteId(hex: latest_contact_event_id_hex) else { return }
|
guard let latest_contact_event_id = NoteId(hex: latest_contact_event_id_hex) else { return }
|
||||||
guard let latest_contact_event: NdbNote = damus_state.ndb.lookup_note_and_copy(latest_contact_event_id) else { return }
|
guard let latest_contact_event: NdbNote = try? damus_state.ndb.lookup_note_and_copy(latest_contact_event_id) else { return }
|
||||||
process_contact_event(state: damus_state, ev: latest_contact_event)
|
process_contact_event(state: damus_state, ev: latest_contact_event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
|
|
||||||
var candidates: [NostrEvent] = []
|
var candidates: [NostrEvent] = []
|
||||||
for key in note_keys {
|
for key in note_keys {
|
||||||
guard let note = damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue }
|
guard let note = try? damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue }
|
||||||
candidates.append(note)
|
candidates.append(note)
|
||||||
}
|
}
|
||||||
return candidates.max(by: { $0.created_at < $1.created_at })
|
return candidates.max(by: { $0.created_at < $1.created_at })
|
||||||
@@ -166,7 +166,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
|
|
||||||
var candidates: [NostrEvent] = []
|
var candidates: [NostrEvent] = []
|
||||||
for key in note_keys {
|
for key in note_keys {
|
||||||
guard let note = damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue }
|
guard let note = try? damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue }
|
||||||
if note.referenced_params.contains(where: { $0.param.matches_str("mute") }) {
|
if note.referenced_params.contains(where: { $0.param.matches_str("mute") }) {
|
||||||
candidates.append(note)
|
candidates.append(note)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ struct SideMenuView: View {
|
|||||||
var display_name: String? = nil
|
var display_name: String? = nil
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
let profile = try? damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||||
name = profile?.name
|
name = profile?.name
|
||||||
display_name = profile?.display_name
|
display_name = profile?.display_name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ struct NWCSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: model.settings.donation_percent) { p in
|
.onChange(of: model.settings.donation_percent) { p in
|
||||||
guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else {
|
guard let profile = try? damus_state.profiles.lookup(id: damus_state.pubkey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ struct NWCSettings: View {
|
|||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
|
||||||
guard let keypair = damus_state.keypair.to_full(),
|
guard let keypair = damus_state.keypair.to_full(),
|
||||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey),
|
let profile = try? damus_state.profiles.lookup(id: damus_state.pubkey),
|
||||||
model.initial_percent != profile.damus_donation
|
model.initial_percent != profile.damus_donation
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ struct TransactionView: View {
|
|||||||
return NSLocalizedString("Unknown", comment: "A name label for an unknown user")
|
return NSLocalizedString("Unknown", comment: "A name label for an unknown user")
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
return Profile.displayName(profile: profile, pubkey: pubkey).displayName
|
return Profile.displayName(profile: profile, pubkey: pubkey).displayName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ struct ProfileZapLinkView<Content: View>: View {
|
|||||||
self.label = label
|
self.label = label
|
||||||
self.action = action
|
self.action = action
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
let lnurl = damus_state.profiles.lookup_with_timestamp(pubkey, borrow: { pr -> String? in
|
let lnurl = try? damus_state.profiles.lookup_with_timestamp(pubkey, borrow: { pr -> String? in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.lnurl
|
case .some(let pr): return pr.lnurl
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func zap_type_desc(type: ZapType, profiles: Profiles, pubkey: Pubkey) -> String
|
|||||||
case .anon:
|
case .anon:
|
||||||
return NSLocalizedString("No one will see that you zapped", comment: "Description of anonymous zap type where the zap is sent anonymously and does not identify the user who sent it.")
|
return NSLocalizedString("No one will see that you zapped", comment: "Description of anonymous zap type where the zap is sent anonymously and does not identify the user who sent it.")
|
||||||
case .priv:
|
case .priv:
|
||||||
let prof = profiles.lookup(id: pubkey)
|
let prof = try? profiles.lookup(id: pubkey)
|
||||||
let name = Profile.displayName(profile: prof, pubkey: pubkey).username.truncate(maxLength: 50)
|
let name = Profile.displayName(profile: prof, pubkey: pubkey).username.truncate(maxLength: 50)
|
||||||
return String.localizedStringWithFormat(NSLocalizedString("private_zap_description", value: "Only '%@' will see that you zapped them", comment: "Description of private zap type where the zap is sent privately and does not identify the user to the public."), name)
|
return String.localizedStringWithFormat(NSLocalizedString("private_zap_description", value: "Only '%@' will see that you zapped them", comment: "Description of private zap type where the zap is sent privately and does not identify the user to the public."), name)
|
||||||
case .non_zap:
|
case .non_zap:
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ struct NIP05Badge: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var username_matches_nip05: Bool {
|
var username_matches_nip05: Bool {
|
||||||
guard let name = damus_state.profiles.lookup(id: pubkey)?.name
|
guard let name = try? damus_state.profiles.lookup(id: pubkey)?.name
|
||||||
else {
|
else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ struct QRCodeView: View {
|
|||||||
|
|
||||||
var QRView: some View {
|
var QRView: some View {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = try? damus_state.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ struct BannerImageView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_banner_url(banner: String?, pubkey: Pubkey, profiles: Profiles) -> URL? {
|
func get_banner_url(banner: String?, pubkey: Pubkey, profiles: Profiles) -> URL? {
|
||||||
let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.banner ?? ""
|
let bannerUrlString = banner ?? (try? profiles.lookup(id: pubkey)?.banner) ?? ""
|
||||||
if let url = URL(string: bannerUrlString) {
|
if let url = URL(string: bannerUrlString) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ class EventCache {
|
|||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ev = self.ndb.lookup_note_and_copy(evid) {
|
if let ev = try? self.ndb.lookup_note_and_copy(evid) {
|
||||||
events[ev.id] = ev
|
events[ev.id] = ev
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Foundation
|
|||||||
import EmojiPicker
|
import EmojiPicker
|
||||||
|
|
||||||
// Generates a test damus state with configurable mock parameters
|
// Generates a test damus state with configurable mock parameters
|
||||||
|
@MainActor
|
||||||
func generate_test_damus_state(
|
func generate_test_damus_state(
|
||||||
mock_profile_info: [Pubkey: Profile]?,
|
mock_profile_info: [Pubkey: Profile]?,
|
||||||
home: HomeModel? = nil,
|
home: HomeModel? = nil,
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// AppLifecycleHandlingTests.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-11-06.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import damus
|
||||||
|
|
||||||
|
|
||||||
|
class AppLifecycleHandlingTests: XCTestCase {
|
||||||
|
|
||||||
|
func getTestNotesJSONL() -> String {
|
||||||
|
// Get the path for the test_notes.jsonl file in the same folder as this test file
|
||||||
|
let testBundle = Bundle(for: type(of: self))
|
||||||
|
let fileURL = testBundle.url(forResource: "test_notes", withExtension: "jsonl")!
|
||||||
|
|
||||||
|
// Load the contents of the file
|
||||||
|
return try! String(contentsOf: fileURL, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests for some race conditions between the app closing down and streams opening throughout the app
|
||||||
|
/// See https://github.com/damus-io/damus/issues/3245 for more context.
|
||||||
|
///
|
||||||
|
/// **Note:** Time delays are intentionally added because we actually want to provoke possible race conditions,
|
||||||
|
/// so using proper waiting mechanisms would defeat the purpose of the test.
|
||||||
|
func testAppLifecycleRaceConditions() async throws {
|
||||||
|
let damusState = await generate_test_damus_state(mock_profile_info: nil)
|
||||||
|
|
||||||
|
let notesJSONL = getTestNotesJSONL()
|
||||||
|
for noteText in notesJSONL.split(separator: "\n") {
|
||||||
|
let _ = damusState.ndb.processEvent("[\"EVENT\",\"subid\",\(String(noteText))]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give some time ndb some time to fill up
|
||||||
|
try? await Task.sleep(for: .milliseconds(2000))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Start measuring the time elapsed for debugging
|
||||||
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
func getElapsedTimeMiliseconds() -> String {
|
||||||
|
return "\((CFAbsoluteTimeGetCurrent() - startTime) * 1000) ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Task.detached {
|
||||||
|
for i in 0...10000 {
|
||||||
|
try await Task.sleep(for: .milliseconds(Int.random(in: 0...10)))
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): About to close Ndb. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
damusState.ndb.close()
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Closed Ndb. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Reopening Ndb. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
_ = damusState.ndb.reopen()
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Reopened Ndb. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0...10000 {
|
||||||
|
do {
|
||||||
|
try await Task.sleep(for: .milliseconds(Int.random(in: 0...10)))
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Starting new query. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
_ = try damusState.ndb.query(filters: [try NdbFilter(from: NostrFilter(kinds: [.text], limit: 1000))], maxResults: 500)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Query error: \(error). Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
}
|
||||||
|
print("APP_LIFECYCLE_TEST \(i): Finished query. Elapsed time: \(getElapsedTimeMiliseconds())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import XCTest
|
|||||||
@testable import damus
|
@testable import damus
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
class NostrNetworkManagerTests: XCTestCase {
|
class NostrNetworkManagerTests: XCTestCase {
|
||||||
var damusState: DamusState? = nil
|
var damusState: DamusState? = nil
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ class NostrNetworkManagerTests: XCTestCase {
|
|||||||
switch item {
|
switch item {
|
||||||
case .event(let noteKey):
|
case .event(let noteKey):
|
||||||
// Lookup the note to verify it exists
|
// Lookup the note to verify it exists
|
||||||
if let note = ndb.lookup_note_by_key_and_copy(noteKey) {
|
if let note = try? ndb.lookup_note_by_key_and_copy(noteKey) {
|
||||||
count += 1
|
count += 1
|
||||||
receivedIds.insert(note.id)
|
receivedIds.insert(note.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ProfilesManagerTests: XCTestCase {
|
|||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
// Verify profile is in NDB
|
// Verify profile is in NDB
|
||||||
let cachedProfile = ndb.lookup_profile_and_copy(profilePubkey)
|
let cachedProfile = try? ndb.lookup_profile_and_copy(profilePubkey)
|
||||||
XCTAssertNotNil(cachedProfile, "Profile should be cached in NDB")
|
XCTAssertNotNil(cachedProfile, "Profile should be cached in NDB")
|
||||||
XCTAssertEqual(cachedProfile?.name, "testuser")
|
XCTAssertEqual(cachedProfile?.name, "testuser")
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ class ProfilesManagerTests: XCTestCase {
|
|||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
// Verify profile is in NDB
|
// Verify profile is in NDB
|
||||||
let cachedProfile = ndb.lookup_profile_and_copy(profilePubkey)
|
let cachedProfile = try? ndb.lookup_profile_and_copy(profilePubkey)
|
||||||
XCTAssertNotNil(cachedProfile, "Profile should be cached in NDB")
|
XCTAssertNotNil(cachedProfile, "Profile should be cached in NDB")
|
||||||
|
|
||||||
// Create ProfilesManager
|
// Create ProfilesManager
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import damus
|
@testable import damus
|
||||||
|
|
||||||
|
@MainActor
|
||||||
final class ThreadModelTests: XCTestCase {
|
final class ThreadModelTests: XCTestCase {
|
||||||
var damusState: DamusState? = nil
|
var damusState: DamusState? = nil
|
||||||
|
|
||||||
@@ -39,8 +40,12 @@ final class ThreadModelTests: XCTestCase {
|
|||||||
|
|
||||||
/// Tests loading up a thread and checking if the repost count loads as expected.
|
/// Tests loading up a thread and checking if the repost count loads as expected.
|
||||||
func testActionBarModel() async throws {
|
func testActionBarModel() async throws {
|
||||||
try! await damusState?.nostrNetwork.userRelayList.set(userRelayList: NIP65.RelayList())
|
guard let damusState else {
|
||||||
await damusState?.nostrNetwork.connect()
|
XCTFail("DamusState is nil, test is misconfigured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try! await damusState.nostrNetwork.userRelayList.set(userRelayList: NIP65.RelayList())
|
||||||
|
await damusState.nostrNetwork.connect()
|
||||||
|
|
||||||
let testNoteJson = """
|
let testNoteJson = """
|
||||||
{"content":"https://smartflowsocial.s3.us-east-1.amazonaws.com/clients/cm7kdrwdk0000qyu6fwtd96ui/0cab65a9-0142-48e3-abd7-94d20e30d3b2.jpg\n\n","pubkey":"71ecabd8b6b33548e075ff01b31568ffda19d0ac2788067d99328c6de4885975","tags":[["t","meme"],["t","memes"],["t","memestr"],["t","plebchain"]],"created_at":1755694800,"id":"64b26d0a587f5f894470e1e4783756b4d8ba971226de975ee30ac1b69970d5a1","kind":1,"sig":"c000794da8c4f7549b546630b16ed17f6edc0af0269b8c46ce14f5b1937431e7575b78351bc152007ebab5720028e5fe4b738f99e8887f273d35dd2217d1cc3d"}
|
{"content":"https://smartflowsocial.s3.us-east-1.amazonaws.com/clients/cm7kdrwdk0000qyu6fwtd96ui/0cab65a9-0142-48e3-abd7-94d20e30d3b2.jpg\n\n","pubkey":"71ecabd8b6b33548e075ff01b31568ffda19d0ac2788067d99328c6de4885975","tags":[["t","meme"],["t","memes"],["t","memestr"],["t","plebchain"]],"created_at":1755694800,"id":"64b26d0a587f5f894470e1e4783756b4d8ba971226de975ee30ac1b69970d5a1","kind":1,"sig":"c000794da8c4f7549b546630b16ed17f6edc0af0269b8c46ce14f5b1937431e7575b78351bc152007ebab5720028e5fe4b738f99e8887f273d35dd2217d1cc3d"}
|
||||||
@@ -48,12 +53,12 @@ final class ThreadModelTests: XCTestCase {
|
|||||||
let testShouldComplete = XCTestExpectation(description: "Test should complete")
|
let testShouldComplete = XCTestExpectation(description: "Test should complete")
|
||||||
Task {
|
Task {
|
||||||
let note = NostrEvent.owned_from_json(json: testNoteJson)!
|
let note = NostrEvent.owned_from_json(json: testNoteJson)!
|
||||||
let threadModel = await ThreadModel(event: note, damus_state: damusState!)
|
let threadModel = ThreadModel(event: note, damus_state: damusState)
|
||||||
await threadModel.subscribe()
|
threadModel.subscribe()
|
||||||
let actionBarModel = make_actionbar_model(ev: note.id, damus: damusState!)
|
let actionBarModel = make_actionbar_model(ev: note.id, damus: damusState)
|
||||||
while true {
|
while true {
|
||||||
try await Task.sleep(nanoseconds: 500_000_000)
|
try await Task.sleep(nanoseconds: 500_000_000)
|
||||||
await actionBarModel.update(damus: damusState!, evid: note.id)
|
await actionBarModel.update(damus: damusState, evid: note.id)
|
||||||
if actionBarModel.boosts >= 5 {
|
if actionBarModel.boosts >= 5 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+332
-270
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import Synchronization
|
||||||
|
|
||||||
fileprivate let APPLICATION_GROUP_IDENTIFIER = "group.com.damus"
|
fileprivate let APPLICATION_GROUP_IDENTIFIER = "group.com.damus"
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ class Ndb {
|
|||||||
var generation: Int
|
var generation: Int
|
||||||
private var closed: Bool
|
private var closed: Bool
|
||||||
private var callbackHandler: Ndb.CallbackHandler
|
private var callbackHandler: Ndb.CallbackHandler
|
||||||
|
private let ndbAccessLock: Ndb.UseLockProtocol = initLock()
|
||||||
|
|
||||||
private static let DEFAULT_WRITER_SCRATCH_SIZE: Int32 = 2097152; // 2mb scratch size for the writer thread, it should match with the one specified in nostrdb.c
|
private static let DEFAULT_WRITER_SCRATCH_SIZE: Int32 = 2097152; // 2mb scratch size for the writer thread, it should match with the one specified in nostrdb.c
|
||||||
|
|
||||||
@@ -158,6 +160,7 @@ class Ndb {
|
|||||||
self.ndb = db
|
self.ndb = db
|
||||||
self.closed = false
|
self.closed = false
|
||||||
self.callbackHandler = callbackHandler
|
self.callbackHandler = callbackHandler
|
||||||
|
self.ndbAccessLock.markNdbOpen()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func migrate_db_location_if_needed() throws {
|
private static func migrate_db_location_if_needed() throws {
|
||||||
@@ -206,7 +209,7 @@ class Ndb {
|
|||||||
// This simple initialization will cause subscriptions not to be ever called. Probably fine because this initializer is used only for empty example ndb instances.
|
// This simple initialization will cause subscriptions not to be ever called. Probably fine because this initializer is used only for empty example ndb instances.
|
||||||
self.callbackHandler = Ndb.CallbackHandler()
|
self.callbackHandler = Ndb.CallbackHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark NostrDB as "closed" without actually closing it.
|
/// Mark NostrDB as "closed" without actually closing it.
|
||||||
/// Useful when shutting down tasks that use NostrDB while avoiding new tasks from using it.
|
/// Useful when shutting down tasks that use NostrDB while avoiding new tasks from using it.
|
||||||
func markClosed() {
|
func markClosed() {
|
||||||
@@ -216,10 +219,13 @@ class Ndb {
|
|||||||
func close() {
|
func close() {
|
||||||
guard !self.is_closed else { return }
|
guard !self.is_closed else { return }
|
||||||
self.closed = true
|
self.closed = true
|
||||||
print("txn: CLOSING NOSTRDB")
|
try! self.ndbAccessLock.waitUntilNdbCanClose(thenClose: {
|
||||||
ndb_destroy(self.ndb.ndb)
|
print("txn: CLOSING NOSTRDB")
|
||||||
self.generation += 1
|
ndb_destroy(self.ndb.ndb)
|
||||||
print("txn: NOSTRDB CLOSED")
|
self.generation += 1
|
||||||
|
print("txn: NOSTRDB CLOSED")
|
||||||
|
return false
|
||||||
|
}, maxTimeout: .milliseconds(2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
func reopen() -> Bool {
|
func reopen() -> Bool {
|
||||||
@@ -229,12 +235,29 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("txn: NOSTRDB REOPENED (gen \(generation))")
|
print("txn: NOSTRDB REOPENED (gen \(generation))")
|
||||||
|
|
||||||
|
self.ndb = db // Set the new DB before marking it as open to prevent access to the old DB
|
||||||
self.closed = false
|
self.closed = false
|
||||||
self.ndb = db
|
self.ndbAccessLock.markNdbOpen()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Thread safety mechanisms
|
||||||
|
// Use these for all externally accessible methods that interact with the nostrdb database to prevent race conditions with app lifecycle events (i.e. NostrDB opening and closing)
|
||||||
|
|
||||||
|
internal func withNdb<T>(_ useFunction: () throws -> T, maxWaitTimeout: DispatchTimeInterval = .milliseconds(500)) throws -> T {
|
||||||
|
guard !self.is_closed else { throw NdbStreamError.ndbClosed }
|
||||||
|
return try self.ndbAccessLock.keepNdbOpen(during: {
|
||||||
|
// Double-check things to avoid TOCTOU race conditions
|
||||||
|
guard !self.is_closed else { throw NdbStreamError.ndbClosed }
|
||||||
|
return try useFunction()
|
||||||
|
}, maxWaitTimeout: maxWaitTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Lookup and query functions
|
||||||
|
|
||||||
// GH_3245 TODO: This is a low level call, make it hidden from outside Ndb
|
// GH_3245 TODO: This is a low level call, make it hidden from outside Ndb
|
||||||
internal func lookup_blocks_by_key_with_txn(_ key: NoteKey, txn: RawNdbTxnAccessible) -> NdbBlockGroup.BlocksMetadata? {
|
internal func lookup_blocks_by_key_with_txn(_ key: NoteKey, txn: RawNdbTxnAccessible) -> NdbBlockGroup.BlocksMetadata? {
|
||||||
guard let blocks = ndb_get_blocks_by_key(self.ndb.ndb, &txn.txn, key) else {
|
guard let blocks = ndb_get_blocks_by_key(self.ndb.ndb, &txn.txn, key) else {
|
||||||
@@ -244,14 +267,16 @@ class Ndb {
|
|||||||
return NdbBlockGroup.BlocksMetadata(ptr: blocks)
|
return NdbBlockGroup.BlocksMetadata(ptr: blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_blocks_by_key<T>(_ key: NoteKey, borrow lendingFunction: (_: borrowing NdbBlockGroup.BlocksMetadata?) throws -> T) rethrows -> T {
|
func lookup_blocks_by_key<T>(_ key: NoteKey, borrow lendingFunction: (_: borrowing NdbBlockGroup.BlocksMetadata?) throws -> T) throws -> T {
|
||||||
let txn = SafeNdbTxn<NdbBlockGroup.BlocksMetadata?>.new(on: self) { txn in
|
return try withNdb({
|
||||||
lookup_blocks_by_key_with_txn(key, txn: txn)
|
let txn = SafeNdbTxn<NdbBlockGroup.BlocksMetadata?>.new(on: self) { txn in
|
||||||
}
|
lookup_blocks_by_key_with_txn(key, txn: txn)
|
||||||
guard let txn else {
|
}
|
||||||
return try lendingFunction(nil)
|
guard let txn else {
|
||||||
}
|
return try lendingFunction(nil)
|
||||||
return try lendingFunction(txn.val)
|
}
|
||||||
|
return try lendingFunction(txn.val)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func lookup_note_by_key_with_txn<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> NdbNote? {
|
private func lookup_note_by_key_with_txn<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> NdbNote? {
|
||||||
@@ -263,174 +288,180 @@ class Ndb {
|
|||||||
return NdbNote(note: ptr, size: size, owned: false, key: key)
|
return NdbNote(note: ptr, size: size, owned: false, key: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func text_search(query: String, limit: Int = 128, order: NdbSearchOrder = .newest_first) -> [NoteKey] {
|
func text_search(query: String, limit: Int = 128, order: NdbSearchOrder = .newest_first) throws -> [NoteKey] {
|
||||||
guard let txn = NdbTxn(ndb: self) else { return [] }
|
return try withNdb({
|
||||||
var results = ndb_text_search_results()
|
guard let txn = NdbTxn(ndb: self) else { return [] }
|
||||||
let res = query.withCString { q in
|
var results = ndb_text_search_results()
|
||||||
let order = order == .newest_first ? NDB_ORDER_DESCENDING : NDB_ORDER_ASCENDING
|
let res = query.withCString { q in
|
||||||
var config = ndb_text_search_config(order: order, limit: Int32(limit))
|
let order = order == .newest_first ? NDB_ORDER_DESCENDING : NDB_ORDER_ASCENDING
|
||||||
return ndb_text_search(&txn.txn, q, &results, &config)
|
var config = ndb_text_search_config(order: order, limit: Int32(limit))
|
||||||
}
|
return ndb_text_search(&txn.txn, q, &results, &config)
|
||||||
|
|
||||||
if res == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
var note_ids = [NoteKey]()
|
|
||||||
for i in 0..<results.num_results {
|
|
||||||
// seriously wtf
|
|
||||||
switch i {
|
|
||||||
case 0: note_ids.append(results.results.0.key.note_id)
|
|
||||||
case 1: note_ids.append(results.results.1.key.note_id)
|
|
||||||
case 2: note_ids.append(results.results.2.key.note_id)
|
|
||||||
case 3: note_ids.append(results.results.3.key.note_id)
|
|
||||||
case 4: note_ids.append(results.results.4.key.note_id)
|
|
||||||
case 5: note_ids.append(results.results.5.key.note_id)
|
|
||||||
case 6: note_ids.append(results.results.6.key.note_id)
|
|
||||||
case 7: note_ids.append(results.results.7.key.note_id)
|
|
||||||
case 8: note_ids.append(results.results.8.key.note_id)
|
|
||||||
case 9: note_ids.append(results.results.9.key.note_id)
|
|
||||||
case 10: note_ids.append(results.results.10.key.note_id)
|
|
||||||
case 11: note_ids.append(results.results.11.key.note_id)
|
|
||||||
case 12: note_ids.append(results.results.12.key.note_id)
|
|
||||||
case 13: note_ids.append(results.results.13.key.note_id)
|
|
||||||
case 14: note_ids.append(results.results.14.key.note_id)
|
|
||||||
case 15: note_ids.append(results.results.15.key.note_id)
|
|
||||||
case 16: note_ids.append(results.results.16.key.note_id)
|
|
||||||
case 17: note_ids.append(results.results.17.key.note_id)
|
|
||||||
case 18: note_ids.append(results.results.18.key.note_id)
|
|
||||||
case 19: note_ids.append(results.results.19.key.note_id)
|
|
||||||
case 20: note_ids.append(results.results.20.key.note_id)
|
|
||||||
case 21: note_ids.append(results.results.21.key.note_id)
|
|
||||||
case 22: note_ids.append(results.results.22.key.note_id)
|
|
||||||
case 23: note_ids.append(results.results.23.key.note_id)
|
|
||||||
case 24: note_ids.append(results.results.24.key.note_id)
|
|
||||||
case 25: note_ids.append(results.results.25.key.note_id)
|
|
||||||
case 26: note_ids.append(results.results.26.key.note_id)
|
|
||||||
case 27: note_ids.append(results.results.27.key.note_id)
|
|
||||||
case 28: note_ids.append(results.results.28.key.note_id)
|
|
||||||
case 29: note_ids.append(results.results.29.key.note_id)
|
|
||||||
case 30: note_ids.append(results.results.30.key.note_id)
|
|
||||||
case 31: note_ids.append(results.results.31.key.note_id)
|
|
||||||
case 32: note_ids.append(results.results.32.key.note_id)
|
|
||||||
case 33: note_ids.append(results.results.33.key.note_id)
|
|
||||||
case 34: note_ids.append(results.results.34.key.note_id)
|
|
||||||
case 35: note_ids.append(results.results.35.key.note_id)
|
|
||||||
case 36: note_ids.append(results.results.36.key.note_id)
|
|
||||||
case 37: note_ids.append(results.results.37.key.note_id)
|
|
||||||
case 38: note_ids.append(results.results.38.key.note_id)
|
|
||||||
case 39: note_ids.append(results.results.39.key.note_id)
|
|
||||||
case 40: note_ids.append(results.results.40.key.note_id)
|
|
||||||
case 41: note_ids.append(results.results.41.key.note_id)
|
|
||||||
case 42: note_ids.append(results.results.42.key.note_id)
|
|
||||||
case 43: note_ids.append(results.results.43.key.note_id)
|
|
||||||
case 44: note_ids.append(results.results.44.key.note_id)
|
|
||||||
case 45: note_ids.append(results.results.45.key.note_id)
|
|
||||||
case 46: note_ids.append(results.results.46.key.note_id)
|
|
||||||
case 47: note_ids.append(results.results.47.key.note_id)
|
|
||||||
case 48: note_ids.append(results.results.48.key.note_id)
|
|
||||||
case 49: note_ids.append(results.results.49.key.note_id)
|
|
||||||
case 50: note_ids.append(results.results.50.key.note_id)
|
|
||||||
case 51: note_ids.append(results.results.51.key.note_id)
|
|
||||||
case 52: note_ids.append(results.results.52.key.note_id)
|
|
||||||
case 53: note_ids.append(results.results.53.key.note_id)
|
|
||||||
case 54: note_ids.append(results.results.54.key.note_id)
|
|
||||||
case 55: note_ids.append(results.results.55.key.note_id)
|
|
||||||
case 56: note_ids.append(results.results.56.key.note_id)
|
|
||||||
case 57: note_ids.append(results.results.57.key.note_id)
|
|
||||||
case 58: note_ids.append(results.results.58.key.note_id)
|
|
||||||
case 59: note_ids.append(results.results.59.key.note_id)
|
|
||||||
case 60: note_ids.append(results.results.60.key.note_id)
|
|
||||||
case 61: note_ids.append(results.results.61.key.note_id)
|
|
||||||
case 62: note_ids.append(results.results.62.key.note_id)
|
|
||||||
case 63: note_ids.append(results.results.63.key.note_id)
|
|
||||||
case 64: note_ids.append(results.results.64.key.note_id)
|
|
||||||
case 65: note_ids.append(results.results.65.key.note_id)
|
|
||||||
case 66: note_ids.append(results.results.66.key.note_id)
|
|
||||||
case 67: note_ids.append(results.results.67.key.note_id)
|
|
||||||
case 68: note_ids.append(results.results.68.key.note_id)
|
|
||||||
case 69: note_ids.append(results.results.69.key.note_id)
|
|
||||||
case 70: note_ids.append(results.results.70.key.note_id)
|
|
||||||
case 71: note_ids.append(results.results.71.key.note_id)
|
|
||||||
case 72: note_ids.append(results.results.72.key.note_id)
|
|
||||||
case 73: note_ids.append(results.results.73.key.note_id)
|
|
||||||
case 74: note_ids.append(results.results.74.key.note_id)
|
|
||||||
case 75: note_ids.append(results.results.75.key.note_id)
|
|
||||||
case 76: note_ids.append(results.results.76.key.note_id)
|
|
||||||
case 77: note_ids.append(results.results.77.key.note_id)
|
|
||||||
case 78: note_ids.append(results.results.78.key.note_id)
|
|
||||||
case 79: note_ids.append(results.results.79.key.note_id)
|
|
||||||
case 80: note_ids.append(results.results.80.key.note_id)
|
|
||||||
case 81: note_ids.append(results.results.81.key.note_id)
|
|
||||||
case 82: note_ids.append(results.results.82.key.note_id)
|
|
||||||
case 83: note_ids.append(results.results.83.key.note_id)
|
|
||||||
case 84: note_ids.append(results.results.84.key.note_id)
|
|
||||||
case 85: note_ids.append(results.results.85.key.note_id)
|
|
||||||
case 86: note_ids.append(results.results.86.key.note_id)
|
|
||||||
case 87: note_ids.append(results.results.87.key.note_id)
|
|
||||||
case 88: note_ids.append(results.results.88.key.note_id)
|
|
||||||
case 89: note_ids.append(results.results.89.key.note_id)
|
|
||||||
case 90: note_ids.append(results.results.90.key.note_id)
|
|
||||||
case 91: note_ids.append(results.results.91.key.note_id)
|
|
||||||
case 92: note_ids.append(results.results.92.key.note_id)
|
|
||||||
case 93: note_ids.append(results.results.93.key.note_id)
|
|
||||||
case 94: note_ids.append(results.results.94.key.note_id)
|
|
||||||
case 95: note_ids.append(results.results.95.key.note_id)
|
|
||||||
case 96: note_ids.append(results.results.96.key.note_id)
|
|
||||||
case 97: note_ids.append(results.results.97.key.note_id)
|
|
||||||
case 98: note_ids.append(results.results.98.key.note_id)
|
|
||||||
case 99: note_ids.append(results.results.99.key.note_id)
|
|
||||||
case 100: note_ids.append(results.results.100.key.note_id)
|
|
||||||
case 101: note_ids.append(results.results.101.key.note_id)
|
|
||||||
case 102: note_ids.append(results.results.102.key.note_id)
|
|
||||||
case 103: note_ids.append(results.results.103.key.note_id)
|
|
||||||
case 104: note_ids.append(results.results.104.key.note_id)
|
|
||||||
case 105: note_ids.append(results.results.105.key.note_id)
|
|
||||||
case 106: note_ids.append(results.results.106.key.note_id)
|
|
||||||
case 107: note_ids.append(results.results.107.key.note_id)
|
|
||||||
case 108: note_ids.append(results.results.108.key.note_id)
|
|
||||||
case 109: note_ids.append(results.results.109.key.note_id)
|
|
||||||
case 110: note_ids.append(results.results.110.key.note_id)
|
|
||||||
case 111: note_ids.append(results.results.111.key.note_id)
|
|
||||||
case 112: note_ids.append(results.results.112.key.note_id)
|
|
||||||
case 113: note_ids.append(results.results.113.key.note_id)
|
|
||||||
case 114: note_ids.append(results.results.114.key.note_id)
|
|
||||||
case 115: note_ids.append(results.results.115.key.note_id)
|
|
||||||
case 116: note_ids.append(results.results.116.key.note_id)
|
|
||||||
case 117: note_ids.append(results.results.117.key.note_id)
|
|
||||||
case 118: note_ids.append(results.results.118.key.note_id)
|
|
||||||
case 119: note_ids.append(results.results.119.key.note_id)
|
|
||||||
case 120: note_ids.append(results.results.120.key.note_id)
|
|
||||||
case 121: note_ids.append(results.results.121.key.note_id)
|
|
||||||
case 122: note_ids.append(results.results.122.key.note_id)
|
|
||||||
case 123: note_ids.append(results.results.123.key.note_id)
|
|
||||||
case 124: note_ids.append(results.results.124.key.note_id)
|
|
||||||
case 125: note_ids.append(results.results.125.key.note_id)
|
|
||||||
case 126: note_ids.append(results.results.126.key.note_id)
|
|
||||||
case 127: note_ids.append(results.results.127.key.note_id)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return note_ids
|
if res == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var note_ids = [NoteKey]()
|
||||||
|
for i in 0..<results.num_results {
|
||||||
|
// seriously wtf
|
||||||
|
switch i {
|
||||||
|
case 0: note_ids.append(results.results.0.key.note_id)
|
||||||
|
case 1: note_ids.append(results.results.1.key.note_id)
|
||||||
|
case 2: note_ids.append(results.results.2.key.note_id)
|
||||||
|
case 3: note_ids.append(results.results.3.key.note_id)
|
||||||
|
case 4: note_ids.append(results.results.4.key.note_id)
|
||||||
|
case 5: note_ids.append(results.results.5.key.note_id)
|
||||||
|
case 6: note_ids.append(results.results.6.key.note_id)
|
||||||
|
case 7: note_ids.append(results.results.7.key.note_id)
|
||||||
|
case 8: note_ids.append(results.results.8.key.note_id)
|
||||||
|
case 9: note_ids.append(results.results.9.key.note_id)
|
||||||
|
case 10: note_ids.append(results.results.10.key.note_id)
|
||||||
|
case 11: note_ids.append(results.results.11.key.note_id)
|
||||||
|
case 12: note_ids.append(results.results.12.key.note_id)
|
||||||
|
case 13: note_ids.append(results.results.13.key.note_id)
|
||||||
|
case 14: note_ids.append(results.results.14.key.note_id)
|
||||||
|
case 15: note_ids.append(results.results.15.key.note_id)
|
||||||
|
case 16: note_ids.append(results.results.16.key.note_id)
|
||||||
|
case 17: note_ids.append(results.results.17.key.note_id)
|
||||||
|
case 18: note_ids.append(results.results.18.key.note_id)
|
||||||
|
case 19: note_ids.append(results.results.19.key.note_id)
|
||||||
|
case 20: note_ids.append(results.results.20.key.note_id)
|
||||||
|
case 21: note_ids.append(results.results.21.key.note_id)
|
||||||
|
case 22: note_ids.append(results.results.22.key.note_id)
|
||||||
|
case 23: note_ids.append(results.results.23.key.note_id)
|
||||||
|
case 24: note_ids.append(results.results.24.key.note_id)
|
||||||
|
case 25: note_ids.append(results.results.25.key.note_id)
|
||||||
|
case 26: note_ids.append(results.results.26.key.note_id)
|
||||||
|
case 27: note_ids.append(results.results.27.key.note_id)
|
||||||
|
case 28: note_ids.append(results.results.28.key.note_id)
|
||||||
|
case 29: note_ids.append(results.results.29.key.note_id)
|
||||||
|
case 30: note_ids.append(results.results.30.key.note_id)
|
||||||
|
case 31: note_ids.append(results.results.31.key.note_id)
|
||||||
|
case 32: note_ids.append(results.results.32.key.note_id)
|
||||||
|
case 33: note_ids.append(results.results.33.key.note_id)
|
||||||
|
case 34: note_ids.append(results.results.34.key.note_id)
|
||||||
|
case 35: note_ids.append(results.results.35.key.note_id)
|
||||||
|
case 36: note_ids.append(results.results.36.key.note_id)
|
||||||
|
case 37: note_ids.append(results.results.37.key.note_id)
|
||||||
|
case 38: note_ids.append(results.results.38.key.note_id)
|
||||||
|
case 39: note_ids.append(results.results.39.key.note_id)
|
||||||
|
case 40: note_ids.append(results.results.40.key.note_id)
|
||||||
|
case 41: note_ids.append(results.results.41.key.note_id)
|
||||||
|
case 42: note_ids.append(results.results.42.key.note_id)
|
||||||
|
case 43: note_ids.append(results.results.43.key.note_id)
|
||||||
|
case 44: note_ids.append(results.results.44.key.note_id)
|
||||||
|
case 45: note_ids.append(results.results.45.key.note_id)
|
||||||
|
case 46: note_ids.append(results.results.46.key.note_id)
|
||||||
|
case 47: note_ids.append(results.results.47.key.note_id)
|
||||||
|
case 48: note_ids.append(results.results.48.key.note_id)
|
||||||
|
case 49: note_ids.append(results.results.49.key.note_id)
|
||||||
|
case 50: note_ids.append(results.results.50.key.note_id)
|
||||||
|
case 51: note_ids.append(results.results.51.key.note_id)
|
||||||
|
case 52: note_ids.append(results.results.52.key.note_id)
|
||||||
|
case 53: note_ids.append(results.results.53.key.note_id)
|
||||||
|
case 54: note_ids.append(results.results.54.key.note_id)
|
||||||
|
case 55: note_ids.append(results.results.55.key.note_id)
|
||||||
|
case 56: note_ids.append(results.results.56.key.note_id)
|
||||||
|
case 57: note_ids.append(results.results.57.key.note_id)
|
||||||
|
case 58: note_ids.append(results.results.58.key.note_id)
|
||||||
|
case 59: note_ids.append(results.results.59.key.note_id)
|
||||||
|
case 60: note_ids.append(results.results.60.key.note_id)
|
||||||
|
case 61: note_ids.append(results.results.61.key.note_id)
|
||||||
|
case 62: note_ids.append(results.results.62.key.note_id)
|
||||||
|
case 63: note_ids.append(results.results.63.key.note_id)
|
||||||
|
case 64: note_ids.append(results.results.64.key.note_id)
|
||||||
|
case 65: note_ids.append(results.results.65.key.note_id)
|
||||||
|
case 66: note_ids.append(results.results.66.key.note_id)
|
||||||
|
case 67: note_ids.append(results.results.67.key.note_id)
|
||||||
|
case 68: note_ids.append(results.results.68.key.note_id)
|
||||||
|
case 69: note_ids.append(results.results.69.key.note_id)
|
||||||
|
case 70: note_ids.append(results.results.70.key.note_id)
|
||||||
|
case 71: note_ids.append(results.results.71.key.note_id)
|
||||||
|
case 72: note_ids.append(results.results.72.key.note_id)
|
||||||
|
case 73: note_ids.append(results.results.73.key.note_id)
|
||||||
|
case 74: note_ids.append(results.results.74.key.note_id)
|
||||||
|
case 75: note_ids.append(results.results.75.key.note_id)
|
||||||
|
case 76: note_ids.append(results.results.76.key.note_id)
|
||||||
|
case 77: note_ids.append(results.results.77.key.note_id)
|
||||||
|
case 78: note_ids.append(results.results.78.key.note_id)
|
||||||
|
case 79: note_ids.append(results.results.79.key.note_id)
|
||||||
|
case 80: note_ids.append(results.results.80.key.note_id)
|
||||||
|
case 81: note_ids.append(results.results.81.key.note_id)
|
||||||
|
case 82: note_ids.append(results.results.82.key.note_id)
|
||||||
|
case 83: note_ids.append(results.results.83.key.note_id)
|
||||||
|
case 84: note_ids.append(results.results.84.key.note_id)
|
||||||
|
case 85: note_ids.append(results.results.85.key.note_id)
|
||||||
|
case 86: note_ids.append(results.results.86.key.note_id)
|
||||||
|
case 87: note_ids.append(results.results.87.key.note_id)
|
||||||
|
case 88: note_ids.append(results.results.88.key.note_id)
|
||||||
|
case 89: note_ids.append(results.results.89.key.note_id)
|
||||||
|
case 90: note_ids.append(results.results.90.key.note_id)
|
||||||
|
case 91: note_ids.append(results.results.91.key.note_id)
|
||||||
|
case 92: note_ids.append(results.results.92.key.note_id)
|
||||||
|
case 93: note_ids.append(results.results.93.key.note_id)
|
||||||
|
case 94: note_ids.append(results.results.94.key.note_id)
|
||||||
|
case 95: note_ids.append(results.results.95.key.note_id)
|
||||||
|
case 96: note_ids.append(results.results.96.key.note_id)
|
||||||
|
case 97: note_ids.append(results.results.97.key.note_id)
|
||||||
|
case 98: note_ids.append(results.results.98.key.note_id)
|
||||||
|
case 99: note_ids.append(results.results.99.key.note_id)
|
||||||
|
case 100: note_ids.append(results.results.100.key.note_id)
|
||||||
|
case 101: note_ids.append(results.results.101.key.note_id)
|
||||||
|
case 102: note_ids.append(results.results.102.key.note_id)
|
||||||
|
case 103: note_ids.append(results.results.103.key.note_id)
|
||||||
|
case 104: note_ids.append(results.results.104.key.note_id)
|
||||||
|
case 105: note_ids.append(results.results.105.key.note_id)
|
||||||
|
case 106: note_ids.append(results.results.106.key.note_id)
|
||||||
|
case 107: note_ids.append(results.results.107.key.note_id)
|
||||||
|
case 108: note_ids.append(results.results.108.key.note_id)
|
||||||
|
case 109: note_ids.append(results.results.109.key.note_id)
|
||||||
|
case 110: note_ids.append(results.results.110.key.note_id)
|
||||||
|
case 111: note_ids.append(results.results.111.key.note_id)
|
||||||
|
case 112: note_ids.append(results.results.112.key.note_id)
|
||||||
|
case 113: note_ids.append(results.results.113.key.note_id)
|
||||||
|
case 114: note_ids.append(results.results.114.key.note_id)
|
||||||
|
case 115: note_ids.append(results.results.115.key.note_id)
|
||||||
|
case 116: note_ids.append(results.results.116.key.note_id)
|
||||||
|
case 117: note_ids.append(results.results.117.key.note_id)
|
||||||
|
case 118: note_ids.append(results.results.118.key.note_id)
|
||||||
|
case 119: note_ids.append(results.results.119.key.note_id)
|
||||||
|
case 120: note_ids.append(results.results.120.key.note_id)
|
||||||
|
case 121: note_ids.append(results.results.121.key.note_id)
|
||||||
|
case 122: note_ids.append(results.results.122.key.note_id)
|
||||||
|
case 123: note_ids.append(results.results.123.key.note_id)
|
||||||
|
case 124: note_ids.append(results.results.124.key.note_id)
|
||||||
|
case 125: note_ids.append(results.results.125.key.note_id)
|
||||||
|
case 126: note_ids.append(results.results.126.key.note_id)
|
||||||
|
case 127: note_ids.append(results.results.127.key.note_id)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return note_ids
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_note_by_key<T>(_ key: NoteKey, borrow lendingFunction: (_: borrowing UnownedNdbNote?) throws -> T) rethrows -> T {
|
func lookup_note_by_key<T>(_ key: NoteKey, borrow lendingFunction: (_: borrowing UnownedNdbNote?) throws -> T) throws -> T {
|
||||||
let txn = NdbTxn(ndb: self) { txn in
|
return try withNdb({
|
||||||
lookup_note_by_key_with_txn(key, txn: txn)
|
let txn = NdbTxn(ndb: self) { txn in
|
||||||
}
|
lookup_note_by_key_with_txn(key, txn: txn)
|
||||||
guard let rawNote = txn?.unsafeUnownedValue else { return try lendingFunction(nil) }
|
}
|
||||||
let unownedNote = UnownedNdbNote(rawNote)
|
guard let rawNote = txn?.unsafeUnownedValue else { return try lendingFunction(nil) }
|
||||||
return try lendingFunction(.some(unownedNote))
|
let unownedNote = UnownedNdbNote(rawNote)
|
||||||
|
return try lendingFunction(.some(unownedNote))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_note_by_key_and_copy(_ key: NoteKey) -> NdbNote? {
|
func lookup_note_by_key_and_copy(_ key: NoteKey) throws -> NdbNote? {
|
||||||
return lookup_note_by_key(key, borrow: { maybeUnownedNote -> NdbNote? in
|
return try withNdb({
|
||||||
switch maybeUnownedNote {
|
return try lookup_note_by_key(key, borrow: { maybeUnownedNote -> NdbNote? in
|
||||||
case .none: return nil
|
switch maybeUnownedNote {
|
||||||
case .some(let unownedNote): return unownedNote.toOwned()
|
case .none: return nil
|
||||||
}
|
case .some(let unownedNote): return unownedNote.toOwned()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,26 +520,30 @@ class Ndb {
|
|||||||
lookup_profile_by_key_inner(key, txn: txn)
|
lookup_profile_by_key_inner(key, txn: txn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
func lookup_profile_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||||
let txn = SafeNdbTxn<ProfileRecord?>.new(on: self) { txn in
|
return try withNdb({
|
||||||
return lookup_profile_by_key_inner(key, txn: txn)
|
let txn = SafeNdbTxn<ProfileRecord?>.new(on: self) { txn in
|
||||||
}
|
return lookup_profile_by_key_inner(key, txn: txn)
|
||||||
guard let txn else { return try lendingFunction(nil) }
|
}
|
||||||
return try lendingFunction(txn.val)
|
guard let txn else { return try lendingFunction(nil) }
|
||||||
|
return try lendingFunction(txn.val)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func lookup_note_with_txn<Y>(id: NoteId, txn: NdbTxn<Y>) -> NdbNote? {
|
private func lookup_note_with_txn<Y>(id: NoteId, txn: NdbTxn<Y>) -> NdbNote? {
|
||||||
lookup_note_with_txn_inner(id: id, txn: txn)
|
lookup_note_with_txn_inner(id: id, txn: txn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile_key(_ pubkey: Pubkey) -> ProfileKey? {
|
func lookup_profile_key(_ pubkey: Pubkey) throws -> ProfileKey? {
|
||||||
guard let txn = NdbTxn(ndb: self, with: { txn in
|
return try withNdb({
|
||||||
lookup_profile_key_with_txn(pubkey, txn: txn)
|
guard let txn = NdbTxn(ndb: self, with: { txn in
|
||||||
}) else {
|
lookup_profile_key_with_txn(pubkey, txn: txn)
|
||||||
return nil
|
}) else {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return txn.value
|
return txn.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func lookup_profile_key_with_txn<Y>(_ pubkey: Pubkey, txn: NdbTxn<Y>) -> ProfileKey? {
|
private func lookup_profile_key_with_txn<Y>(_ pubkey: Pubkey, txn: NdbTxn<Y>) -> ProfileKey? {
|
||||||
@@ -537,40 +572,46 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_note_key(_ id: NoteId) -> NoteKey? {
|
func lookup_note_key(_ id: NoteId) throws -> NoteKey? {
|
||||||
guard let txn = NdbTxn(ndb: self, with: { txn in
|
return try withNdb({
|
||||||
lookup_note_key_with_txn(id, txn: txn)
|
guard let txn = NdbTxn(ndb: self, with: { txn in
|
||||||
}) else {
|
lookup_note_key_with_txn(id, txn: txn)
|
||||||
return nil
|
}) else {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return txn.value
|
return txn.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_note<T>(_ id: NoteId, borrow lendingFunction: (_: borrowing UnownedNdbNote?) throws -> T) rethrows -> T {
|
func lookup_note<T>(_ id: NoteId, borrow lendingFunction: (_: borrowing UnownedNdbNote?) throws -> T) throws -> T {
|
||||||
let txn = NdbTxn(ndb: self) { txn in
|
return try withNdb({
|
||||||
lookup_note_with_txn_inner(id: id, txn: txn)
|
let txn = NdbTxn(ndb: self) { txn in
|
||||||
}
|
lookup_note_with_txn_inner(id: id, txn: txn)
|
||||||
guard let rawNote = txn?.unsafeUnownedValue else { return try lendingFunction(nil) }
|
}
|
||||||
return try lendingFunction(UnownedNdbNote(rawNote))
|
guard let rawNote = txn?.unsafeUnownedValue else { return try lendingFunction(nil) }
|
||||||
|
return try lendingFunction(UnownedNdbNote(rawNote))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_note_and_copy(_ id: NoteId) -> NdbNote? {
|
func lookup_note_and_copy(_ id: NoteId) throws -> NdbNote? {
|
||||||
return self.lookup_note(id, borrow: { unownedNote in
|
return try self.lookup_note(id, borrow: { unownedNote in
|
||||||
return unownedNote?.toOwned()
|
return unownedNote?.toOwned()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
func lookup_profile<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||||
let txn = SafeNdbTxn<ProfileRecord?>.new(on: self) { txn in
|
return try withNdb({
|
||||||
lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn)
|
let txn = SafeNdbTxn<ProfileRecord?>.new(on: self) { txn in
|
||||||
}
|
lookup_profile_with_txn_inner(pubkey: pubkey, txn: txn)
|
||||||
guard let txn else { return try lendingFunction(nil) }
|
}
|
||||||
return try lendingFunction(txn.val)
|
guard let txn else { return try lendingFunction(nil) }
|
||||||
|
return try lendingFunction(txn.val)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile_lnurl(_ pubkey: Pubkey) -> String? {
|
func lookup_profile_lnurl(_ pubkey: Pubkey) throws -> String? {
|
||||||
return lookup_profile(pubkey, borrow: { pr in
|
return try lookup_profile(pubkey, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
case .some(let pr): return pr.lnurl
|
case .some(let pr): return pr.lnurl
|
||||||
@@ -578,8 +619,8 @@ class Ndb {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile_and_copy(_ pubkey: Pubkey) -> Profile? {
|
func lookup_profile_and_copy(_ pubkey: Pubkey) throws -> Profile? {
|
||||||
return self.lookup_profile(pubkey, borrow: { pr in
|
return try self.lookup_profile(pubkey, borrow: { pr in
|
||||||
switch pr {
|
switch pr {
|
||||||
case .some(let pr): return pr.profile
|
case .some(let pr): return pr.profile
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
@@ -592,18 +633,20 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func process_client_event(_ str: String) -> Bool {
|
func process_client_event(_ str: String) -> Bool {
|
||||||
guard !self.is_closed else { return false }
|
return (try? withNdb({
|
||||||
return str.withCString { cstr in
|
return str.withCString { cstr in
|
||||||
return ndb_process_client_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
|
return ndb_process_client_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
|
||||||
}
|
}
|
||||||
|
})) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func write_profile_last_fetched(pubkey: Pubkey, fetched_at: UInt64) {
|
func write_profile_last_fetched(pubkey: Pubkey, fetched_at: UInt64) throws {
|
||||||
guard !closed else { return }
|
return try withNdb({
|
||||||
let _ = pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> () in
|
let _ = pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> () in
|
||||||
guard let p = ptr.baseAddress else { return }
|
guard let p = ptr.baseAddress else { return }
|
||||||
ndb_write_last_profile_fetch(ndb.ndb, p, fetched_at)
|
ndb_write_last_profile_fetch(ndb.ndb, p, fetched_at)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func read_profile_last_fetched<Y>(txn: NdbTxn<Y>, pubkey: Pubkey) -> UInt64? {
|
private func read_profile_last_fetched<Y>(txn: NdbTxn<Y>, pubkey: Pubkey) -> UInt64? {
|
||||||
@@ -619,41 +662,50 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func read_profile_last_fetched(pubkey: Pubkey) -> UInt64? {
|
func read_profile_last_fetched(pubkey: Pubkey) throws -> UInt64? {
|
||||||
var last_fetched: UInt64? = nil
|
return try withNdb({
|
||||||
let _ = NdbTxn(ndb: self) { txn in
|
var last_fetched: UInt64? = nil
|
||||||
last_fetched = read_profile_last_fetched(txn: txn, pubkey: pubkey)
|
let _ = NdbTxn(ndb: self) { txn in
|
||||||
}
|
last_fetched = read_profile_last_fetched(txn: txn, pubkey: pubkey)
|
||||||
return last_fetched
|
}
|
||||||
|
return last_fetched
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func process_event(_ str: String, originRelayURL: String? = nil) -> Bool {
|
func process_event(_ str: String, originRelayURL: String? = nil) -> Bool {
|
||||||
guard !is_closed else { return false }
|
let response = try? withNdb({
|
||||||
guard let originRelayURL else {
|
guard !is_closed else { return false }
|
||||||
|
guard let originRelayURL else {
|
||||||
|
return str.withCString { cstr in
|
||||||
|
return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
return str.withCString { cstr in
|
return str.withCString { cstr in
|
||||||
return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
|
return originRelayURL.withCString { originRelayCString in
|
||||||
|
let meta = UnsafeMutablePointer<ndb_ingest_meta>.allocate(capacity: 1)
|
||||||
|
defer { meta.deallocate() }
|
||||||
|
ndb_ingest_meta_init(meta, 0, originRelayCString)
|
||||||
|
return ndb_process_event_with(ndb.ndb, cstr, Int32(str.utf8.count), meta) != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
return str.withCString { cstr in
|
return response ?? false
|
||||||
return originRelayURL.withCString { originRelayCString in
|
|
||||||
let meta = UnsafeMutablePointer<ndb_ingest_meta>.allocate(capacity: 1)
|
|
||||||
defer { meta.deallocate() }
|
|
||||||
ndb_ingest_meta_init(meta, 0, originRelayCString)
|
|
||||||
return ndb_process_event_with(ndb.ndb, cstr, Int32(str.utf8.count), meta) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func process_events(_ str: String) -> Bool {
|
func process_events(_ str: String) -> Bool {
|
||||||
guard !is_closed else { return false }
|
let response = try? withNdb({
|
||||||
return str.withCString { cstr in
|
return str.withCString { cstr in
|
||||||
return ndb_process_events(ndb.ndb, cstr, str.utf8.count) != 0
|
return ndb_process_events(ndb.ndb, cstr, str.utf8.count) != 0
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return response ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func search_profile(_ search: String, limit: Int) -> [Pubkey] {
|
func search_profile(_ search: String, limit: Int) throws -> [Pubkey] {
|
||||||
guard let txn = NdbTxn<()>.init(ndb: self) else { return [] }
|
return try withNdb({
|
||||||
return search_profile(search, limit: limit, txn: txn)
|
guard let txn = NdbTxn<()>.init(ndb: self) else { return [] }
|
||||||
|
return search_profile(search, limit: limit, txn: txn)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func search_profile<Y>(_ search: String, limit: Int, txn: NdbTxn<Y>) -> [Pubkey] {
|
private func search_profile<Y>(_ search: String, limit: Int, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||||
@@ -684,9 +736,11 @@ class Ndb {
|
|||||||
|
|
||||||
// MARK: NdbFilter queries and subscriptions
|
// MARK: NdbFilter queries and subscriptions
|
||||||
|
|
||||||
func query(filters: [NdbFilter], maxResults: Int) throws(NdbStreamError) -> [NoteKey] {
|
func query(filters: [NdbFilter], maxResults: Int) throws -> [NoteKey] {
|
||||||
guard let txn = NdbTxn(ndb: self) else { return [] }
|
return try withNdb({
|
||||||
return try query(with: txn, filters: filters, maxResults: maxResults)
|
guard let txn = NdbTxn(ndb: self) else { return [] }
|
||||||
|
return try query(with: txn, filters: filters, maxResults: maxResults)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Safe wrapper around the `ndb_query` C function
|
/// Safe wrapper around the `ndb_query` C function
|
||||||
@@ -696,8 +750,7 @@ class Ndb {
|
|||||||
/// - maxResults: Maximum number of results to return
|
/// - maxResults: Maximum number of results to return
|
||||||
/// - Returns: Array of note keys matching the filters
|
/// - Returns: Array of note keys matching the filters
|
||||||
/// - Throws: NdbStreamError if the query fails
|
/// - Throws: NdbStreamError if the query fails
|
||||||
private func query<Y>(with txn: NdbTxn<Y>, filters: [NdbFilter], maxResults: Int) throws(NdbStreamError) -> [NoteKey] {
|
private func query<Y>(with txn: NdbTxn<Y>, filters: [NdbFilter], maxResults: Int) throws -> [NoteKey] {
|
||||||
guard !self.is_closed else { throw .ndbClosed }
|
|
||||||
let filtersPointer = UnsafeMutablePointer<ndb_filter>.allocate(capacity: filters.count)
|
let filtersPointer = UnsafeMutablePointer<ndb_filter>.allocate(capacity: filters.count)
|
||||||
defer { filtersPointer.deallocate() }
|
defer { filtersPointer.deallocate() }
|
||||||
|
|
||||||
@@ -711,7 +764,6 @@ class Ndb {
|
|||||||
let results = UnsafeMutablePointer<ndb_query_result>.allocate(capacity: maxResults)
|
let results = UnsafeMutablePointer<ndb_query_result>.allocate(capacity: maxResults)
|
||||||
defer { results.deallocate() }
|
defer { results.deallocate() }
|
||||||
|
|
||||||
guard !self.is_closed else { throw .ndbClosed }
|
|
||||||
guard ndb_query(&txn.txn, filtersPointer, Int32(filters.count), results, Int32(maxResults), count) == 1 else {
|
guard ndb_query(&txn.txn, filtersPointer, Int32(filters.count), results, Int32(maxResults), count) == 1 else {
|
||||||
throw NdbStreamError.initialQueryFailed
|
throw NdbStreamError.initialQueryFailed
|
||||||
}
|
}
|
||||||
@@ -760,7 +812,7 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up subscription
|
// Set up subscription
|
||||||
subid = ndb_subscribe(self.ndb.ndb, filtersPointer, Int32(filters.count))
|
guard let subid = try? withNdb({ ndb_subscribe(self.ndb.ndb, filtersPointer, Int32(filters.count)) }) else { return }
|
||||||
|
|
||||||
// We are setting the continuation after issuing the subscription call.
|
// We are setting the continuation after issuing the subscription call.
|
||||||
// This won't cause lost notes because if any notes get issued before registering
|
// This won't cause lost notes because if any notes get issued before registering
|
||||||
@@ -789,17 +841,24 @@ class Ndb {
|
|||||||
|
|
||||||
do { try Task.checkCancellation() } catch { throw NdbStreamError.cancelled }
|
do { try Task.checkCancellation() } catch { throw NdbStreamError.cancelled }
|
||||||
|
|
||||||
// CRITICAL: Create the subscription FIRST before querying to avoid race condition
|
var noteIds: [NoteKey] = []
|
||||||
// This ensures that any events indexed after subscription but before query won't be missed
|
|
||||||
let newEventsStream = ndbSubscribe(filters: filters)
|
|
||||||
|
|
||||||
// Now fetch initial results after subscription is registered
|
let newEventsStream = try withNdb({
|
||||||
guard let txn = NdbTxn(ndb: self) else { throw NdbStreamError.cannotOpenTransaction }
|
|
||||||
|
// CRITICAL: Create the subscription FIRST before querying to avoid race condition
|
||||||
// Use our safe wrapper instead of direct C function call
|
// This ensures that any events indexed after subscription but before query won't be missed
|
||||||
let noteIds = try query(with: txn, filters: filters, maxResults: maxSimultaneousResults)
|
let newEventsStream = ndbSubscribe(filters: filters)
|
||||||
|
|
||||||
do { try Task.checkCancellation() } catch { throw NdbStreamError.cancelled }
|
// Now fetch initial results after subscription is registered
|
||||||
|
guard let txn = NdbTxn(ndb: self) else { throw NdbStreamError.cannotOpenTransaction }
|
||||||
|
|
||||||
|
// Use our safe wrapper instead of direct C function call
|
||||||
|
noteIds = try query(with: txn, filters: filters, maxResults: maxSimultaneousResults)
|
||||||
|
|
||||||
|
do { try Task.checkCancellation() } catch { throw NdbStreamError.cancelled }
|
||||||
|
|
||||||
|
return newEventsStream
|
||||||
|
})
|
||||||
|
|
||||||
// Create a cascading stream that combines initial results with new events
|
// Create a cascading stream that combines initial results with new events
|
||||||
return AsyncStream<StreamItem> { continuation in
|
return AsyncStream<StreamItem> { continuation in
|
||||||
@@ -861,7 +920,9 @@ class Ndb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func was(noteKey: NoteKey, seenOn relayUrl: String) throws -> Bool {
|
func was(noteKey: NoteKey, seenOn relayUrl: String) throws -> Bool {
|
||||||
return try self.was(noteKey: noteKey, seenOn: relayUrl, txn: nil)
|
return try withNdb({
|
||||||
|
return try self.was(noteKey: noteKey, seenOn: relayUrl, txn: nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if a given note was seen on any of the listed relay URLs
|
/// Determines if a given note was seen on any of the listed relay URLs
|
||||||
@@ -877,7 +938,9 @@ class Ndb {
|
|||||||
|
|
||||||
/// Determines if a given note was seen on any of the listed relay URLs
|
/// Determines if a given note was seen on any of the listed relay URLs
|
||||||
func was(noteKey: NoteKey, seenOnAnyOf relayUrls: [String]) throws -> Bool {
|
func was(noteKey: NoteKey, seenOnAnyOf relayUrls: [String]) throws -> Bool {
|
||||||
return try self.was(noteKey: noteKey, seenOnAnyOf: relayUrls, txn: nil)
|
return try withNdb({
|
||||||
|
return try self.was(noteKey: noteKey, seenOnAnyOf: relayUrls, txn: nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Internal ndb callback interfaces
|
// MARK: Internal ndb callback interfaces
|
||||||
@@ -1035,4 +1098,3 @@ func getDebugCheckedRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) thro
|
|||||||
func remove_file_prefix(_ str: String) -> String {
|
func remove_file_prefix(_ str: String) -> String {
|
||||||
return str.replacingOccurrences(of: "file://", with: "")
|
return str.replacingOccurrences(of: "file://", with: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -478,8 +478,8 @@ extension NdbNote {
|
|||||||
return ThreadReply(tags: self.tags)?.reply.note_id
|
return ThreadReply(tags: self.tags)?.reply.note_id
|
||||||
}
|
}
|
||||||
|
|
||||||
func block_offsets<T>(ndb: Ndb, borrow lendingFunction: (_: borrowing NdbBlockGroup.BlocksMetadata?) throws -> T) rethrows -> T {
|
func block_offsets<T>(ndb: Ndb, borrow lendingFunction: (_: borrowing NdbBlockGroup.BlocksMetadata?) throws -> T) throws -> T {
|
||||||
guard let key = ndb.lookup_note_key(self.id) else { return try lendingFunction(nil) }
|
guard let key = try ndb.lookup_note_key(self.id) else { return try lendingFunction(nil) }
|
||||||
|
|
||||||
return try ndb.lookup_blocks_by_key(key, borrow: { blocks in
|
return try ndb.lookup_blocks_by_key(key, borrow: { blocks in
|
||||||
return try lendingFunction(blocks)
|
return try lendingFunction(blocks)
|
||||||
|
|||||||
+45
-28
@@ -24,6 +24,12 @@ class NdbTxn<T>: RawNdbTxnAccessible {
|
|||||||
static func pure(ndb: Ndb, val: T) -> NdbTxn<T> {
|
static func pure(ndb: Ndb, val: T) -> NdbTxn<T> {
|
||||||
.init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
|
.init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple helper struct for the init function to avoid compiler errors encountered by using other techniques
|
||||||
|
private struct R {
|
||||||
|
let txn: ndb_txn
|
||||||
|
let generation: Int
|
||||||
|
}
|
||||||
|
|
||||||
init?(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }, name: String? = nil) {
|
init?(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }, name: String? = nil) {
|
||||||
guard !ndb.is_closed else { return nil }
|
guard !ndb.is_closed else { return nil }
|
||||||
@@ -43,17 +49,18 @@ class NdbTxn<T>: RawNdbTxnAccessible {
|
|||||||
let new_ref_count = ref_count + 1
|
let new_ref_count = ref_count + 1
|
||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
||||||
} else {
|
} else {
|
||||||
self.txn = ndb_txn()
|
let result: R? = try? ndb.withNdb({
|
||||||
guard !ndb.is_closed else { return nil }
|
var txn = ndb_txn()
|
||||||
self.generation = ndb.generation
|
#if TXNDEBUG
|
||||||
#if TXNDEBUG
|
txn_count += 1
|
||||||
txn_count += 1
|
#endif
|
||||||
#endif
|
let ok = ndb_begin_query(ndb.ndb.ndb, &txn) != 0
|
||||||
let ok = ndb_begin_query(ndb.ndb.ndb, &self.txn) != 0
|
guard ok else { return .none }
|
||||||
if !ok {
|
return .some(R(txn: txn, generation: ndb.generation))
|
||||||
return nil
|
}, maxWaitTimeout: .milliseconds(200))
|
||||||
}
|
guard let result else { return nil }
|
||||||
self.generation = ndb.generation
|
self.txn = result.txn
|
||||||
|
self.generation = result.generation
|
||||||
Thread.current.threadDictionary["ndb_txn"] = self.txn
|
Thread.current.threadDictionary["ndb_txn"] = self.txn
|
||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = 1
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = 1
|
||||||
Thread.current.threadDictionary["txn_generation"] = ndb.generation
|
Thread.current.threadDictionary["txn_generation"] = ndb.generation
|
||||||
@@ -97,7 +104,9 @@ class NdbTxn<T>: RawNdbTxnAccessible {
|
|||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
||||||
assert(new_ref_count >= 0, "NdbTxn reference count should never be below zero")
|
assert(new_ref_count >= 0, "NdbTxn reference count should never be below zero")
|
||||||
if new_ref_count <= 0 {
|
if new_ref_count <= 0 {
|
||||||
ndb_end_query(&self.txn)
|
_ = try? ndb.withNdb({
|
||||||
|
ndb_end_query(&self.txn)
|
||||||
|
}, maxWaitTimeout: .milliseconds(200))
|
||||||
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
|
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
|
||||||
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn_ref_count")
|
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn_ref_count")
|
||||||
}
|
}
|
||||||
@@ -156,10 +165,16 @@ class SafeNdbTxn<T: ~Copyable> {
|
|||||||
.init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
|
.init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple helper struct for the init function to avoid compiler errors encountered by using other techniques
|
||||||
|
private struct R {
|
||||||
|
let txn: ndb_txn
|
||||||
|
let generation: Int
|
||||||
|
}
|
||||||
|
|
||||||
static func new(on ndb: Ndb, with valueGetter: (PlaceholderNdbTxn) -> T? = { _ in () }, name: String = "txn") -> SafeNdbTxn<T>? {
|
static func new(on ndb: Ndb, with valueGetter: (PlaceholderNdbTxn) -> T? = { _ in () }, name: String = "txn") -> SafeNdbTxn<T>? {
|
||||||
guard !ndb.is_closed else { return nil }
|
guard !ndb.is_closed else { return nil }
|
||||||
var generation = ndb.generation
|
let generation: Int
|
||||||
var txn: ndb_txn
|
let txn: ndb_txn
|
||||||
let inherited: Bool
|
let inherited: Bool
|
||||||
if let active_txn = Thread.current.threadDictionary["ndb_txn"] as? ndb_txn,
|
if let active_txn = Thread.current.threadDictionary["ndb_txn"] as? ndb_txn,
|
||||||
let txn_generation = Thread.current.threadDictionary["txn_generation"] as? Int,
|
let txn_generation = Thread.current.threadDictionary["txn_generation"] as? Int,
|
||||||
@@ -174,26 +189,26 @@ class SafeNdbTxn<T: ~Copyable> {
|
|||||||
let new_ref_count = ref_count + 1
|
let new_ref_count = ref_count + 1
|
||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
||||||
} else {
|
} else {
|
||||||
txn = ndb_txn()
|
let result: R? = try? ndb.withNdb({
|
||||||
guard !ndb.is_closed else { return nil }
|
var txn = ndb_txn()
|
||||||
generation = ndb.generation
|
#if TXNDEBUG
|
||||||
#if TXNDEBUG
|
txn_count += 1
|
||||||
txn_count += 1
|
#endif
|
||||||
#endif
|
let ok = ndb_begin_query(ndb.ndb.ndb, &txn) != 0
|
||||||
let ok = ndb_begin_query(ndb.ndb.ndb, &txn) != 0
|
guard ok else { return .none }
|
||||||
if !ok {
|
return .some(R(txn: txn, generation: ndb.generation))
|
||||||
return nil
|
}, maxWaitTimeout: .milliseconds(200))
|
||||||
}
|
guard let result else { return nil }
|
||||||
generation = ndb.generation
|
txn = result.txn
|
||||||
|
generation = result.generation
|
||||||
Thread.current.threadDictionary["ndb_txn"] = txn
|
Thread.current.threadDictionary["ndb_txn"] = txn
|
||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = 1
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = 1
|
||||||
Thread.current.threadDictionary["txn_generation"] = ndb.generation
|
Thread.current.threadDictionary["txn_generation"] = ndb.generation
|
||||||
inherited = false
|
inherited = false
|
||||||
}
|
}
|
||||||
#if TXNDEBUG
|
#if TXNDEBUG
|
||||||
print("txn: open gen\(self.generation) '\(self.name)' \(txn_count)")
|
print("txn: open gen\(generation) '\(name)' \(txn_count)")
|
||||||
#endif
|
#endif
|
||||||
let moved = false
|
|
||||||
let placeholderTxn = PlaceholderNdbTxn(txn: txn)
|
let placeholderTxn = PlaceholderNdbTxn(txn: txn)
|
||||||
guard let val = valueGetter(placeholderTxn) else { return nil }
|
guard let val = valueGetter(placeholderTxn) else { return nil }
|
||||||
return SafeNdbTxn<T>(ndb: ndb, txn: txn, val: val, generation: generation, inherited: inherited, name: name)
|
return SafeNdbTxn<T>(ndb: ndb, txn: txn, val: val, generation: generation, inherited: inherited, name: name)
|
||||||
@@ -223,7 +238,9 @@ class SafeNdbTxn<T: ~Copyable> {
|
|||||||
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
Thread.current.threadDictionary["ndb_txn_ref_count"] = new_ref_count
|
||||||
assert(new_ref_count >= 0, "NdbTxn reference count should never be below zero")
|
assert(new_ref_count >= 0, "NdbTxn reference count should never be below zero")
|
||||||
if new_ref_count <= 0 {
|
if new_ref_count <= 0 {
|
||||||
ndb_end_query(&self.txn)
|
_ = try? ndb.withNdb({
|
||||||
|
ndb_end_query(&self.txn)
|
||||||
|
}, maxWaitTimeout: .milliseconds(200))
|
||||||
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
|
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
|
||||||
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn_ref_count")
|
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn_ref_count")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
//
|
||||||
|
// NdbUseLock.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-11-12.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Dispatch
|
||||||
|
import Synchronization
|
||||||
|
|
||||||
|
extension Ndb {
|
||||||
|
/// Creates a `sync` mechanism for coordinating usages of ndb (read or write) with the app's ability to close ndb.
|
||||||
|
///
|
||||||
|
/// This prevents race condition between threads reading from `ndb` and the app trying to close `ndb`
|
||||||
|
///
|
||||||
|
/// Implementation notes:
|
||||||
|
/// - This was made as a synchronous mechanism because using `async` solutions (e.g. isolating `Ndb` into an `NdbActor`)
|
||||||
|
/// creates a necessity to change way too much code around the codebase, the interface becomes more cumbersome and difficult to use,
|
||||||
|
/// and might create unnecessary async delays (e.g. it would prevent two tasks from reading Ndb data at once)
|
||||||
|
@available(iOS 18.0, *)
|
||||||
|
class UseLock: UseLockProtocol {
|
||||||
|
/// Number of functions using the `ndb` object (for reading or writing data)
|
||||||
|
private let ndbUserCount = Mutex<UInt>(0)
|
||||||
|
/// Semaphore for general access to `ndb`. A closing task requires exclusive access. Users of `ndb` (read/write tasks) share the access
|
||||||
|
private let ndbAccessSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
||||||
|
private let ndbIsOpen = Mutex<Bool>(false)
|
||||||
|
/// How long a thread can block before throwing an error
|
||||||
|
private static let DEFAULT_TIMEOUT: DispatchTimeInterval = .milliseconds(500)
|
||||||
|
|
||||||
|
/// Keeps the ndb open while performing some specified operation.
|
||||||
|
///
|
||||||
|
/// **WARNING:** Ensure ndb is open _before_ calling this, otherwise the thread may block for the `maxTimeout` period.
|
||||||
|
/// **Implementation note:** NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to perform while `ndb` is open. Keep this as short as safely possible!
|
||||||
|
/// - Parameter maxTimeout: The maximum amount of time the function will wait for the lock before giving up.
|
||||||
|
/// - Returns: The return result for the given operation
|
||||||
|
func keepNdbOpen<T>(during operation: () throws -> T, maxWaitTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws -> T {
|
||||||
|
try self.incrementUserCount(maxTimeout: maxWaitTimeout)
|
||||||
|
defer { self.decrementUserCount() } // Use defer to guarantee this will always be called no matter the outcome of the function
|
||||||
|
return try operation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for ndb to be able to close, then closes it.
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to close. Must return the final boolean value indicating if ndb was closed in the end
|
||||||
|
///
|
||||||
|
/// Implementation note: NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
func waitUntilNdbCanClose(thenClose operation: () -> Bool, maxTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws {
|
||||||
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
|
ndbIsOpen.withLock { ndbIsOpen in
|
||||||
|
ndbIsOpen = operation()
|
||||||
|
if ndbIsOpen {
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markNdbOpen() {
|
||||||
|
ndbIsOpen.withLock { ndbIsOpen in
|
||||||
|
if !ndbIsOpen {
|
||||||
|
ndbIsOpen = true
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func incrementUserCount(maxTimeout: DispatchTimeInterval = .seconds(2)) throws {
|
||||||
|
try ndbUserCount.withLock { currentCount in
|
||||||
|
// Signal that ndb cannot close while we have at least one user using ndb
|
||||||
|
if currentCount == 0 {
|
||||||
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
|
}
|
||||||
|
currentCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decrementUserCount() {
|
||||||
|
ndbUserCount.withLock { currentCount in
|
||||||
|
currentCount -= 1
|
||||||
|
// Signal that ndb can close if we have zero users using ndb
|
||||||
|
if currentCount == 0 {
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LockError: Error {
|
||||||
|
case timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fallback implementation for `UseLock` that works in iOS older than iOS 18, with reduced syncing mechanisms
|
||||||
|
class FallbackUseLock: UseLockProtocol {
|
||||||
|
/// Number of functions using the `ndb` object (for reading or writing data)
|
||||||
|
private var ndbUserCount: UInt = 0
|
||||||
|
/// Semaphore for general access to `ndb`. A closing task requires exclusive access. Users of `ndb` (read/write tasks) share the access
|
||||||
|
private let ndbAccessSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
||||||
|
/// How long a thread can block before throwing an error
|
||||||
|
private static let DEFAULT_TIMEOUT: DispatchTimeInterval = .milliseconds(500)
|
||||||
|
|
||||||
|
/// Keeps the ndb open while performing some specified operation.
|
||||||
|
///
|
||||||
|
/// **WARNING:** Ensure ndb is open _before_ calling this, otherwise the thread may block for the `maxTimeout` period.
|
||||||
|
/// **Implementation note:** NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to perform while `ndb` is open. Keep this as short as safely possible!
|
||||||
|
/// - Parameter maxTimeout: The maximum amount of time the function will wait for the lock before giving up.
|
||||||
|
/// - Returns: The return result for the given operation
|
||||||
|
func keepNdbOpen<T>(during operation: () throws -> T, maxWaitTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws -> T {
|
||||||
|
try self.incrementUserCount(maxTimeout: maxWaitTimeout)
|
||||||
|
defer { self.decrementUserCount() } // Use defer to guarantee this will always be called no matter the outcome of the function
|
||||||
|
return try operation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for ndb to be able to close, then closes it.
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to close. Must return the final boolean value indicating if ndb was closed in the end
|
||||||
|
///
|
||||||
|
/// Implementation note: NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
func waitUntilNdbCanClose(thenClose operation: () -> Bool, maxTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws {
|
||||||
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
|
let ndbIsOpen = operation()
|
||||||
|
if ndbIsOpen {
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks `ndb` as open to allow other users to use it. Do not call this more than once
|
||||||
|
func markNdbOpen() {
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func incrementUserCount(maxTimeout: DispatchTimeInterval = .seconds(2)) throws {
|
||||||
|
if ndbUserCount == 0 {
|
||||||
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
|
}
|
||||||
|
ndbUserCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decrementUserCount() {
|
||||||
|
ndbUserCount -= 1
|
||||||
|
// Signal that ndb can close if we have zero users using ndb
|
||||||
|
if ndbUserCount == 0 {
|
||||||
|
ndbAccessSemaphore.signal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LockError: Error {
|
||||||
|
case timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol UseLockProtocol {
|
||||||
|
/// Keeps the ndb open while performing some specified operation.
|
||||||
|
///
|
||||||
|
/// **WARNING:** Ensure ndb is open _before_ calling this, otherwise the thread may block for the `maxTimeout` period.
|
||||||
|
/// **Implementation note:** NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to perform while `ndb` is open. Keep this as short as safely possible!
|
||||||
|
/// - Parameter maxTimeout: The maximum amount of time the function will wait for the lock before giving up.
|
||||||
|
/// - Returns: The return result for the given operation
|
||||||
|
func keepNdbOpen<T>(during operation: () throws -> T, maxWaitTimeout: DispatchTimeInterval) throws -> T
|
||||||
|
|
||||||
|
/// Waits for ndb to be able to close, then closes it.
|
||||||
|
///
|
||||||
|
/// - Parameter operation: The operation to close. Must return the final boolean value indicating if ndb was closed in the end
|
||||||
|
///
|
||||||
|
/// Implementation note: NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
|
func waitUntilNdbCanClose(thenClose operation: () -> Bool, maxTimeout: DispatchTimeInterval) throws
|
||||||
|
|
||||||
|
/// Marks `ndb` as open to allow other users to use it. Do not call this more than once
|
||||||
|
func markNdbOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func initLock() -> UseLockProtocol {
|
||||||
|
if #available(iOS 18.0, *) {
|
||||||
|
return UseLock()
|
||||||
|
} else {
|
||||||
|
return FallbackUseLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension DispatchSemaphore {
|
||||||
|
func waitOrThrow(timeout: DispatchTime) throws(TimingError) {
|
||||||
|
let result = self.wait(timeout: timeout)
|
||||||
|
switch result {
|
||||||
|
case .success: return
|
||||||
|
case .timedOut: throw .timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TimingError: Error {
|
||||||
|
case timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,15 +63,14 @@ final class NdbTests: XCTestCase {
|
|||||||
do {
|
do {
|
||||||
let ndb = Ndb(path: db_dir)!
|
let ndb = Ndb(path: db_dir)!
|
||||||
let id = NoteId(hex: "d12c17bde3094ad32f4ab862a6cc6f5c289cfe7d5802270bdf34904df585f349")!
|
let id = NoteId(hex: "d12c17bde3094ad32f4ab862a6cc6f5c289cfe7d5802270bdf34904df585f349")!
|
||||||
guard let txn = NdbTxn(ndb: ndb) else { return XCTAssert(false) }
|
let note = try? ndb.lookup_note_and_copy(id)
|
||||||
let note = ndb.lookup_note_and_copy(id)
|
|
||||||
XCTAssertNotNil(note)
|
XCTAssertNotNil(note)
|
||||||
guard let note else { return }
|
guard let note else { return }
|
||||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||||
XCTAssertEqual(note.pubkey, pk)
|
XCTAssertEqual(note.pubkey, pk)
|
||||||
|
|
||||||
let profile = ndb.lookup_profile_and_copy(pk)
|
let profile = try? ndb.lookup_profile_and_copy(pk)
|
||||||
let lnurl = ndb.lookup_profile_lnurl(pk)
|
let lnurl = try? ndb.lookup_profile_lnurl(pk)
|
||||||
XCTAssertNotNil(profile)
|
XCTAssertNotNil(profile)
|
||||||
guard let profile else { return }
|
guard let profile else { return }
|
||||||
|
|
||||||
@@ -91,14 +90,14 @@ final class NdbTests: XCTestCase {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let ndb = Ndb(path: db_dir)!
|
let ndb = Ndb(path: db_dir)!
|
||||||
let note_ids = ndb.text_search(query: "barked")
|
let note_ids = (try? ndb.text_search(query: "barked")) ?? []
|
||||||
XCTAssertEqual(note_ids.count, 1)
|
XCTAssertEqual(note_ids.count, 1)
|
||||||
let expected_note_id = NoteId(hex: "b17a540710fe8495b16bfbaf31c6962c4ba8387f3284a7973ad523988095417e")!
|
let expected_note_id = NoteId(hex: "b17a540710fe8495b16bfbaf31c6962c4ba8387f3284a7973ad523988095417e")!
|
||||||
guard note_ids.count > 0 else {
|
guard note_ids.count > 0 else {
|
||||||
XCTFail("Expected at least one note to be found")
|
XCTFail("Expected at least one note to be found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let note_id = ndb.lookup_note_by_key(note_ids[0], borrow: { maybeUnownedNote -> NoteId? in
|
let note_id = try? ndb.lookup_note_by_key(note_ids[0], borrow: { maybeUnownedNote -> NoteId? in
|
||||||
switch maybeUnownedNote {
|
switch maybeUnownedNote {
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
case .some(let unownedNote): return unownedNote.id
|
case .some(let unownedNote): return unownedNote.id
|
||||||
|
|||||||
Reference in New Issue
Block a user