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 }