notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

accounts.rs (15410B)


      1 use uuid::Uuid;
      2 
      3 use crate::account::cache::AccountCache;
      4 use crate::account::contacts::Contacts;
      5 use crate::account::mute::AccountMutedData;
      6 use crate::account::relay::{
      7     modify_advertised_relays, update_relay_configuration, AccountRelayData, RelayAction,
      8     RelayDefaults,
      9 };
     10 use crate::storage::AccountStorageWriter;
     11 use crate::user_account::UserAccountSerializable;
     12 use crate::{
     13     AccountStorage, MuteFun, SingleUnkIdAction, UnifiedSubscription, UnknownIds, UserAccount,
     14     ZapWallet,
     15 };
     16 use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool};
     17 use nostrdb::{Ndb, Note, Transaction};
     18 
     19 // TODO: remove this
     20 use std::sync::Arc;
     21 
     22 /// The interface for managing the user's accounts.
     23 /// Represents all user-facing operations related to account management.
     24 pub struct Accounts {
     25     pub cache: AccountCache,
     26     storage_writer: Option<AccountStorageWriter>,
     27     relay_defaults: RelayDefaults,
     28     subs: AccountSubs,
     29 }
     30 
     31 impl Accounts {
     32     #[allow(clippy::too_many_arguments)]
     33     pub fn new(
     34         key_store: Option<AccountStorage>,
     35         forced_relays: Vec<String>,
     36         fallback: Pubkey,
     37         ndb: &mut Ndb,
     38         txn: &Transaction,
     39         pool: &mut RelayPool,
     40         ctx: &egui::Context,
     41         unknown_ids: &mut UnknownIds,
     42     ) -> Self {
     43         let (mut cache, unknown_id) = AccountCache::new(UserAccount::new(
     44             Keypair::only_pubkey(fallback),
     45             AccountData::new(fallback.bytes()),
     46         ));
     47 
     48         unknown_id.process_action(unknown_ids, ndb, txn);
     49 
     50         let mut storage_writer = None;
     51         if let Some(keystore) = key_store {
     52             let (reader, writer) = keystore.rw();
     53             match reader.get_accounts() {
     54                 Ok(accounts) => {
     55                     for account in accounts {
     56                         add_account_from_storage(&mut cache, account).process_action(
     57                             unknown_ids,
     58                             ndb,
     59                             txn,
     60                         )
     61                     }
     62                 }
     63                 Err(e) => {
     64                     tracing::error!("could not get keys: {e}");
     65                 }
     66             }
     67             if let Some(selected) = reader.get_selected_key().ok().flatten() {
     68                 cache.select(selected);
     69             }
     70 
     71             storage_writer = Some(writer);
     72         };
     73 
     74         let relay_defaults = RelayDefaults::new(forced_relays);
     75 
     76         let selected = cache.selected_mut();
     77         let selected_data = &mut selected.data;
     78 
     79         selected_data.query(ndb, txn);
     80 
     81         let subs = {
     82             AccountSubs::new(
     83                 ndb,
     84                 pool,
     85                 &relay_defaults,
     86                 &selected.key.pubkey,
     87                 selected_data,
     88                 create_wakeup(ctx),
     89             )
     90         };
     91 
     92         Accounts {
     93             cache,
     94             storage_writer,
     95             relay_defaults,
     96             subs,
     97         }
     98     }
     99 
    100     pub fn remove_account(
    101         &mut self,
    102         pk: &Pubkey,
    103         ndb: &mut Ndb,
    104         pool: &mut RelayPool,
    105         ctx: &egui::Context,
    106     ) -> bool {
    107         let Some(resp) = self.cache.remove(pk) else {
    108             return false;
    109         };
    110 
    111         if pk != self.cache.fallback() {
    112             if let Some(key_store) = &self.storage_writer {
    113                 if let Err(e) = key_store.remove_key(&resp.deleted) {
    114                     tracing::error!("Could not remove account {pk}: {e}");
    115                 }
    116             }
    117         }
    118 
    119         if let Some(swap_to) = resp.swap_to {
    120             let txn = Transaction::new(ndb).expect("txn");
    121             self.select_account_internal(&swap_to, ndb, &txn, pool, ctx);
    122         }
    123 
    124         true
    125     }
    126 
    127     pub fn contains_full_kp(&self, pubkey: &enostr::Pubkey) -> bool {
    128         self.cache
    129             .get(pubkey)
    130             .is_some_and(|u| u.key.secret_key.is_some())
    131     }
    132 
    133     #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
    134     pub fn add_account(&mut self, kp: Keypair) -> Option<AddAccountResponse> {
    135         let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) {
    136             if kp.secret_key.is_none() || acc.key.secret_key.is_some() {
    137                 tracing::info!("Already have account, not adding");
    138                 return None;
    139             }
    140 
    141             acc.key = kp.clone();
    142             AccType::Acc(&*acc)
    143         } else {
    144             let new_account_data = AccountData::new(kp.pubkey.bytes());
    145             AccType::Entry(
    146                 self.cache
    147                     .add(UserAccount::new(kp.clone(), new_account_data)),
    148             )
    149         };
    150 
    151         if let Some(key_store) = &self.storage_writer {
    152             if let Err(e) = key_store.write_account(&acc.get_acc().into()) {
    153                 tracing::error!("Could not add key for {:?}: {e}", kp.pubkey);
    154             }
    155         }
    156 
    157         Some(AddAccountResponse {
    158             switch_to: kp.pubkey,
    159             unk_id_action: SingleUnkIdAction::pubkey(kp.pubkey),
    160         })
    161     }
    162 
    163     /// Update the `UserAccount` via callback and save the result to disk.
    164     /// return true if the update was successful
    165     pub fn update_current_account(&mut self, update: impl FnOnce(&mut UserAccount)) -> bool {
    166         let cur_account = self.get_selected_account_mut();
    167 
    168         update(cur_account);
    169 
    170         let cur_acc = self.get_selected_account();
    171 
    172         let Some(key_store) = &self.storage_writer else {
    173             return false;
    174         };
    175 
    176         if let Err(err) = key_store.write_account(&cur_acc.into()) {
    177             tracing::error!("Could not add account {:?} to storage: {err}", cur_acc.key);
    178             return false;
    179         }
    180 
    181         true
    182     }
    183 
    184     pub fn selected_filled(&self) -> Option<FilledKeypair<'_>> {
    185         self.get_selected_account().key.to_full()
    186     }
    187 
    188     /// Get the selected account's pubkey as bytes. Common operation so
    189     /// we make it a helper here.
    190     pub fn selected_account_pubkey_bytes(&self) -> &[u8; 32] {
    191         self.get_selected_account().key.pubkey.bytes()
    192     }
    193 
    194     pub fn selected_account_pubkey(&self) -> &Pubkey {
    195         &self.get_selected_account().key.pubkey
    196     }
    197 
    198     pub fn get_selected_account(&self) -> &UserAccount {
    199         self.cache.selected()
    200     }
    201 
    202     pub fn selected_account_has_wallet(&self) -> bool {
    203         self.get_selected_account().wallet.is_some()
    204     }
    205 
    206     fn get_selected_account_mut(&mut self) -> &mut UserAccount {
    207         self.cache.selected_mut()
    208     }
    209 
    210     pub fn get_selected_wallet(&self) -> Option<&ZapWallet> {
    211         self.cache.selected().wallet.as_ref()
    212     }
    213 
    214     pub fn get_selected_wallet_mut(&mut self) -> Option<&mut ZapWallet> {
    215         self.cache.selected_mut().wallet.as_mut()
    216     }
    217 
    218     fn get_selected_account_data(&self) -> &AccountData {
    219         &self.cache.selected().data
    220     }
    221 
    222     pub fn select_account(
    223         &mut self,
    224         pk_to_select: &Pubkey,
    225         ndb: &mut Ndb,
    226         txn: &Transaction,
    227         pool: &mut RelayPool,
    228         ctx: &egui::Context,
    229     ) {
    230         if !self.cache.select(*pk_to_select) {
    231             return;
    232         }
    233 
    234         self.select_account_internal(pk_to_select, ndb, txn, pool, ctx);
    235     }
    236 
    237     /// Have already selected in `AccountCache`, updating other things
    238     fn select_account_internal(
    239         &mut self,
    240         pk_to_select: &Pubkey,
    241         ndb: &mut Ndb,
    242         txn: &Transaction,
    243         pool: &mut RelayPool,
    244         ctx: &egui::Context,
    245     ) {
    246         if let Some(key_store) = &self.storage_writer {
    247             if let Err(e) = key_store.select_key(Some(*pk_to_select)) {
    248                 tracing::error!("Could not select key {:?}: {e}", pk_to_select);
    249             }
    250         }
    251 
    252         self.get_selected_account_mut().data.query(ndb, txn);
    253         self.subs.swap_to(
    254             ndb,
    255             pool,
    256             &self.relay_defaults,
    257             pk_to_select,
    258             &self.cache.selected().data,
    259             create_wakeup(ctx),
    260         );
    261     }
    262 
    263     pub fn mutefun(&self) -> Box<MuteFun> {
    264         let account_data = self.get_selected_account_data();
    265 
    266         let muted = Arc::clone(&account_data.muted.muted);
    267         Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread))
    268     }
    269 
    270     pub fn send_initial_filters(&mut self, pool: &mut RelayPool, relay_url: &str) {
    271         let data = &self.get_selected_account().data;
    272         // send the active account's relay list subscription
    273         pool.send_to(
    274             &ClientMessage::req(
    275                 self.subs.relay.remote.clone(),
    276                 vec![data.relay.filter.clone()],
    277             ),
    278             relay_url,
    279         );
    280         // send the active account's muted subscription
    281         pool.send_to(
    282             &ClientMessage::req(
    283                 self.subs.mute.remote.clone(),
    284                 vec![data.muted.filter.clone()],
    285             ),
    286             relay_url,
    287         );
    288         pool.send_to(
    289             &ClientMessage::req(
    290                 self.subs.contacts.remote.clone(),
    291                 vec![data.contacts.filter.clone()],
    292             ),
    293             relay_url,
    294         );
    295     }
    296 
    297     pub fn update(&mut self, ndb: &mut Ndb, pool: &mut RelayPool, ctx: &egui::Context) {
    298         // IMPORTANT - This function is called in the UI update loop,
    299         // make sure it is fast when idle
    300 
    301         let Some(update) = self
    302             .cache
    303             .selected_mut()
    304             .data
    305             .poll_for_updates(ndb, &self.subs)
    306         else {
    307             return;
    308         };
    309 
    310         match update {
    311             // If needed, update the relay configuration
    312             AccountDataUpdate::Relay => {
    313                 let acc = self.cache.selected();
    314                 update_relay_configuration(
    315                     pool,
    316                     &self.relay_defaults,
    317                     &acc.key.pubkey,
    318                     &acc.data.relay,
    319                     create_wakeup(ctx),
    320                 );
    321             }
    322         }
    323     }
    324 
    325     pub fn get_full<'a>(&'a self, pubkey: &Pubkey) -> Option<FilledKeypair<'a>> {
    326         self.cache.get(pubkey).and_then(|r| r.key.to_full())
    327     }
    328 
    329     pub fn process_relay_action(
    330         &mut self,
    331         ctx: &egui::Context,
    332         pool: &mut RelayPool,
    333         action: RelayAction,
    334     ) {
    335         let acc = self.cache.selected_mut();
    336         modify_advertised_relays(&acc.key, action, pool, &self.relay_defaults, &mut acc.data);
    337 
    338         update_relay_configuration(
    339             pool,
    340             &self.relay_defaults,
    341             &acc.key.pubkey,
    342             &acc.data.relay,
    343             create_wakeup(ctx),
    344         );
    345     }
    346 
    347     pub fn get_subs(&self) -> &AccountSubs {
    348         &self.subs
    349     }
    350 }
    351 
    352 enum AccType<'a> {
    353     Entry(hashbrown::hash_map::OccupiedEntry<'a, Pubkey, UserAccount>),
    354     Acc(&'a UserAccount),
    355 }
    356 
    357 impl<'a> AccType<'a> {
    358     fn get_acc(&'a self) -> &'a UserAccount {
    359         match self {
    360             AccType::Entry(occupied_entry) => occupied_entry.get(),
    361             AccType::Acc(user_account) => user_account,
    362         }
    363     }
    364 }
    365 
    366 fn create_wakeup(ctx: &egui::Context) -> impl Fn() + Send + Sync + Clone + 'static {
    367     let ctx = ctx.clone();
    368     move || {
    369         ctx.request_repaint();
    370     }
    371 }
    372 
    373 fn add_account_from_storage(
    374     cache: &mut AccountCache,
    375     user_account_serializable: UserAccountSerializable,
    376 ) -> SingleUnkIdAction {
    377     let Some(acc) = get_acc_from_storage(user_account_serializable) else {
    378         return SingleUnkIdAction::NoAction;
    379     };
    380 
    381     let pk = acc.key.pubkey;
    382     cache.add(acc);
    383 
    384     SingleUnkIdAction::pubkey(pk)
    385 }
    386 
    387 fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> Option<UserAccount> {
    388     let keypair = user_account_serializable.key;
    389     let new_account_data = AccountData::new(keypair.pubkey.bytes());
    390 
    391     let mut wallet = None;
    392     if let Some(wallet_s) = user_account_serializable.wallet {
    393         let m_wallet: Result<crate::ZapWallet, crate::Error> = wallet_s.into();
    394         match m_wallet {
    395             Ok(w) => wallet = Some(w),
    396             Err(e) => {
    397                 tracing::error!("Problem creating wallet from disk: {e}");
    398             }
    399         };
    400     }
    401 
    402     Some(UserAccount {
    403         key: keypair,
    404         wallet,
    405         data: new_account_data,
    406     })
    407 }
    408 
    409 #[derive(Clone)]
    410 pub struct AccountData {
    411     pub(crate) relay: AccountRelayData,
    412     pub(crate) muted: AccountMutedData,
    413     pub contacts: Contacts,
    414 }
    415 
    416 impl AccountData {
    417     pub fn new(pubkey: &[u8; 32]) -> Self {
    418         Self {
    419             relay: AccountRelayData::new(pubkey),
    420             muted: AccountMutedData::new(pubkey),
    421             contacts: Contacts::new(pubkey),
    422         }
    423     }
    424 
    425     pub(super) fn poll_for_updates(
    426         &mut self,
    427         ndb: &Ndb,
    428         subs: &AccountSubs,
    429     ) -> Option<AccountDataUpdate> {
    430         let txn = Transaction::new(ndb).expect("txn");
    431         let mut resp = None;
    432         if self.relay.poll_for_updates(ndb, &txn, subs.relay.local) {
    433             resp = Some(AccountDataUpdate::Relay);
    434         }
    435 
    436         self.muted.poll_for_updates(ndb, &txn, subs.mute.local);
    437         self.contacts
    438             .poll_for_updates(ndb, &txn, subs.contacts.local);
    439 
    440         resp
    441     }
    442 
    443     /// Note: query should be called as close to the subscription as possible
    444     pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
    445         self.relay.query(ndb, txn);
    446         self.muted.query(ndb, txn);
    447         self.contacts.query(ndb, txn);
    448     }
    449 }
    450 
    451 pub(super) enum AccountDataUpdate {
    452     Relay,
    453 }
    454 
    455 pub struct AddAccountResponse {
    456     pub switch_to: Pubkey,
    457     pub unk_id_action: SingleUnkIdAction,
    458 }
    459 
    460 pub struct AccountSubs {
    461     relay: UnifiedSubscription,
    462     mute: UnifiedSubscription,
    463     pub contacts: UnifiedSubscription,
    464 }
    465 
    466 impl AccountSubs {
    467     pub(super) fn new(
    468         ndb: &mut Ndb,
    469         pool: &mut RelayPool,
    470         relay_defaults: &RelayDefaults,
    471         pk: &Pubkey,
    472         data: &AccountData,
    473         wakeup: impl Fn() + Send + Sync + Clone + 'static,
    474     ) -> Self {
    475         let relay = subscribe(ndb, pool, &data.relay.filter);
    476         let mute = subscribe(ndb, pool, &data.muted.filter);
    477         let contacts = subscribe(ndb, pool, &data.contacts.filter);
    478         update_relay_configuration(pool, relay_defaults, pk, &data.relay, wakeup);
    479 
    480         Self {
    481             relay,
    482             mute,
    483             contacts,
    484         }
    485     }
    486 
    487     pub(super) fn swap_to(
    488         &mut self,
    489         ndb: &mut Ndb,
    490         pool: &mut RelayPool,
    491         relay_defaults: &RelayDefaults,
    492         pk: &Pubkey,
    493         new_selection_data: &AccountData,
    494         wakeup: impl Fn() + Send + Sync + Clone + 'static,
    495     ) {
    496         unsubscribe(ndb, pool, &self.relay);
    497         unsubscribe(ndb, pool, &self.mute);
    498         unsubscribe(ndb, pool, &self.contacts);
    499 
    500         *self = AccountSubs::new(ndb, pool, relay_defaults, pk, new_selection_data, wakeup);
    501     }
    502 }
    503 
    504 fn subscribe(ndb: &Ndb, pool: &mut RelayPool, filter: &nostrdb::Filter) -> UnifiedSubscription {
    505     let filters = vec![filter.clone()];
    506     let sub = ndb
    507         .subscribe(&filters)
    508         .expect("ndb relay list subscription");
    509 
    510     // remote subscription
    511     let subid = Uuid::new_v4().to_string();
    512     pool.subscribe(subid.clone(), filters);
    513 
    514     UnifiedSubscription {
    515         local: sub,
    516         remote: subid,
    517     }
    518 }
    519 
    520 fn unsubscribe(ndb: &mut Ndb, pool: &mut RelayPool, sub: &UnifiedSubscription) {
    521     pool.unsubscribe(sub.remote.clone());
    522 
    523     // local subscription
    524     ndb.unsubscribe(sub.local)
    525         .expect("ndb relay list unsubscribe");
    526 }