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