damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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