nostrdb: add profiles to nostrdb
This adds profiles to nostrdb - Remove in-memory Profiles caches, nostrdb is as fast as an in-memory cache - Remove ProfileDatabase and just use nostrdb directly Changelog-Changed: Use nostrdb for profiles
This commit is contained in:
@@ -355,6 +355,7 @@
|
|||||||
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */; };
|
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */; };
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
|
||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
|
||||||
|
4CEF958D2A9CE650000F901B /* verifier.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792D42A9939BD00489948 /* verifier.c */; };
|
||||||
4CF0ABD42980996B00D66079 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD32980996B00D66079 /* Report.swift */; };
|
4CF0ABD42980996B00D66079 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD32980996B00D66079 /* Report.swift */; };
|
||||||
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; };
|
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; };
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; };
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; };
|
||||||
@@ -377,10 +378,6 @@
|
|||||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
||||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||||
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
|
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
|
||||||
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */; };
|
|
||||||
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
|
|
||||||
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
|
|
||||||
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */; };
|
|
||||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||||
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; };
|
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; };
|
||||||
@@ -1059,10 +1056,6 @@
|
|||||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
||||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||||
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
|
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
|
||||||
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabaseTests.swift; sourceTree = "<group>"; };
|
|
||||||
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
|
|
||||||
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
|
|
||||||
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabase.swift; sourceTree = "<group>"; };
|
|
||||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||||
504323A62A34915F006AE6DC /* RelayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModel.swift; sourceTree = "<group>"; };
|
504323A62A34915F006AE6DC /* RelayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -1691,7 +1684,6 @@
|
|||||||
4C75EFAB28049CC80006080F /* Nostr */ = {
|
4C75EFAB28049CC80006080F /* Nostr */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
501F8C5329FF5EE2001AFC1D /* CoreData */,
|
|
||||||
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
|
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
|
||||||
50A60D132A28BEEE00186190 /* RelayLog.swift */,
|
50A60D132A28BEEE00186190 /* RelayLog.swift */,
|
||||||
4C75EFA527FF87A20006080F /* Nostr.swift */,
|
4C75EFA527FF87A20006080F /* Nostr.swift */,
|
||||||
@@ -1704,7 +1696,6 @@
|
|||||||
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */,
|
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */,
|
||||||
4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */,
|
4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */,
|
||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
||||||
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */,
|
|
||||||
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
||||||
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
||||||
50088DA029E8271A008A1FDF /* WebSocket.swift */,
|
50088DA029E8271A008A1FDF /* WebSocket.swift */,
|
||||||
@@ -2137,7 +2128,6 @@
|
|||||||
4CB883A9297612FF00DC99E7 /* ZapTests.swift */,
|
4CB883A9297612FF00DC99E7 /* ZapTests.swift */,
|
||||||
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */,
|
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */,
|
||||||
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */,
|
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */,
|
||||||
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */,
|
|
||||||
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */,
|
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */,
|
||||||
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
|
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
|
||||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
|
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
|
||||||
@@ -2249,15 +2239,6 @@
|
|||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
501F8C5329FF5EE2001AFC1D /* CoreData */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */,
|
|
||||||
501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */,
|
|
||||||
);
|
|
||||||
path = CoreData;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
7C0F392D29B57C8F0039859C /* Extensions */ = {
|
7C0F392D29B57C8F0039859C /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2497,6 +2478,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4CEF958D2A9CE650000F901B /* verifier.c in Sources */,
|
||||||
4C32B9342A9AD01A00DC3548 /* NdbProfile.swift in Sources */,
|
4C32B9342A9AD01A00DC3548 /* NdbProfile.swift in Sources */,
|
||||||
4C32B9332A99845B00DC3548 /* Ndb.swift in Sources */,
|
4C32B9332A99845B00DC3548 /* Ndb.swift in Sources */,
|
||||||
4C4793082A993E8900489948 /* refmap.c in Sources */,
|
4C4793082A993E8900489948 /* refmap.c in Sources */,
|
||||||
@@ -2624,7 +2606,6 @@
|
|||||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
||||||
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
|
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */,
|
|
||||||
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
||||||
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */,
|
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */,
|
||||||
@@ -2757,7 +2738,6 @@
|
|||||||
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||||
F71694EA2A662232001F4053 /* SuggestedUsersView.swift in Sources */,
|
F71694EA2A662232001F4053 /* SuggestedUsersView.swift in Sources */,
|
||||||
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */,
|
|
||||||
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
|
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
|
||||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
||||||
@@ -2848,7 +2828,6 @@
|
|||||||
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
||||||
4C1253602A76CF890004F4B8 /* ScrollToTopNotify.swift in Sources */,
|
4C1253602A76CF890004F4B8 /* ScrollToTopNotify.swift in Sources */,
|
||||||
4CA3529E2A76AE67003BB08B /* FollowNotify.swift in Sources */,
|
4CA3529E2A76AE67003BB08B /* FollowNotify.swift in Sources */,
|
||||||
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */,
|
|
||||||
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
|
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
|
||||||
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
||||||
4C1253642A76D08F0004F4B8 /* ReportNotify.swift in Sources */,
|
4C1253642A76D08F0004F4B8 /* ReportNotify.swift in Sources */,
|
||||||
@@ -2893,7 +2872,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4C684A572A7FFAE6005E6031 /* UrlTests.swift in Sources */,
|
4C684A572A7FFAE6005E6031 /* UrlTests.swift in Sources */,
|
||||||
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */,
|
|
||||||
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
||||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
||||||
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
||||||
@@ -3451,19 +3429,6 @@
|
|||||||
productName = secp256k1;
|
productName = secp256k1;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
|
||||||
501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */ = {
|
|
||||||
isa = XCVersionGroup;
|
|
||||||
children = (
|
|
||||||
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */,
|
|
||||||
);
|
|
||||||
currentVersion = 501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */;
|
|
||||||
path = Damus.xcdatamodeld;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
versionGroupType = wrapper.xcdatamodel;
|
|
||||||
};
|
|
||||||
/* End XCVersionGroup section */
|
|
||||||
};
|
};
|
||||||
rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */;
|
rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,6 @@ struct UserStatusSheet: View {
|
|||||||
|
|
||||||
struct UserStatusSheet_Previews: PreviewProvider {
|
struct UserStatusSheet_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UserStatusSheet(postbox: PostBox(pool: RelayPool()), keypair: Keypair(pubkey: .empty, privkey: nil), status: .init())
|
UserStatusSheet(postbox: PostBox(pool: RelayPool(ndb: .empty)), keypair: Keypair(pubkey: .empty, privkey: nil), status: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ import SwiftUI
|
|||||||
import AVKit
|
import AVKit
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
|
|
||||||
struct TimestampedProfile {
|
|
||||||
let profile: Profile
|
|
||||||
let timestamp: UInt32
|
|
||||||
let event: NostrEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ZapSheet {
|
struct ZapSheet {
|
||||||
let target: ZapTarget
|
let target: ZapTarget
|
||||||
let lnurl: String
|
let lnurl: String
|
||||||
@@ -378,10 +372,9 @@ struct ContentView: View {
|
|||||||
invalidate_zapper_cache(pubkey: keypair.pubkey, profiles: ds.profiles, lnurl: ds.lnurls)
|
invalidate_zapper_cache(pubkey: keypair.pubkey, profiles: ds.profiles, lnurl: ds.lnurls)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.lud16 = lud16
|
let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: lud16, nip05: profile.nip05, damus_donation: profile.damus_donation, reactions: profile.reactions)
|
||||||
guard let ev = make_metadata_event(keypair: keypair, metadata: profile) else {
|
|
||||||
return
|
guard let ev = make_metadata_event(keypair: keypair, metadata: prof) else { return }
|
||||||
}
|
|
||||||
ds.postbox.send(ev)
|
ds.postbox.send(ev)
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.broadcast)) { ev in
|
.onReceive(handle_notify(.broadcast)) { ev in
|
||||||
@@ -389,8 +382,10 @@ struct ContentView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ds.postbox.send(ev)
|
ds.postbox.send(ev)
|
||||||
if let profile = ds.profiles.lookup_with_timestamp(id: ev.pubkey) {
|
if let record = ds.profiles.lookup_with_timestamp(ev.pubkey),
|
||||||
ds.postbox.send(profile.event)
|
let event = ds.events.lookup_by_key(record.noteKey)
|
||||||
|
{
|
||||||
|
ds.postbox.send(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unfollow)) { target in
|
.onReceive(handle_notify(.unfollow)) { target in
|
||||||
@@ -501,10 +496,9 @@ struct ContentView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.reactions = !hide
|
let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: profile.lud16, nip05: profile.nip05, damus_donation: profile.damus_donation, reactions: !hide)
|
||||||
guard let profile_ev = make_metadata_event(keypair: keypair, metadata: profile) else {
|
|
||||||
return
|
guard let profile_ev = make_metadata_event(keypair: keypair, metadata: prof) else { return }
|
||||||
}
|
|
||||||
damus_state.postbox.send(profile_ev)
|
damus_state.postbox.send(profile_ev)
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("User muted", comment: "Alert message to indicate the user has been muted"), isPresented: $user_muted_confirm, actions: {
|
.alert(NSLocalizedString("User muted", comment: "Alert message to indicate the user has been muted"), isPresented: $user_muted_confirm, actions: {
|
||||||
@@ -597,7 +591,10 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func connect() {
|
func connect() {
|
||||||
let pool = RelayPool()
|
// nostrdb
|
||||||
|
let ndb = Ndb()!
|
||||||
|
|
||||||
|
let pool = RelayPool(ndb: ndb)
|
||||||
let model_cache = RelayModelCache()
|
let model_cache = RelayModelCache()
|
||||||
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
||||||
@@ -622,13 +619,14 @@ struct ContentView: View {
|
|||||||
try? pool.add_relay(.nwc(url: nwc.relay))
|
try? pool.add_relay(.nwc(url: nwc.relay))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let user_search_cache = UserSearchCache()
|
let user_search_cache = UserSearchCache()
|
||||||
self.damus_state = DamusState(pool: pool,
|
self.damus_state = DamusState(pool: pool,
|
||||||
keypair: keypair,
|
keypair: keypair,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
boosts: EventCounter(our_pubkey: pubkey),
|
boosts: EventCounter(our_pubkey: pubkey),
|
||||||
contacts: Contacts(our_pubkey: pubkey),
|
contacts: Contacts(our_pubkey: pubkey),
|
||||||
profiles: Profiles(user_search_cache: user_search_cache),
|
profiles: Profiles(user_search_cache: user_search_cache, ndb: ndb),
|
||||||
dms: home.dms,
|
dms: home.dms,
|
||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
zaps: Zaps(our_pubkey: pubkey),
|
zaps: Zaps(our_pubkey: pubkey),
|
||||||
@@ -637,7 +635,7 @@ struct ContentView: View {
|
|||||||
relay_filters: relay_filters,
|
relay_filters: relay_filters,
|
||||||
relay_model_cache: model_cache,
|
relay_model_cache: model_cache,
|
||||||
drafts: Drafts(),
|
drafts: Drafts(),
|
||||||
events: EventCache(),
|
events: EventCache(ndb: ndb),
|
||||||
bookmarks: BookmarksManager(pubkey: pubkey),
|
bookmarks: BookmarksManager(pubkey: pubkey),
|
||||||
postbox: PostBox(pool: pool),
|
postbox: PostBox(pool: pool),
|
||||||
bootstrap_relays: bootstrap_relays,
|
bootstrap_relays: bootstrap_relays,
|
||||||
@@ -647,7 +645,8 @@ struct ContentView: View {
|
|||||||
nav: self.navigationCoordinator,
|
nav: self.navigationCoordinator,
|
||||||
user_search_cache: user_search_cache,
|
user_search_cache: user_search_cache,
|
||||||
music: MusicController(onChange: music_changed),
|
music: MusicController(onChange: music_changed),
|
||||||
video: VideoController()
|
video: VideoController(),
|
||||||
|
ndb: ndb
|
||||||
)
|
)
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
@@ -817,8 +816,11 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
|
|||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .profile(let pubkey):
|
case .profile(let pubkey):
|
||||||
if let profile = state.profiles.lookup_with_timestamp(id: pubkey) {
|
if let record = state.profiles.lookup_with_timestamp(pubkey),
|
||||||
callback(.profile(profile.profile, profile.event))
|
let profile = record.profile,
|
||||||
|
let event = state.events.lookup_by_key(record.noteKey)
|
||||||
|
{
|
||||||
|
callback(.profile(profile, event))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
||||||
@@ -855,14 +857,11 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
|
|||||||
switch query {
|
switch query {
|
||||||
case .profile:
|
case .profile:
|
||||||
if ev.known_kind == .metadata {
|
if ev.known_kind == .metadata {
|
||||||
process_metadata_event(events: state.events, our_pubkey: state.pubkey, profiles: state.profiles, ev: ev) { profile in
|
guard let profile = state.profiles.lookup(id: ev.pubkey) else {
|
||||||
guard let profile else {
|
|
||||||
callback(.invalid_profile(ev))
|
callback(.invalid_profile(ev))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callback(.profile(profile, ev))
|
callback(.profile(profile, ev))
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .event:
|
case .event:
|
||||||
callback(.event(ev))
|
callback(.event(ev))
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ struct DamusState {
|
|||||||
let user_search_cache: UserSearchCache
|
let user_search_cache: UserSearchCache
|
||||||
let music: MusicController?
|
let music: MusicController?
|
||||||
let video: VideoController
|
let video: VideoController
|
||||||
|
let ndb: Ndb
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func add_zap(zap: Zapping) -> Bool {
|
func add_zap(zap: Zapping) -> Bool {
|
||||||
@@ -67,12 +68,12 @@ struct DamusState {
|
|||||||
let kp = Keypair(pubkey: empty_pub, privkey: nil)
|
let kp = Keypair(pubkey: empty_pub, privkey: nil)
|
||||||
|
|
||||||
return DamusState.init(
|
return DamusState.init(
|
||||||
pool: RelayPool(),
|
pool: RelayPool(ndb: .empty),
|
||||||
keypair: Keypair(pubkey: empty_pub, privkey: empty_sec),
|
keypair: Keypair(pubkey: empty_pub, privkey: empty_sec),
|
||||||
likes: EventCounter(our_pubkey: empty_pub),
|
likes: EventCounter(our_pubkey: empty_pub),
|
||||||
boosts: EventCounter(our_pubkey: empty_pub),
|
boosts: EventCounter(our_pubkey: empty_pub),
|
||||||
contacts: Contacts(our_pubkey: empty_pub),
|
contacts: Contacts(our_pubkey: empty_pub),
|
||||||
profiles: Profiles(user_search_cache: user_search_cache),
|
profiles: Profiles(user_search_cache: user_search_cache, ndb: .empty),
|
||||||
dms: DirectMessagesModel(our_pubkey: empty_pub),
|
dms: DirectMessagesModel(our_pubkey: empty_pub),
|
||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
zaps: Zaps(our_pubkey: empty_pub),
|
zaps: Zaps(our_pubkey: empty_pub),
|
||||||
@@ -81,9 +82,9 @@ struct DamusState {
|
|||||||
relay_filters: RelayFilters(our_pubkey: empty_pub),
|
relay_filters: RelayFilters(our_pubkey: empty_pub),
|
||||||
relay_model_cache: RelayModelCache(),
|
relay_model_cache: RelayModelCache(),
|
||||||
drafts: Drafts(),
|
drafts: Drafts(),
|
||||||
events: EventCache(),
|
events: EventCache(ndb: .empty),
|
||||||
bookmarks: BookmarksManager(pubkey: empty_pub),
|
bookmarks: BookmarksManager(pubkey: empty_pub),
|
||||||
postbox: PostBox(pool: RelayPool()),
|
postbox: PostBox(pool: RelayPool(ndb: .empty)),
|
||||||
bootstrap_relays: [],
|
bootstrap_relays: [],
|
||||||
replies: ReplyCounter(our_pubkey: empty_pub),
|
replies: ReplyCounter(our_pubkey: empty_pub),
|
||||||
muted_threads: MutedThreadsManager(keypair: kp),
|
muted_threads: MutedThreadsManager(keypair: kp),
|
||||||
@@ -91,7 +92,8 @@ struct DamusState {
|
|||||||
nav: NavigationCoordinator(),
|
nav: NavigationCoordinator(),
|
||||||
user_search_cache: user_search_cache,
|
user_search_cache: user_search_cache,
|
||||||
music: nil,
|
music: nil,
|
||||||
video: VideoController()
|
video: VideoController(),
|
||||||
|
ndb: .empty
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ class FollowersModel: ObservableObject {
|
|||||||
|
|
||||||
if ev.known_kind == .contacts {
|
if ev.known_kind == .contacts {
|
||||||
handle_contact_event(ev)
|
handle_contact_event(ev)
|
||||||
} else if ev.known_kind == .metadata {
|
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("followingmodel notice: \(msg)")
|
print("followingmodel notice: \(msg)")
|
||||||
|
|
||||||
|
|||||||
@@ -54,22 +54,6 @@ class FollowingModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
switch ev {
|
// don't need to do anything here really
|
||||||
case .ws_event:
|
|
||||||
break
|
|
||||||
case .nostr_event(let nev):
|
|
||||||
switch nev {
|
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .event(_, let ev):
|
|
||||||
if ev.kind == 0 {
|
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
|
||||||
case .notice(let msg):
|
|
||||||
print("followingmodel notice: \(msg)")
|
|
||||||
case .eose:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||||
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
||||||
return
|
return
|
||||||
@@ -169,7 +170,8 @@ class HomeModel {
|
|||||||
case .contacts:
|
case .contacts:
|
||||||
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
||||||
case .metadata:
|
case .metadata:
|
||||||
handle_metadata_event(ev)
|
// profile metadata processing is handled by nostrdb
|
||||||
|
break
|
||||||
case .list:
|
case .list:
|
||||||
handle_list_event(ev)
|
handle_list_event(ev)
|
||||||
case .boost:
|
case .boost:
|
||||||
@@ -195,6 +197,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_status_event(_ ev: NostrEvent) {
|
func handle_status_event(_ ev: NostrEvent) {
|
||||||
guard let st = UserStatus(ev: ev) else {
|
guard let st = UserStatus(ev: ev) else {
|
||||||
return
|
return
|
||||||
@@ -249,6 +252,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_zap_event(_ ev: NostrEvent) {
|
func handle_zap_event(_ ev: NostrEvent) {
|
||||||
process_zap_event(damus_state: damus_state, ev: ev) { zapres in
|
process_zap_event(damus_state: damus_state, ev: ev) { zapres in
|
||||||
guard case .done(let zap) = zapres,
|
guard case .done(let zap) = zapres,
|
||||||
@@ -373,7 +377,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
||||||
switch conn_event {
|
switch conn_event {
|
||||||
case .ws_event(let ev):
|
case .ws_event(let ev):
|
||||||
@@ -582,10 +586,6 @@ class HomeModel {
|
|||||||
damus_state.contacts.set_mutelist(ev)
|
damus_state.contacts.set_mutelist(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_metadata_event(_ ev: NostrEvent) {
|
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_last_event_of_kind(relay_id: String, kind: UInt32) -> NostrEvent? {
|
func get_last_event_of_kind(relay_id: String, kind: UInt32) -> NostrEvent? {
|
||||||
guard let m = last_event_of_kind[relay_id] else {
|
guard let m = last_event_of_kind[relay_id] else {
|
||||||
last_event_of_kind[relay_id] = [:]
|
last_event_of_kind[relay_id] = [:]
|
||||||
@@ -791,45 +791,6 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) {
|
|
||||||
var old_nip05: String? = nil
|
|
||||||
let mprof = profiles.lookup_with_timestamp(id: ev.pubkey)
|
|
||||||
|
|
||||||
if let mprof {
|
|
||||||
old_nip05 = mprof.profile.nip05
|
|
||||||
if mprof.event.created_at > ev.created_at {
|
|
||||||
// skip if we already have an newer profile
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if old_nip05 != profile.nip05 {
|
|
||||||
// if it's been validated before, invalidate it now
|
|
||||||
profiles.invalidate_nip05(ev.pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at, event: ev)
|
|
||||||
profiles.add(id: ev.pubkey, profile: tprof)
|
|
||||||
|
|
||||||
// load pfps asap
|
|
||||||
|
|
||||||
var changed = false
|
|
||||||
|
|
||||||
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
|
|
||||||
if URL(string: picture) != nil {
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let banner = tprof.profile.banner ?? ""
|
|
||||||
if URL(string: banner) != nil {
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
notify(.profile_updated(pubkey: ev.pubkey, profile: profile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this, let nostrdb handle all validation
|
// TODO: remove this, let nostrdb handle all validation
|
||||||
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
|
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
|
||||||
let validated = events.is_event_valid(ev.id)
|
let validated = events.is_event_valid(ev.id)
|
||||||
@@ -856,24 +817,6 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func process_metadata_event(events: EventCache, our_pubkey: Pubkey, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) {
|
|
||||||
guard_valid_event(events: events, ev: ev) {
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
|
||||||
completion?(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.cache_lnurl()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
|
|
||||||
completion?(profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func robohash(_ pk: Pubkey) -> String {
|
func robohash(_ pk: Pubkey) -> String {
|
||||||
return "https://robohash.org/" + pk.hex()
|
return "https://robohash.org/" + pk.hex()
|
||||||
}
|
}
|
||||||
@@ -1394,6 +1337,7 @@ func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? {
|
|||||||
return pk
|
return pk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
|
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
|
||||||
// These are zap notifications
|
// These are zap notifications
|
||||||
guard let ptag = get_zap_target_pubkey(ev: ev, events: damus_state.events) else {
|
guard let ptag = get_zap_target_pubkey(ev: ev, events: damus_state.events) else {
|
||||||
@@ -1417,17 +1361,13 @@ func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @esc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let profile = damus_state.profiles.lookup(id: ptag) else {
|
guard let record = damus_state.profiles.lookup_with_timestamp(ptag),
|
||||||
|
let lnurl = record.lnurl else {
|
||||||
completion(.failed)
|
completion(.failed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let lnurl = profile.lnurl else {
|
Task { [lnurl] in
|
||||||
completion(.failed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Task {
|
|
||||||
guard let zapper = await fetch_zapper_from_lnurl(lnurls: damus_state.lnurls, pubkey: ptag, lnurl: lnurl) else {
|
guard let zapper = await fetch_zapper_from_lnurl(lnurls: damus_state.lnurls, pubkey: ptag, lnurl: lnurl) else {
|
||||||
completion(.failed)
|
completion(.failed)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -102,8 +102,6 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
}
|
}
|
||||||
} else if ev.known_kind == .contacts {
|
} else if ev.known_kind == .contacts {
|
||||||
handle_profile_contact_event(ev)
|
handle_profile_contact_event(ev)
|
||||||
} else if ev.known_kind == .metadata {
|
|
||||||
process_metadata_event(events: damus.events, our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
|
|
||||||
}
|
}
|
||||||
seen_event.insert(ev.id)
|
seen_event.insert(ev.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,18 +139,10 @@ func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad
|
|||||||
authors: authors)
|
authors: authors)
|
||||||
|
|
||||||
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
|
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
|
||||||
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
|
guard case .nostr_event(let ev) = conn_ev,
|
||||||
guard sub_id == profiles_subid else {
|
case .eose = ev,
|
||||||
return
|
sub_id == profiles_subid
|
||||||
}
|
else {
|
||||||
|
|
||||||
if ev.known_kind == .metadata {
|
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
guard done && sid == profiles_subid else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class ThreadModel: ObservableObject {
|
|||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
|
|
||||||
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
||||||
@@ -105,9 +106,7 @@ class ThreadModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ev.known_kind == .metadata {
|
if ev.known_kind == .zap {
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
} else if ev.known_kind == .zap {
|
|
||||||
process_zap_event(damus_state: damus_state, ev: ev) { zap in
|
process_zap_event(damus_state: damus_state, ev: ev) { zap in
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import Foundation
|
|||||||
/// Cache of searchable users by name, display_name, NIP-05 identifier, or own contact list petname.
|
/// Cache of searchable users by name, display_name, NIP-05 identifier, or own contact list petname.
|
||||||
/// Optimized for fast searches of substrings by using a Trie.
|
/// Optimized for fast searches of substrings by using a Trie.
|
||||||
/// Optimal for performing user searches that could be initiated by typing quickly on a keyboard into a text input field.
|
/// Optimal for performing user searches that could be initiated by typing quickly on a keyboard into a text input field.
|
||||||
|
|
||||||
|
// TODO: replace with lmdb (the b tree should handle this just fine ?)
|
||||||
|
// we just need a name to profile index
|
||||||
class UserSearchCache {
|
class UserSearchCache {
|
||||||
private let trie = Trie<Pubkey>()
|
private let trie = Trie<Pubkey>()
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ class UserSearchCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the differences between an old profile, if it exists, and a new profile, and updates the user search cache accordingly.
|
/// Computes the differences between an old profile, if it exists, and a new profile, and updates the user search cache accordingly.
|
||||||
|
@MainActor
|
||||||
func updateProfile(id: Pubkey, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) {
|
func updateProfile(id: Pubkey, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) {
|
||||||
// Remove searchable keys tied to the old profile if they differ from the new profile
|
// Remove searchable keys tied to the old profile if they differ from the new profile
|
||||||
// to keep the trie clean without empty nodes while avoiding excessive graph searching.
|
// to keep the trie clean without empty nodes while avoiding excessive graph searching.
|
||||||
@@ -38,6 +42,7 @@ class UserSearchCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a profile to the user search cache.
|
/// Adds a profile to the user search cache.
|
||||||
|
@MainActor
|
||||||
private func addProfile(id: Pubkey, profiles: Profiles, profile: Profile) {
|
private func addProfile(id: Pubkey, profiles: Profiles, profile: Profile) {
|
||||||
// Searchable by name.
|
// Searchable by name.
|
||||||
if let name = profile.name {
|
if let name = profile.name {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class ZapsModel: ObservableObject {
|
|||||||
state.pool.unsubscribe(sub_id: zaps_subid)
|
state.pool.unsubscribe(sub_id: zaps_subid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
||||||
guard case .nostr_event(let resp) = conn_ev else {
|
guard case .nostr_event(let resp) = conn_ev else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
//
|
|
||||||
// PersistedProfile.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Bryan Montz on 4/30/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreData
|
|
||||||
|
|
||||||
@objc(PersistedProfile)
|
|
||||||
final class PersistedProfile: NSManagedObject {
|
|
||||||
@NSManaged var id: String?
|
|
||||||
@NSManaged var name: String?
|
|
||||||
@NSManaged var display_name: String?
|
|
||||||
@NSManaged var about: String?
|
|
||||||
@NSManaged var picture: String?
|
|
||||||
@NSManaged var banner: String?
|
|
||||||
@NSManaged var website: String?
|
|
||||||
@NSManaged var lud06: String?
|
|
||||||
@NSManaged var lud16: String?
|
|
||||||
@NSManaged var nip05: String?
|
|
||||||
@NSManaged var damus_donation: Int16
|
|
||||||
@NSManaged var last_update: Date? // The date that the profile was last updated by the user
|
|
||||||
@NSManaged var network_pull_date: Date? // The date we got this profile from a relay (for staleness checking)
|
|
||||||
|
|
||||||
func copyValues(from profile: Profile) {
|
|
||||||
name = profile.name
|
|
||||||
display_name = profile.display_name
|
|
||||||
about = profile.about
|
|
||||||
picture = profile.picture
|
|
||||||
banner = profile.banner
|
|
||||||
website = profile.website
|
|
||||||
lud06 = profile.lud06
|
|
||||||
lud16 = profile.lud16
|
|
||||||
nip05 = profile.nip05
|
|
||||||
damus_donation = profile.damus_donation != nil ? Int16(profile.damus_donation!) : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,114 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
typealias Profile = NdbProfile
|
||||||
|
//typealias ProfileRecord = NdbProfileRecord
|
||||||
|
|
||||||
|
class ProfileRecord {
|
||||||
|
let data: NdbProfileRecord
|
||||||
|
|
||||||
|
init(data: NdbProfileRecord) {
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile: Profile? { return data.profile }
|
||||||
|
var receivedAt: UInt64 { data.receivedAt }
|
||||||
|
var noteKey: UInt64 { data.noteKey }
|
||||||
|
|
||||||
|
private var _lnurl: String? = nil
|
||||||
|
var lnurl: String? {
|
||||||
|
if let _lnurl {
|
||||||
|
return _lnurl
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let profile = data.profile,
|
||||||
|
let addr = profile.lud16 ?? profile.lud06 else {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.contains("@") {
|
||||||
|
// this is a heavy op and is used a lot in views, cache it!
|
||||||
|
let addr = lnaddress_to_lnurl(addr);
|
||||||
|
self._lnurl = addr
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !addr.lowercased().hasPrefix("lnurl") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NdbProfile {
|
||||||
|
var display_name: String? {
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
static func displayName(profile: Profile?, pubkey: Pubkey) -> DisplayName {
|
||||||
|
return parse_display_name(profile: profile, pubkey: pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var damus_donation: Int? {
|
||||||
|
return Int(damusDonation)
|
||||||
|
}
|
||||||
|
|
||||||
|
var damus_donation_v2: Int {
|
||||||
|
return Int(damusDonationV2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var website_url: URL? {
|
||||||
|
if self.website?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.website.flatMap { url in
|
||||||
|
let trim = url.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if !(trim.hasPrefix("http://") || trim.hasPrefix("https://")) {
|
||||||
|
return URL(string: "https://" + trim)
|
||||||
|
}
|
||||||
|
return URL(string: trim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(name: String? = nil, display_name: String? = nil, about: String? = nil, picture: String? = nil, banner: String? = nil, website: String? = nil, lud06: String? = nil, lud16: String? = nil, nip05: String? = nil, damus_donation: Int? = nil, reactions: Bool = true) {
|
||||||
|
|
||||||
|
var fbb = FlatBufferBuilder()
|
||||||
|
|
||||||
|
let name_off = fbb.create(string: name)
|
||||||
|
let display_name_off = fbb.create(string: display_name)
|
||||||
|
let about_off = fbb.create(string: about)
|
||||||
|
let picture_off = fbb.create(string: picture)
|
||||||
|
let banner_off = fbb.create(string: banner)
|
||||||
|
let website_off = fbb.create(string: website)
|
||||||
|
let lud06_off = fbb.create(string: lud06)
|
||||||
|
let lud16_off = fbb.create(string: lud16)
|
||||||
|
let nip05_off = fbb.create(string: nip05)
|
||||||
|
|
||||||
|
let profile_data = NdbProfile.createNdbProfile(&fbb,
|
||||||
|
nameOffset: name_off,
|
||||||
|
websiteOffset: website_off,
|
||||||
|
aboutOffset: about_off,
|
||||||
|
lud16Offset: lud16_off,
|
||||||
|
bannerOffset: banner_off,
|
||||||
|
displayNameOffset: display_name_off,
|
||||||
|
reactions: reactions,
|
||||||
|
pictureOffset: picture_off,
|
||||||
|
nip05Offset: nip05_off,
|
||||||
|
damusDonation: 0,
|
||||||
|
damusDonationV2: damus_donation.map({ Int32($0) }) ?? 0,
|
||||||
|
lud06Offset: lud06_off)
|
||||||
|
|
||||||
|
fbb.finish(offset: profile_data)
|
||||||
|
|
||||||
|
var buf = ByteBuffer(bytes: fbb.sizedByteArray)
|
||||||
|
let profile: Profile = try! getCheckedRoot(byteBuffer: &buf)
|
||||||
|
self = profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
class Profile: Codable {
|
class Profile: Codable {
|
||||||
var value: [String: AnyCodable]
|
var value: [String: AnyCodable]
|
||||||
|
|
||||||
@@ -24,19 +132,6 @@ class Profile: Codable {
|
|||||||
self.damus_donation = damus_donation
|
self.damus_donation = damus_donation
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(persisted_profile: PersistedProfile) {
|
|
||||||
self.init(name: persisted_profile.name,
|
|
||||||
display_name: persisted_profile.display_name,
|
|
||||||
about: persisted_profile.about,
|
|
||||||
picture: persisted_profile.picture,
|
|
||||||
banner: persisted_profile.banner,
|
|
||||||
website: persisted_profile.website,
|
|
||||||
lud06: persisted_profile.lud06,
|
|
||||||
lud16: persisted_profile.lud16,
|
|
||||||
nip05: persisted_profile.nip05,
|
|
||||||
damus_donation: Int(persisted_profile.damus_donation))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func str(_ str: String) -> String? {
|
private func str(_ str: String) -> String? {
|
||||||
return get_val(str)
|
return get_val(str)
|
||||||
}
|
}
|
||||||
@@ -200,6 +295,7 @@ class Profile: Codable {
|
|||||||
return parse_display_name(profile: profile, pubkey: pubkey)
|
return parse_display_name(profile: profile, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func make_test_profile() -> Profile {
|
func make_test_profile() -> Profile {
|
||||||
return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com", damus_donation: 1)
|
return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com", damus_donation: 1)
|
||||||
@@ -222,3 +318,4 @@ func lnaddress_to_lnurl(_ lnaddr: String) -> String? {
|
|||||||
|
|
||||||
return bech32_encode(hrp: "lnurl", Array(dat))
|
return bech32_encode(hrp: "lnurl", Array(dat))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
//
|
|
||||||
// ProfileDatabase.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Bryan Montz on 4/30/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreData
|
|
||||||
|
|
||||||
enum ProfileDatabaseError: Error {
|
|
||||||
case missing_context
|
|
||||||
case outdated_input
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ProfileDatabase {
|
|
||||||
|
|
||||||
private let entity_name = "PersistedProfile"
|
|
||||||
private var persistent_container: NSPersistentContainer?
|
|
||||||
private var background_context: NSManagedObjectContext?
|
|
||||||
private let cache_url: URL
|
|
||||||
|
|
||||||
/// This queue is used to synchronize access to the network_pull_date_cache dictionary, which
|
|
||||||
/// prevents data races from crashing the app.
|
|
||||||
private var queue = DispatchQueue(label: "io.damus.profile_db",
|
|
||||||
qos: .userInteractive,
|
|
||||||
attributes: .concurrent)
|
|
||||||
private var network_pull_date_cache = [Pubkey: Date]()
|
|
||||||
|
|
||||||
init(cache_url: URL = ProfileDatabase.profile_cache_url) {
|
|
||||||
self.cache_url = cache_url
|
|
||||||
set_up()
|
|
||||||
}
|
|
||||||
|
|
||||||
private static var profile_cache_url: URL {
|
|
||||||
(FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("profiles"))!
|
|
||||||
}
|
|
||||||
|
|
||||||
private var persistent_store_description: NSPersistentStoreDescription {
|
|
||||||
let description = NSPersistentStoreDescription(url: cache_url)
|
|
||||||
description.type = NSSQLiteStoreType
|
|
||||||
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
|
|
||||||
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
|
|
||||||
description.setOption(true as NSNumber, forKey: NSSQLiteManualVacuumOption)
|
|
||||||
return description
|
|
||||||
}
|
|
||||||
|
|
||||||
private var object_model: NSManagedObjectModel? {
|
|
||||||
guard let url = Bundle.main.url(forResource: "Damus", withExtension: "momd") else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return NSManagedObjectModel(contentsOf: url)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func set_up() {
|
|
||||||
guard let object_model else {
|
|
||||||
print("⚠️ Warning: ProfileDatabase failed to load its object model")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
persistent_container = NSPersistentContainer(name: "Damus", managedObjectModel: object_model)
|
|
||||||
persistent_container?.persistentStoreDescriptions = [persistent_store_description]
|
|
||||||
persistent_container?.loadPersistentStores { _, error in
|
|
||||||
if let error {
|
|
||||||
print("WARNING: ProfileDatabase failed to load: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
persistent_container?.viewContext.automaticallyMergesChangesFromParent = true
|
|
||||||
persistent_container?.viewContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
|
||||||
|
|
||||||
background_context = persistent_container?.newBackgroundContext()
|
|
||||||
background_context?.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func get_persisted(id: Pubkey, context: NSManagedObjectContext) -> PersistedProfile? {
|
|
||||||
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
|
|
||||||
request.predicate = NSPredicate(format: "id == %@", id.hex())
|
|
||||||
request.fetchLimit = 1
|
|
||||||
return try? context.fetch(request).first
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_network_pull_date(id: Pubkey) -> Date? {
|
|
||||||
var pull_date: Date?
|
|
||||||
queue.sync {
|
|
||||||
pull_date = network_pull_date_cache[id]
|
|
||||||
}
|
|
||||||
if let pull_date {
|
|
||||||
return pull_date
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
|
|
||||||
request.predicate = NSPredicate(format: "id == %@", id.hex())
|
|
||||||
request.fetchLimit = 1
|
|
||||||
request.propertiesToFetch = ["network_pull_date"]
|
|
||||||
guard let profile = try? persistent_container?.viewContext.fetch(request).first else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.async(flags: .barrier) {
|
|
||||||
self.network_pull_date_cache[id] = profile.network_pull_date
|
|
||||||
}
|
|
||||||
return profile.network_pull_date
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public
|
|
||||||
|
|
||||||
/// Updates or inserts a new Profile into the local database. Rejects profiles whose update date
|
|
||||||
/// is older than one we already have. Database writes occur on a background context for best performance.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - id: Profile id (pubkey)
|
|
||||||
/// - profile: Profile object to be stored
|
|
||||||
/// - last_update: Date that the Profile was updated
|
|
||||||
func upsert(id: Pubkey, profile: Profile, last_update: Date) async throws {
|
|
||||||
guard let context = background_context else {
|
|
||||||
throw ProfileDatabaseError.missing_context
|
|
||||||
}
|
|
||||||
|
|
||||||
try await context.perform {
|
|
||||||
var persisted_profile: PersistedProfile?
|
|
||||||
if let profile = self.get_persisted(id: id, context: context) {
|
|
||||||
if let existing_last_update = profile.last_update, last_update < existing_last_update {
|
|
||||||
throw ProfileDatabaseError.outdated_input
|
|
||||||
} else {
|
|
||||||
persisted_profile = profile
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
persisted_profile = NSEntityDescription.insertNewObject(forEntityName: self.entity_name, into: context) as? PersistedProfile
|
|
||||||
persisted_profile?.id = id.hex()
|
|
||||||
}
|
|
||||||
persisted_profile?.copyValues(from: profile)
|
|
||||||
persisted_profile?.last_update = last_update
|
|
||||||
|
|
||||||
let pull_date = Date.now
|
|
||||||
persisted_profile?.network_pull_date = pull_date
|
|
||||||
self.queue.async(flags: .barrier) {
|
|
||||||
self.network_pull_date_cache[id] = pull_date
|
|
||||||
}
|
|
||||||
|
|
||||||
try context.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func get(id: Pubkey) -> Profile? {
|
|
||||||
guard let container = persistent_container,
|
|
||||||
let profile = get_persisted(id: id, context: container.viewContext) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Profile(persisted_profile: profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
var count: Int {
|
|
||||||
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
|
|
||||||
let count = try? persistent_container?.viewContext.count(for: request)
|
|
||||||
return count ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove_all_profiles() throws {
|
|
||||||
guard let context = background_context, let container = persistent_container else {
|
|
||||||
throw ProfileDatabaseError.missing_context
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.async(flags: .barrier) {
|
|
||||||
self.network_pull_date_cache.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entity_name)
|
|
||||||
let batch_delete_request = NSBatchDeleteRequest(fetchRequest: request)
|
|
||||||
batch_delete_request.resultType = .resultTypeObjectIDs
|
|
||||||
|
|
||||||
let result = try container.persistentStoreCoordinator.execute(batch_delete_request, with: context) as! NSBatchDeleteResult
|
|
||||||
|
|
||||||
// NSBatchDeleteRequest is an NSPersistentStoreRequest, which operates on disk. So now we'll manually update our in-memory context.
|
|
||||||
if let object_ids = result.result as? [NSManagedObjectID] {
|
|
||||||
let changes: [AnyHashable: Any] = [
|
|
||||||
NSDeletedObjectsKey: object_ids
|
|
||||||
]
|
|
||||||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,72 +15,52 @@ class ValidationModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileDataModel: ObservableObject {
|
|
||||||
@Published var profile: TimestampedProfile?
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self.profile = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProfileData {
|
class ProfileData {
|
||||||
var status: UserStatusModel
|
var status: UserStatusModel
|
||||||
var profile_model: ProfileDataModel
|
|
||||||
var validation_model: ValidationModel
|
var validation_model: ValidationModel
|
||||||
var zapper: Pubkey?
|
var zapper: Pubkey?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
status = .init()
|
status = .init()
|
||||||
profile_model = .init()
|
|
||||||
validation_model = .init()
|
validation_model = .init()
|
||||||
zapper = nil
|
zapper = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Profiles {
|
class Profiles {
|
||||||
|
private var ndb: Ndb
|
||||||
|
|
||||||
static let db_freshness_threshold: TimeInterval = 24 * 60 * 60
|
static let db_freshness_threshold: TimeInterval = 24 * 60 * 60
|
||||||
|
|
||||||
/// This queue is used to synchronize access to the profiles dictionary, which
|
@MainActor
|
||||||
/// prevents data races from crashing the app.
|
|
||||||
private var profiles_queue = DispatchQueue(label: "io.damus.profiles",
|
|
||||||
qos: .userInteractive,
|
|
||||||
attributes: .concurrent)
|
|
||||||
|
|
||||||
private var validated_queue = DispatchQueue(label: "io.damus.profiles.validated",
|
|
||||||
qos: .userInteractive,
|
|
||||||
attributes: .concurrent)
|
|
||||||
|
|
||||||
private var profiles: [Pubkey: ProfileData] = [:]
|
private var profiles: [Pubkey: ProfileData] = [:]
|
||||||
|
|
||||||
|
@MainActor
|
||||||
var nip05_pubkey: [String: Pubkey] = [:]
|
var nip05_pubkey: [String: Pubkey] = [:]
|
||||||
|
|
||||||
private let database = ProfileDatabase()
|
|
||||||
|
|
||||||
let user_search_cache: UserSearchCache
|
let user_search_cache: UserSearchCache
|
||||||
|
|
||||||
init(user_search_cache: UserSearchCache) {
|
init(user_search_cache: UserSearchCache, ndb: Ndb) {
|
||||||
self.user_search_cache = user_search_cache
|
self.user_search_cache = user_search_cache
|
||||||
|
self.ndb = ndb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func is_validated(_ pk: Pubkey) -> NIP05? {
|
func is_validated(_ pk: Pubkey) -> NIP05? {
|
||||||
validated_queue.sync {
|
|
||||||
self.profile_data(pk).validation_model.validated
|
self.profile_data(pk).validation_model.validated
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func invalidate_nip05(_ pk: Pubkey) {
|
func invalidate_nip05(_ pk: Pubkey) {
|
||||||
validated_queue.async(flags: .barrier) {
|
|
||||||
self.profile_data(pk).validation_model.validated = nil
|
self.profile_data(pk).validation_model.validated = nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func set_validated(_ pk: Pubkey, nip05: NIP05?) {
|
func set_validated(_ pk: Pubkey, nip05: NIP05?) {
|
||||||
validated_queue.async(flags: .barrier) {
|
|
||||||
self.profile_data(pk).validation_model.validated = nip05
|
self.profile_data(pk).validation_model.validated = nip05
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func profile_data(_ pubkey: Pubkey) -> ProfileData {
|
func profile_data(_ pubkey: Pubkey) -> ProfileData {
|
||||||
guard let data = profiles[pubkey] else {
|
guard let data = profiles[pubkey] else {
|
||||||
let data = ProfileData()
|
let data = ProfileData()
|
||||||
@@ -91,60 +71,28 @@ class Profiles {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func lookup_zapper(pubkey: Pubkey) -> Pubkey? {
|
func lookup_zapper(pubkey: Pubkey) -> Pubkey? {
|
||||||
profile_data(pubkey).zapper
|
profile_data(pubkey).zapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(id: Pubkey, profile: TimestampedProfile) {
|
func lookup_with_timestamp(_ pubkey: Pubkey) -> ProfileRecord? {
|
||||||
profiles_queue.async(flags: .barrier) {
|
return ndb.lookup_profile(pubkey)
|
||||||
let old_timestamped_profile = self.profile_data(id).profile_model.profile
|
|
||||||
self.profile_data(id).profile_model.profile = profile
|
|
||||||
self.user_search_cache.updateProfile(id: id, profiles: self, oldProfile: old_timestamped_profile?.profile, newProfile: profile.profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
try await database.upsert(id: id, profile: profile.profile, last_update: Date(timeIntervalSince1970: TimeInterval(profile.timestamp)))
|
|
||||||
} catch {
|
|
||||||
print("⚠️ Warning: Profiles failed to save a profile: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(id: Pubkey) -> Profile? {
|
func lookup(id: Pubkey) -> Profile? {
|
||||||
var profile: Profile?
|
return ndb.lookup_profile(id)?.profile
|
||||||
profiles_queue.sync {
|
|
||||||
profile = self.profile_data(id).profile_model.profile?.profile
|
|
||||||
}
|
|
||||||
return profile ?? database.get(id: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookup_with_timestamp(id: Pubkey) -> TimestampedProfile? {
|
|
||||||
profiles_queue.sync {
|
|
||||||
return self.profile_data(id).profile_model.profile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func has_fresh_profile(id: Pubkey) -> Bool {
|
func has_fresh_profile(id: Pubkey) -> Bool {
|
||||||
var profile: Profile?
|
var profile: Profile?
|
||||||
profiles_queue.sync {
|
guard let profile = lookup_with_timestamp(id) else { return false }
|
||||||
profile = self.profile_data(id).profile_model.profile?.profile
|
return Date.now.timeIntervalSince(Date(timeIntervalSince1970: Double(profile.receivedAt))) < Profiles.db_freshness_threshold
|
||||||
}
|
|
||||||
if profile != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// check memory first
|
|
||||||
return false
|
|
||||||
|
|
||||||
// then disk
|
|
||||||
guard let pull_date = database.get_network_pull_date(id: id) else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return Date.now.timeIntervalSince(pull_date) < Profiles.db_freshness_threshold
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func invalidate_zapper_cache(pubkey: Pubkey, profiles: Profiles, lnurl: LNUrls) {
|
func invalidate_zapper_cache(pubkey: Pubkey, profiles: Profiles, lnurl: LNUrls) {
|
||||||
profiles.profile_data(pubkey).zapper = nil
|
profiles.profile_data(pubkey).zapper = nil
|
||||||
lnurl.endpoints.removeValue(forKey: pubkey)
|
lnurl.endpoints.removeValue(forKey: pubkey)
|
||||||
|
|||||||
@@ -56,12 +56,17 @@ final class RelayConnection: ObservableObject {
|
|||||||
private var subscriptionToken: AnyCancellable?
|
private var subscriptionToken: AnyCancellable?
|
||||||
|
|
||||||
private var handleEvent: (NostrConnectionEvent) -> ()
|
private var handleEvent: (NostrConnectionEvent) -> ()
|
||||||
|
private var processEvent: (WebSocketEvent) -> ()
|
||||||
private let url: RelayURL
|
private let url: RelayURL
|
||||||
var log: RelayLog?
|
var log: RelayLog?
|
||||||
|
|
||||||
init(url: RelayURL, handleEvent: @escaping (NostrConnectionEvent) -> ()) {
|
init(url: RelayURL,
|
||||||
|
handleEvent: @escaping (NostrConnectionEvent) -> (),
|
||||||
|
processEvent: @escaping (WebSocketEvent) -> ())
|
||||||
|
{
|
||||||
self.url = url
|
self.url = url
|
||||||
self.handleEvent = handleEvent
|
self.handleEvent = handleEvent
|
||||||
|
self.processEvent = processEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func ping() {
|
func ping() {
|
||||||
@@ -138,6 +143,7 @@ final class RelayConnection: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func receive(event: WebSocketEvent) {
|
private func receive(event: WebSocketEvent) {
|
||||||
|
processEvent(event)
|
||||||
switch event {
|
switch event {
|
||||||
case .connected:
|
case .connected:
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -30,12 +30,15 @@ class RelayPool {
|
|||||||
var request_queue: [QueuedRequest] = []
|
var request_queue: [QueuedRequest] = []
|
||||||
var seen: Set<SeenEvent> = Set()
|
var seen: Set<SeenEvent> = Set()
|
||||||
var counts: [String: UInt64] = [:]
|
var counts: [String: UInt64] = [:]
|
||||||
|
var ndb: Ndb
|
||||||
|
|
||||||
private let network_monitor = NWPathMonitor()
|
private let network_monitor = NWPathMonitor()
|
||||||
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
||||||
private var last_network_status: NWPath.Status = .unsatisfied
|
private var last_network_status: NWPath.Status = .unsatisfied
|
||||||
|
|
||||||
init() {
|
init(ndb: Ndb) {
|
||||||
|
self.ndb = ndb
|
||||||
|
|
||||||
network_monitor.pathUpdateHandler = { [weak self] path in
|
network_monitor.pathUpdateHandler = { [weak self] path in
|
||||||
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
|
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -110,9 +113,15 @@ class RelayPool {
|
|||||||
if get_relay(relay_id) != nil {
|
if get_relay(relay_id) != nil {
|
||||||
throw RelayError.RelayAlreadyExists
|
throw RelayError.RelayAlreadyExists
|
||||||
}
|
}
|
||||||
let conn = RelayConnection(url: url) { event in
|
let conn = RelayConnection(url: url, handleEvent: { event in
|
||||||
self.handle_event(relay_id: relay_id, event: event)
|
self.handle_event(relay_id: relay_id, event: event)
|
||||||
}
|
}, processEvent: { wsev in
|
||||||
|
guard case .message(let msg) = wsev,
|
||||||
|
case .string(let str) = msg
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.ndb.process_event(str)
|
||||||
|
})
|
||||||
let relay = Relay(descriptor: desc, connection: conn)
|
let relay = Relay(descriptor: desc, connection: conn)
|
||||||
self.relays.append(relay)
|
self.relays.append(relay)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,11 @@ let test_following_model = FollowingModel(damus_state: test_damus_state(), conta
|
|||||||
func test_damus_state() -> DamusState {
|
func test_damus_state() -> DamusState {
|
||||||
let damus = DamusState.empty
|
let damus = DamusState.empty
|
||||||
|
|
||||||
|
/*
|
||||||
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)
|
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)
|
||||||
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_note)
|
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_note)
|
||||||
damus.profiles.add(id: test_pubkey, profile: tsprof)
|
damus.profiles.add(id: test_pubkey, profile: tsprof)
|
||||||
|
*/
|
||||||
return damus
|
return damus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
func created_deleted_account_profile(keypair: FullKeypair) -> NostrEvent? {
|
func created_deleted_account_profile(keypair: FullKeypair) -> NostrEvent? {
|
||||||
let profile = Profile()
|
let about = "account deleted"
|
||||||
profile.about = "account deleted"
|
let name = "nobody"
|
||||||
profile.name = "nobody"
|
|
||||||
|
|
||||||
guard let content = encode_json(profile) else {
|
let profile = Profile(name: name, about: about)
|
||||||
return nil
|
|
||||||
}
|
guard let content = encode_json(profile) else { return nil }
|
||||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 0)
|
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ class EventData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EventCache {
|
class EventCache {
|
||||||
|
private let ndb: Ndb
|
||||||
private var events: [NoteId: NostrEvent] = [:]
|
private var events: [NoteId: NostrEvent] = [:]
|
||||||
private var replies = ReplyMap()
|
private var replies = ReplyMap()
|
||||||
private var cancellable: AnyCancellable?
|
private var cancellable: AnyCancellable?
|
||||||
@@ -145,7 +146,8 @@ class EventCache {
|
|||||||
|
|
||||||
//private var thread_latest: [String: Int64]
|
//private var thread_latest: [String: Int64]
|
||||||
|
|
||||||
init() {
|
init(ndb: Ndb) {
|
||||||
|
self.ndb = ndb
|
||||||
cancellable = NotificationCenter.default.publisher(
|
cancellable = NotificationCenter.default.publisher(
|
||||||
for: UIApplication.didReceiveMemoryWarningNotification
|
for: UIApplication.didReceiveMemoryWarningNotification
|
||||||
).sink { [weak self] _ in
|
).sink { [weak self] _ in
|
||||||
@@ -251,6 +253,10 @@ class EventCache {
|
|||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookup_by_key(_ key: UInt64) -> NostrEvent? {
|
||||||
|
ndb.lookup_note_by_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
func lookup(_ evid: NoteId) -> NostrEvent? {
|
func lookup(_ evid: NoteId) -> NostrEvent? {
|
||||||
return events[evid]
|
return events[evid]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
damus_state.profiles.lookup(id: event.pubkey)?.lnurl
|
damus_state.profiles.lookup_with_timestamp(event.pubkey)?.lnurl
|
||||||
}
|
}
|
||||||
|
|
||||||
var show_like: Bool {
|
var show_like: Bool {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct EventTop: View {
|
struct EventTop: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct EventShell<Content: View>: View {
|
struct EventShell<Content: View>: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SuggestedUser: Codable {
|
struct SuggestedUser {
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
let name: String
|
let name: String
|
||||||
let about: String
|
let about: String
|
||||||
|
|||||||
@@ -86,13 +86,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
|
|
||||||
switch nev {
|
switch nev {
|
||||||
case .event(let sub_id, let ev):
|
case .event(let sub_id, let ev):
|
||||||
guard sub_id == self.sub_id else {
|
break
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev.known_kind == .metadata {
|
|
||||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("suggested user profiles notice: \(msg)")
|
print("suggested user profiles notice: \(msg)")
|
||||||
|
|||||||
@@ -45,17 +45,13 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func to_profile() -> Profile {
|
func to_profile() -> Profile {
|
||||||
let profile = self.profile ?? Profile()
|
let new_nip05 = nip05.isEmpty ? nil : nip05
|
||||||
|
let new_picture = picture.isEmpty ? nil : picture
|
||||||
|
let new_banner = banner.isEmpty ? nil : banner
|
||||||
|
let new_lud06 = ln.contains("@") ? nil : ln
|
||||||
|
let new_lud16 = ln.contains("@") ? ln : nil
|
||||||
|
|
||||||
profile.name = name
|
let profile = Profile(name: name, display_name: display_name, about: about, picture: new_picture, banner: new_banner, website: website, lud06: new_lud06, lud16: new_lud16, nip05: new_nip05, damus_donation: nil)
|
||||||
profile.display_name = display_name
|
|
||||||
profile.about = about
|
|
||||||
profile.website = website
|
|
||||||
profile.nip05 = nip05.isEmpty ? nil : nip05
|
|
||||||
profile.picture = picture.isEmpty ? nil : picture
|
|
||||||
profile.banner = banner.isEmpty ? nil : banner
|
|
||||||
profile.lud06 = ln.contains("@") ? nil : ln
|
|
||||||
profile.lud16 = ln.contains("@") ? ln : nil
|
|
||||||
|
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Profile Name used when displaying an event in the timeline
|
/// Profile Name used when displaying an event in the timeline
|
||||||
|
@MainActor
|
||||||
struct EventProfileName: View {
|
struct EventProfileName: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ struct ProfileName: View {
|
|||||||
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
var current_nip05: NIP05? {
|
var current_nip05: NIP05? {
|
||||||
nip05 ?? damus_state.profiles.is_validated(pubkey)
|
nip05 ?? damus_state.profiles.is_validated(pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,11 +105,11 @@ func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> UR
|
|||||||
|
|
||||||
func make_preview_profiles(_ pubkey: Pubkey) -> Profiles {
|
func make_preview_profiles(_ pubkey: Pubkey) -> Profiles {
|
||||||
let user_search_cache = UserSearchCache()
|
let user_search_cache = UserSearchCache()
|
||||||
let profiles = Profiles(user_search_cache: user_search_cache)
|
let profiles = Profiles(user_search_cache: user_search_cache, ndb: .empty)
|
||||||
let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
||||||
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com", damus_donation: nil)
|
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com", damus_donation: nil)
|
||||||
let ts_profile = TimestampedProfile(profile: profile, timestamp: 0, event: test_note)
|
//let ts_profile = TimestampedProfile(profile: profile, timestamp: 0, event: test_note)
|
||||||
profiles.add(id: pubkey, profile: ts_profile)
|
//profiles.add(id: pubkey, profile: ts_profile)
|
||||||
return profiles
|
return profiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,8 @@ struct ProfileView: View {
|
|||||||
.accentColor(DamusColors.white)
|
.accentColor(DamusColors.white)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
func lnButton(lnurl: String, record: ProfileRecord, profile: Profile) -> some View {
|
||||||
|
let profile = record.profile!
|
||||||
let button_img = profile.reactions == false ? "zap.fill" : "zap"
|
let button_img = profile.reactions == false ? "zap.fill" : "zap"
|
||||||
return Button(action: {
|
return Button(action: {
|
||||||
present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl))
|
present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl))
|
||||||
@@ -235,7 +236,7 @@ struct ProfileView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(addr, image: "copy2")
|
Label(addr, image: "copy2")
|
||||||
}
|
}
|
||||||
} else if let lnurl = profile.lnurl {
|
} else if let lnurl = record.lnurl {
|
||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.string = lnurl
|
UIPasteboard.general.string = lnurl
|
||||||
} label: {
|
} label: {
|
||||||
@@ -268,14 +269,14 @@ struct ProfileView: View {
|
|||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionSection(profile_data: Profile?) -> some View {
|
func actionSection(record: ProfileRecord?) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
|
if let record,
|
||||||
if let profile = profile_data,
|
let profile = record.profile,
|
||||||
let lnurl = profile.lnurl,
|
let lnurl = record.lnurl,
|
||||||
lnurl != ""
|
lnurl != ""
|
||||||
{
|
{
|
||||||
lnButton(lnurl: lnurl, profile: profile)
|
lnButton(lnurl: lnurl, record: record, profile: profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
dmButton
|
dmButton
|
||||||
@@ -307,8 +308,9 @@ struct ProfileView: View {
|
|||||||
return scale < 1 ? scale : 1
|
return scale < 1 ? scale : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func nameSection(profile_data: Profile?) -> some View {
|
func nameSection(profile_data: ProfileRecord?) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
|
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
.padding(.top, -(pfp_size / 2.0))
|
.padding(.top, -(pfp_size / 2.0))
|
||||||
@@ -323,47 +325,45 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
|
|
||||||
if follows_you {
|
if follows_you {
|
||||||
followsYouBadge
|
followsYouBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
actionSection(profile_data: profile_data)
|
actionSection(record: profile_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileNameView(pubkey: profile.pubkey, profile: profile_data, damus: damus_state)
|
ProfileNameView(pubkey: profile.pubkey, profile: profile_data?.profile, damus: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var followersCount: some View {
|
var followersCount: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if followers.count == nil {
|
if let followerCount = followers.count {
|
||||||
|
let nounString = pluralizedString(key: "followers_count", count: followerCount)
|
||||||
|
let nounText = Text(verbatim: nounString).font(.subheadline).foregroundColor(.gray)
|
||||||
|
Text("\(Text(verbatim: followerCount.formatted()).font(.subheadline.weight(.medium))) \(nounText)", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
||||||
|
} else {
|
||||||
Image("download")
|
Image("download")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
Text("Followers", comment: "Label describing followers of a user.")
|
Text("Followers", comment: "Label describing followers of a user.")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
} else {
|
|
||||||
let followerCount = followers.count!
|
|
||||||
let nounString = pluralizedString(key: "followers_count", count: followerCount)
|
|
||||||
let nounText = Text(verbatim: nounString).font(.subheadline).foregroundColor(.gray)
|
|
||||||
Text("\(Text(verbatim: followerCount.formatted()).font(.subheadline.weight(.medium))) \(nounText)", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var aboutSection: some View {
|
var aboutSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 8.0) {
|
VStack(alignment: .leading, spacing: 8.0) {
|
||||||
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
|
let profile_data = damus_state.profiles.lookup_with_timestamp(profile.pubkey)
|
||||||
|
|
||||||
nameSection(profile_data: profile_data)
|
nameSection(profile_data: profile_data)
|
||||||
|
|
||||||
if let about = profile_data?.about {
|
if let about = profile_data?.profile?.about {
|
||||||
AboutView(state: damus_state, about: about)
|
AboutView(state: damus_state, about: about)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let url = profile_data?.website_url {
|
if let url = profile_data?.profile?.website_url {
|
||||||
WebsiteLink(url: url)
|
WebsiteLink(url: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +514,7 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) {
|
func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) {
|
||||||
guard let profile = profiles.lookup(id: pubkey),
|
guard let profile = profiles.lookup(id: pubkey),
|
||||||
let nip05 = profile.nip05,
|
let nip05 = profile.nip05,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Security
|
|||||||
|
|
||||||
struct SaveKeysView: View {
|
struct SaveKeysView: View {
|
||||||
let account: CreateAccountModel
|
let account: CreateAccountModel
|
||||||
let pool: RelayPool = RelayPool()
|
let pool: RelayPool = RelayPool(ndb: Ndb()!)
|
||||||
@State var pub_copied: Bool = false
|
@State var pub_copied: Bool = false
|
||||||
@State var priv_copied: Bool = false
|
@State var priv_copied: Bool = false
|
||||||
@State var loading: Bool = false
|
@State var loading: Bool = false
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum SearchType: Equatable {
|
|||||||
case nip05(String)
|
case nip05(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct SearchingEventView: View {
|
struct SearchingEventView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let search_type: SearchType
|
let search_type: SearchType
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct SideMenuView: View {
|
struct SideMenuView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@Binding var isSidebarVisible: Bool
|
@Binding var isSidebarVisible: Bool
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ struct WalletView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.damus_donation = p
|
let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: profile.lud16, nip05: profile.nip05, damus_donation: p, reactions: profile.reactions)
|
||||||
|
|
||||||
notify(.profile_updated(pubkey: damus_state.pubkey, profile: profile))
|
notify(.profile_updated(pubkey: damus_state.pubkey, profile: prof))
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
guard let keypair = damus_state.keypair.to_full(),
|
guard let keypair = damus_state.keypair.to_full(),
|
||||||
@@ -180,12 +180,11 @@ struct WalletView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.damus_donation = settings.donation_percent
|
let prof = Profile(name: profile.name, display_name: profile.display_name, about: profile.about, picture: profile.picture, banner: profile.banner, website: profile.website, lud06: profile.lud06, lud16: profile.lud16, nip05: profile.nip05, damus_donation: settings.donation_percent, reactions: profile.reactions)
|
||||||
guard let meta = make_metadata_event(keypair: keypair, metadata: profile) else {
|
|
||||||
|
guard let meta = make_metadata_event(keypair: keypair, metadata: prof) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tsprofile = TimestampedProfile(profile: profile, timestamp: meta.created_at, event: meta)
|
|
||||||
damus_state.profiles.add(id: damus_state.pubkey, profile: tsprofile)
|
|
||||||
damus_state.postbox.send(meta)
|
damus_state.postbox.send(meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ final class NostrScriptTests: XCTestCase {
|
|||||||
|
|
||||||
func test_bool_set() throws {
|
func test_bool_set() throws {
|
||||||
let data = try load_bool_set_test_wasm().bytes
|
let data = try load_bool_set_test_wasm().bytes
|
||||||
let pool = RelayPool()
|
let pool = RelayPool(ndb: .empty)
|
||||||
let script = NostrScript(pool: pool, data: data)
|
let script = NostrScript(pool: pool, data: data)
|
||||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||||
UserSettingsStore.pubkey = pk
|
UserSettingsStore.pubkey = pk
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
//
|
|
||||||
// ProfileDatabaseTests.swift
|
|
||||||
// damusTests
|
|
||||||
//
|
|
||||||
// Created by Bryan Montz on 5/13/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
@testable import damus
|
|
||||||
|
|
||||||
class ProfileDatabaseTests: XCTestCase {
|
|
||||||
|
|
||||||
static let cache_url = (FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("test-profiles"))!
|
|
||||||
let database = ProfileDatabase(cache_url: ProfileDatabaseTests.cache_url)
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
// This method is called after the invocation of each test method in the class.
|
|
||||||
try database.remove_all_profiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
var test_profile: Profile {
|
|
||||||
Profile(name: "test-name",
|
|
||||||
display_name: "test-display-name",
|
|
||||||
about: "test-about",
|
|
||||||
picture: "test-picture",
|
|
||||||
banner: "test-banner",
|
|
||||||
website: "test-website",
|
|
||||||
lud06: "test-lud06",
|
|
||||||
lud16: "test-lud16",
|
|
||||||
nip05: "test-nip05",
|
|
||||||
damus_donation: 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStoreAndRetrieveProfile() async throws {
|
|
||||||
let id = test_pubkey
|
|
||||||
|
|
||||||
let profile = test_profile
|
|
||||||
|
|
||||||
// make sure it's not there yet
|
|
||||||
XCTAssertNil(database.get(id: id))
|
|
||||||
|
|
||||||
// store the profile
|
|
||||||
try await database.upsert(id: id, profile: profile, last_update: .now)
|
|
||||||
|
|
||||||
// read the profile out of the database
|
|
||||||
let retrievedProfile = try XCTUnwrap(database.get(id: id))
|
|
||||||
|
|
||||||
XCTAssertEqual(profile.name, retrievedProfile.name)
|
|
||||||
XCTAssertEqual(profile.display_name, retrievedProfile.display_name)
|
|
||||||
XCTAssertEqual(profile.about, retrievedProfile.about)
|
|
||||||
XCTAssertEqual(profile.picture, retrievedProfile.picture)
|
|
||||||
XCTAssertEqual(profile.banner, retrievedProfile.banner)
|
|
||||||
XCTAssertEqual(profile.website, retrievedProfile.website)
|
|
||||||
XCTAssertEqual(profile.lud06, retrievedProfile.lud06)
|
|
||||||
XCTAssertEqual(profile.lud16, retrievedProfile.lud16)
|
|
||||||
XCTAssertEqual(profile.nip05, retrievedProfile.nip05)
|
|
||||||
XCTAssertEqual(profile.damus_donation, retrievedProfile.damus_donation)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRejectOutdatedProfile() async throws {
|
|
||||||
let id = test_pubkey
|
|
||||||
|
|
||||||
// store a profile
|
|
||||||
let profile = test_profile
|
|
||||||
let profile_last_updated = Date.now
|
|
||||||
try await database.upsert(id: id, profile: profile, last_update: profile_last_updated)
|
|
||||||
|
|
||||||
// try to store a profile with the same id but the last_update date is older than the previously stored profile
|
|
||||||
let outdatedProfile = test_profile
|
|
||||||
let outdated_last_updated = profile_last_updated.addingTimeInterval(-60)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try await database.upsert(id: id, profile: outdatedProfile, last_update: outdated_last_updated)
|
|
||||||
XCTFail("expected to throw error")
|
|
||||||
} catch let error as ProfileDatabaseError {
|
|
||||||
XCTAssertEqual(error, ProfileDatabaseError.outdated_input)
|
|
||||||
} catch {
|
|
||||||
XCTFail("not the expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdateExistingProfile() async throws {
|
|
||||||
let id = test_pubkey
|
|
||||||
|
|
||||||
// store a profile
|
|
||||||
let profile = test_profile
|
|
||||||
let profile_last_update = Date.now
|
|
||||||
try await database.upsert(id: id, profile: profile, last_update: profile_last_update)
|
|
||||||
|
|
||||||
// update the same profile
|
|
||||||
let updated_profile = test_profile
|
|
||||||
updated_profile.nip05 = "updated-nip05"
|
|
||||||
let updated_profile_last_update = profile_last_update.addingTimeInterval(60)
|
|
||||||
try await database.upsert(id: id, profile: updated_profile, last_update: updated_profile_last_update)
|
|
||||||
|
|
||||||
// retrieve the profile and make sure it was updated
|
|
||||||
let retrieved_profile = database.get(id: id)
|
|
||||||
XCTAssertEqual(retrieved_profile?.nip05, "updated-nip05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStoreMultipleAndRemoveAllProfiles() async throws {
|
|
||||||
XCTAssertEqual(database.count, 0)
|
|
||||||
|
|
||||||
// store a profile
|
|
||||||
let id = test_pubkey
|
|
||||||
let profile = test_profile
|
|
||||||
let profile_last_update = Date.now
|
|
||||||
try await database.upsert(id: id, profile: profile, last_update: profile_last_update)
|
|
||||||
|
|
||||||
XCTAssertEqual(database.count, 1)
|
|
||||||
|
|
||||||
// store another profile
|
|
||||||
let id2 = test_pubkey_2
|
|
||||||
let profile2 = test_profile
|
|
||||||
let profile_last_update2 = Date.now
|
|
||||||
try await database.upsert(id: id2, profile: profile2, last_update: profile_last_update2)
|
|
||||||
|
|
||||||
XCTAssertEqual(database.count, 2)
|
|
||||||
|
|
||||||
try database.remove_all_profiles()
|
|
||||||
|
|
||||||
XCTAssertEqual(database.count, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ final class UserSearchCacheTests: XCTestCase {
|
|||||||
let damusState = DamusState.empty
|
let damusState = DamusState.empty
|
||||||
let nip05 = "_@somedomain.com"
|
let nip05 = "_@somedomain.com"
|
||||||
|
|
||||||
|
@MainActor
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
keypair = try XCTUnwrap(generate_new_keypair())
|
keypair = try XCTUnwrap(generate_new_keypair())
|
||||||
|
|
||||||
@@ -24,8 +25,6 @@ final class UserSearchCacheTests: XCTestCase {
|
|||||||
damusState.profiles.set_validated(pubkey, nip05: validatedNip05)
|
damusState.profiles.set_validated(pubkey, nip05: validatedNip05)
|
||||||
|
|
||||||
let profile = Profile(name: "tyiu", display_name: "Terry Yiu", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nip05, damus_donation: nil)
|
let profile = Profile(name: "tyiu", display_name: "Terry Yiu", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nip05, damus_donation: nil)
|
||||||
let timestampedProfile = TimestampedProfile(profile: profile, timestamp: 0, event: test_note)
|
|
||||||
damusState.profiles.add(id: pubkey, profile: timestampedProfile)
|
|
||||||
|
|
||||||
// Lookup to synchronize access on profiles dictionary to avoid race conditions.
|
// Lookup to synchronize access on profiles dictionary to avoid race conditions.
|
||||||
let _ = damusState.profiles.lookup(id: pubkey)
|
let _ = damusState.profiles.lookup(id: pubkey)
|
||||||
@@ -47,6 +46,7 @@ final class UserSearchCacheTests: XCTestCase {
|
|||||||
XCTAssertEqual(damusState.user_search_cache.search(key: "i"), [keypair.pubkey])
|
XCTAssertEqual(damusState.user_search_cache.search(key: "i"), [keypair.pubkey])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testUpdateProfile() throws {
|
func testUpdateProfile() throws {
|
||||||
let keypair = try XCTUnwrap(keypair)
|
let keypair = try XCTUnwrap(keypair)
|
||||||
|
|
||||||
@@ -56,8 +56,6 @@ final class UserSearchCacheTests: XCTestCase {
|
|||||||
damusState.profiles.set_validated(keypair.pubkey, nip05: NIP05.parse(newNip05))
|
damusState.profiles.set_validated(keypair.pubkey, nip05: NIP05.parse(newNip05))
|
||||||
|
|
||||||
let newProfile = Profile(name: "whoami", display_name: "T-DAWG", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: newNip05, damus_donation: nil)
|
let newProfile = Profile(name: "whoami", display_name: "T-DAWG", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: newNip05, damus_donation: nil)
|
||||||
let newTimestampedProfile = TimestampedProfile(profile: newProfile, timestamp: 1000, event: test_note)
|
|
||||||
damusState.profiles.add(id: keypair.pubkey, profile: newTimestampedProfile)
|
|
||||||
|
|
||||||
// Lookup to synchronize access on profiles dictionary to avoid race conditions.
|
// Lookup to synchronize access on profiles dictionary to avoid race conditions.
|
||||||
let _ = damusState.profiles.lookup(id: keypair.pubkey)
|
let _ = damusState.profiles.lookup(id: keypair.pubkey)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ final class WalletConnectTests: XCTestCase {
|
|||||||
let pk = "89446b900c70d62438dcf66756405eea6225ad94dc61f3856f62f9699111a9a6"
|
let pk = "89446b900c70d62438dcf66756405eea6225ad94dc61f3856f62f9699111a9a6"
|
||||||
let nwc = WalletConnectURL(str: "nostrwalletconnect://\(pk)?relay=ws://127.0.0.1&secret=\(sec)&lud16=jb55@jb55.com")!
|
let nwc = WalletConnectURL(str: "nostrwalletconnect://\(pk)?relay=ws://127.0.0.1&secret=\(sec)&lud16=jb55@jb55.com")!
|
||||||
|
|
||||||
let pool = RelayPool()
|
let pool = RelayPool(ndb: .empty)
|
||||||
let box = PostBox(pool: pool)
|
let box = PostBox(pool: pool)
|
||||||
|
|
||||||
nwc_pay(url: nwc, pool: pool, post: box, invoice: "invoice")
|
nwc_pay(url: nwc, pool: pool, post: box, invoice: "invoice")
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import XCTest
|
|||||||
final class ZapTests: XCTestCase {
|
final class ZapTests: XCTestCase {
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
let db = ProfileDatabase()
|
|
||||||
try db.remove_all_profiles()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
override func tearDownWithError() throws {
|
||||||
@@ -71,7 +69,7 @@ final class ZapTests: XCTestCase {
|
|||||||
XCTAssertEqual(zap.target, ZapTarget.profile(profile))
|
XCTAssertEqual(zap.target, ZapTarget.profile(profile))
|
||||||
|
|
||||||
XCTAssertEqual(zap_notification_title(zap), "Zap")
|
XCTAssertEqual(zap_notification_title(zap), "Zap")
|
||||||
XCTAssertEqual(zap_notification_body(profiles: Profiles(user_search_cache: UserSearchCache()), zap: zap), "You received 1k sats from 107jk7ht:2quqncxg")
|
XCTAssertEqual(zap_notification_body(profiles: Profiles(user_search_cache: UserSearchCache(), ndb: .empty), zap: zap), "You received 1k sats from 107jk7ht:2quqncxg")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,28 @@ class Ndb {
|
|||||||
(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.absoluteString.replacingOccurrences(of: "file://", with: ""))!
|
(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.absoluteString.replacingOccurrences(of: "file://", with: ""))!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var empty: Ndb {
|
||||||
|
Ndb(ndb: ndb_t(ndb: nil))
|
||||||
|
}
|
||||||
|
|
||||||
init?() {
|
init?() {
|
||||||
|
//try? FileManager.default.removeItem(atPath: Ndb.db_path + "/lock.mdb")
|
||||||
|
//try? FileManager.default.removeItem(atPath: Ndb.db_path + "/data.mdb")
|
||||||
|
|
||||||
var ndb_p: OpaquePointer? = nil
|
var ndb_p: OpaquePointer? = nil
|
||||||
|
|
||||||
|
let ingest_threads: Int32 = 4
|
||||||
|
var mapsize: Int = 1024 * 1024 * 1024 * 32
|
||||||
|
|
||||||
let ok = Ndb.db_path.withCString { testdir in
|
let ok = Ndb.db_path.withCString { testdir in
|
||||||
return ndb_init(&ndb_p, testdir, 1024 * 1024 * 1024 * 32, 4) != 0
|
var ok = false
|
||||||
|
while !ok && mapsize > 1024 * 1024 * 700 {
|
||||||
|
ok = ndb_init(&ndb_p, testdir, mapsize, ingest_threads) != 0
|
||||||
|
if !ok {
|
||||||
|
mapsize /= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -28,24 +45,51 @@ class Ndb {
|
|||||||
self.ndb = ndb_t(ndb: ndb_p)
|
self.ndb = ndb_t(ndb: ndb_p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(ndb: ndb_t) {
|
||||||
|
self.ndb = ndb
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookup_note_by_key(_ key: UInt64) -> NdbNote? {
|
||||||
|
guard let note_p = ndb_get_note_by_key(ndb.ndb, key, nil) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NdbNote(note: note_p, owned_size: nil)
|
||||||
|
}
|
||||||
|
|
||||||
func lookup_note(_ id: NoteId) -> NdbNote? {
|
func lookup_note(_ id: NoteId) -> NdbNote? {
|
||||||
id.id.withUnsafeBytes { bs in
|
id.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> NdbNote? in
|
||||||
guard let note_p = ndb_get_note_by_id(ndb.ndb, bs, nil) else {
|
guard let baseAddress = ptr.baseAddress,
|
||||||
|
let note_p = ndb_get_note_by_id(ndb.ndb, baseAddress, nil) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return NdbNote(note: note_p, owned_size: nil)
|
return NdbNote(note: note_p, owned_size: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_profile(_ pubkey: Pubkey) -> NdbProfile? {
|
func lookup_profile(_ pubkey: Pubkey) -> ProfileRecord? {
|
||||||
return pubkey.id.withUnsafeBytes { pk_bytes in
|
return pubkey.id.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> ProfileRecord? in
|
||||||
var size: Int = 0
|
var size: Int = 0
|
||||||
guard let profile_p = ndb_get_profile_by_pubkey(ndb.ndb, pk_bytes, &size) else {
|
|
||||||
|
guard let baseAddress = ptr.baseAddress,
|
||||||
|
let profile_p = ndb_get_profile_by_pubkey(ndb.ndb, baseAddress, &size)
|
||||||
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf = ByteBuffer(assumingMemoryBound: profile_p, capacity: size)
|
do {
|
||||||
return NdbProfile(buf, o: 0)
|
var buf = ByteBuffer(assumingMemoryBound: profile_p, capacity: size)
|
||||||
|
let rec: NdbProfileRecord = try getDebugCheckedRoot(byteBuffer: &buf)
|
||||||
|
return ProfileRecord(data: rec)
|
||||||
|
} catch {
|
||||||
|
// Handle error appropriately
|
||||||
|
print("UNUSUAL: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func process_event(_ str: String) -> Bool {
|
||||||
|
return str.withCString { cstr in
|
||||||
|
return ndb_process_event(ndb.ndb, cstr, Int32(str.utf8.count)) != 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,3 +103,13 @@ class Ndb {
|
|||||||
ndb_destroy(ndb.ndb)
|
ndb_destroy(ndb.ndb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
func getDebugCheckedRoot<T: FlatBufferObject & Verifiable>(byteBuffer: inout ByteBuffer) throws -> T {
|
||||||
|
return try getCheckedRoot(byteBuffer: &byteBuffer)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
func getDebugCheckedRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) throws -> T {
|
||||||
|
return try getRoot(byteBuffer: &byteBuffer)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ final class NdbTests: XCTestCase {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_profile_creation() {
|
||||||
|
let profile = make_test_profile()
|
||||||
|
XCTAssertEqual(profile.name, "jb55")
|
||||||
|
}
|
||||||
|
|
||||||
func test_ndb_init() {
|
func test_ndb_init() {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -54,7 +59,8 @@ final class NdbTests: XCTestCase {
|
|||||||
XCTAssertNotNil(profile)
|
XCTAssertNotNil(profile)
|
||||||
guard let profile else { return }
|
guard let profile else { return }
|
||||||
|
|
||||||
XCTAssertEqual(profile.name, "jb55")
|
XCTAssertEqual(profile.profile?.name, "jb55")
|
||||||
|
XCTAssertEqual(profile.lnurl, "fixme")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +77,7 @@ final class NdbTests: XCTestCase {
|
|||||||
XCTAssertEqual(note.id, id)
|
XCTAssertEqual(note.id, id)
|
||||||
XCTAssertEqual(note.pubkey, pubkey)
|
XCTAssertEqual(note.pubkey, pubkey)
|
||||||
|
|
||||||
XCTAssertEqual(note.count, 34322)
|
XCTAssertEqual(note.count, 34328)
|
||||||
XCTAssertEqual(note.kind, 3)
|
XCTAssertEqual(note.kind, 3)
|
||||||
XCTAssertEqual(note.created_at, 1689904312)
|
XCTAssertEqual(note.created_at, 1689904312)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user