damus

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

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