SearchHomeModel.swift (5894B)
1 // 2 // SearchHomeModel.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-06-06. 6 // 7 8 import Foundation 9 10 11 /// The data model for the SearchHome view, typically something global-like 12 class SearchHomeModel: ObservableObject { 13 var events: EventHolder 14 @Published var loading: Bool = false 15 16 var seen_pubkey: Set<Pubkey> = Set() 17 let damus_state: DamusState 18 let base_subid = UUID().description 19 let profiles_subid = UUID().description 20 let limit: UInt32 = 500 21 //let multiple_events_per_pubkey: Bool = false 22 23 init(damus_state: DamusState) { 24 self.damus_state = damus_state 25 self.events = EventHolder(on_queue: { ev in 26 preload_events(state: damus_state, events: [ev]) 27 }) 28 } 29 30 func get_base_filter() -> NostrFilter { 31 var filter = NostrFilter(kinds: [.text, .chat]) 32 filter.limit = self.limit 33 filter.until = UInt32(Date.now.timeIntervalSince1970) 34 return filter 35 } 36 37 func filter_muted() { 38 events.filter { should_show_event(state: damus_state, ev: $0) } 39 self.objectWillChange.send() 40 } 41 42 func subscribe() { 43 loading = true 44 let to_relays = determine_to_relays(pool: damus_state.pool, filters: damus_state.relay_filters) 45 damus_state.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays) 46 } 47 48 func unsubscribe(to: RelayURL? = nil) { 49 loading = false 50 damus_state.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] }) 51 } 52 53 func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) { 54 guard case .nostr_event(let event) = conn_ev else { 55 return 56 } 57 58 switch event { 59 case .event(let sub_id, let ev): 60 guard sub_id == self.base_subid || sub_id == self.profiles_subid else { 61 return 62 } 63 if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply(damus_state.keypair) 64 { 65 if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) { 66 return 67 } 68 seen_pubkey.insert(ev.pubkey) 69 70 if self.events.insert(ev) { 71 self.objectWillChange.send() 72 } 73 } 74 case .notice(let msg): 75 print("search home notice: \(msg)") 76 case .ok: 77 break 78 case .eose(let sub_id): 79 loading = false 80 81 if sub_id == self.base_subid { 82 // Make sure we unsubscribe after we've fetched the global events 83 // global events are not realtime 84 unsubscribe(to: relay_id) 85 86 guard let txn = NdbTxn(ndb: damus_state.ndb) else { return } 87 load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn) 88 } 89 90 break 91 case .auth: 92 break 93 } 94 } 95 } 96 97 func find_profiles_to_fetch<Y>(profiles: Profiles, load: PubkeysToLoad, cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] { 98 switch load { 99 case .from_events(let events): 100 return find_profiles_to_fetch_from_events(profiles: profiles, events: events, cache: cache, txn: txn) 101 case .from_keys(let pks): 102 return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks, txn: txn) 103 } 104 } 105 106 func find_profiles_to_fetch_from_keys<Y>(profiles: Profiles, pks: [Pubkey], txn: NdbTxn<Y>) -> [Pubkey] { 107 Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk, txn: txn) })) 108 } 109 110 func find_profiles_to_fetch_from_events<Y>(profiles: Profiles, events: [NostrEvent], cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] { 111 var pubkeys = Set<Pubkey>() 112 113 for ev in events { 114 // lookup profiles from boosted events 115 if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey, txn: txn) { 116 pubkeys.insert(bev.pubkey) 117 } 118 119 if !profiles.has_fresh_profile(id: ev.pubkey, txn: txn) { 120 pubkeys.insert(ev.pubkey) 121 } 122 } 123 124 return Array(pubkeys) 125 } 126 127 enum PubkeysToLoad { 128 case from_events([NostrEvent]) 129 case from_keys([Pubkey]) 130 } 131 132 func load_profiles<Y>(context: String, profiles_subid: String, relay_id: RelayURL, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) { 133 let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn) 134 135 guard !authors.isEmpty else { 136 return 137 } 138 139 print("load_profiles[\(context)]: requesting \(authors.count) profiles from \(relay_id)") 140 141 let filter = NostrFilter(kinds: [.metadata], authors: authors) 142 143 damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { rid, conn_ev in 144 145 let now = UInt64(Date.now.timeIntervalSince1970) 146 switch conn_ev { 147 case .ws_event: 148 break 149 case .nostr_event(let ev): 150 guard ev.subid == profiles_subid, rid == relay_id else { return } 151 152 switch ev { 153 case .event(_, let ev): 154 if ev.known_kind == .metadata { 155 damus_state.ndb.write_profile_last_fetched(pubkey: ev.pubkey, fetched_at: now) 156 } 157 case .eose: 158 print("load_profiles[\(context)]: done loading \(authors.count) profiles from \(relay_id)") 159 damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id]) 160 case .ok: 161 break 162 case .notice: 163 break 164 case .auth: 165 break 166 } 167 } 168 169 170 } 171 } 172