onboarding: lookup profile after accounts are added

To reduce the side effects of this change, we introduce a new UnknownId
action type:

  - SingleUnkIdAction

This can be returned from functions to signal that we need to do some
work to look for things. We add a `must_use` directive to this type
to ensure callers handle it.

Changelog-Fixed: Fix missing profiles when new accounts are added
Fixes: https://github.com/damus-io/notedeck/issues/356
This commit is contained in:
William Casarin
2024-11-18 17:06:52 -08:00
parent 3b3b8246c8
commit 19933c84f1
6 changed files with 146 additions and 27 deletions

View File

@@ -14,6 +14,7 @@ use crate::{
account_login_view::{AccountLoginResponse, AccountLoginView}, account_login_view::{AccountLoginResponse, AccountLoginView},
account_management::{AccountsView, AccountsViewResponse}, account_management::{AccountsView, AccountsViewResponse},
}, },
unknowns::SingleUnkIdAction,
}; };
use tracing::{error, info}; use tracing::{error, info};
@@ -50,7 +51,7 @@ pub fn render_accounts_route(
accounts: &mut AccountManager, accounts: &mut AccountManager,
login_state: &mut AcquireKeyState, login_state: &mut AcquireKeyState,
route: AccountsRoute, route: AccountsRoute,
) { ) -> SingleUnkIdAction {
let router = columns.column_mut(col).router_mut(); let router = columns.column_mut(col).router_mut();
let resp = match route { let resp = match route {
AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache) AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache)
@@ -68,13 +69,17 @@ pub fn render_accounts_route(
match resp { match resp {
AccountsRouteResponse::Accounts(response) => { AccountsRouteResponse::Accounts(response) => {
process_accounts_view_response(accounts, response, router); process_accounts_view_response(accounts, response, router);
SingleUnkIdAction::no_action()
} }
AccountsRouteResponse::AddAccount(response) => { AccountsRouteResponse::AddAccount(response) => {
process_login_view_response(accounts, response); let action = process_login_view_response(accounts, response);
*login_state = Default::default(); *login_state = Default::default();
router.go_back(); router.go_back();
action
} }
} }
} else {
SingleUnkIdAction::no_action()
} }
} }
@@ -153,14 +158,17 @@ impl AccountManager {
false false
} }
pub fn add_account(&mut self, account: Keypair) -> bool { #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
pub fn add_account(&mut self, account: Keypair) -> SingleUnkIdAction {
if self.has_account_pubkey(account.pubkey.bytes()) { if self.has_account_pubkey(account.pubkey.bytes()) {
info!("already have account, not adding {}", account.pubkey); info!("already have account, not adding {}", account.pubkey);
return false; return SingleUnkIdAction::pubkey(account.pubkey);
} }
let _ = self.key_store.add_key(&account); let _ = self.key_store.add_key(&account);
let pk = account.pubkey;
self.accounts.push(account); self.accounts.push(account);
true SingleUnkIdAction::pubkey(pk)
} }
pub fn num_accounts(&self) -> usize { pub fn num_accounts(&self) -> usize {
@@ -215,14 +223,16 @@ fn get_selected_index(accounts: &[UserAccount], keystore: &KeyStorageType) -> Op
None None
} }
pub fn process_login_view_response(manager: &mut AccountManager, response: AccountLoginResponse) { pub fn process_login_view_response(
match response { manager: &mut AccountManager,
response: AccountLoginResponse,
) -> SingleUnkIdAction {
let r = match response {
AccountLoginResponse::CreateNew => { AccountLoginResponse::CreateNew => {
manager.add_account(FullKeypair::generate().to_keypair()); manager.add_account(FullKeypair::generate().to_keypair())
} }
AccountLoginResponse::LoginWith(keypair) => { AccountLoginResponse::LoginWith(keypair) => manager.add_account(keypair),
manager.add_account(keypair); };
}
}
manager.select_account(manager.num_accounts() - 1); manager.select_account(manager.num_accounts() - 1);
r
} }

View File

@@ -419,9 +419,17 @@ impl Damus {
let num_keys = parsed_args.keys.len(); let num_keys = parsed_args.keys.len();
for key in parsed_args.keys { let mut unknown_ids = UnknownIds::default();
info!("adding account: {}", key.pubkey); let ndb = Ndb::new(&dbpath_str, &config).expect("ndb");
accounts.add_account(key);
{
let txn = Transaction::new(&ndb).expect("txn");
for key in parsed_args.keys {
info!("adding account: {}", key.pubkey);
accounts
.add_account(key)
.process_action(&mut unknown_ids, &ndb, &txn);
}
} }
if num_keys != 0 { if num_keys != 0 {
@@ -454,7 +462,6 @@ impl Damus {
.get_selected_account() .get_selected_account()
.as_ref() .as_ref()
.map(|a| a.pubkey.bytes()); .map(|a| a.pubkey.bytes());
let ndb = Ndb::new(&dbpath_str, &config).expect("ndb");
let mut columns = if parsed_args.columns.is_empty() { let mut columns = if parsed_args.columns.is_empty() {
if let Some(serializable_columns) = storage::load_columns(&path) { if let Some(serializable_columns) = storage::load_columns(&path) {
@@ -491,7 +498,7 @@ impl Damus {
Self { Self {
pool, pool,
debug, debug,
unknown_ids: UnknownIds::default(), unknown_ids,
subscriptions: Subscriptions::default(), subscriptions: Subscriptions::default(),
since_optimize: parsed_args.since_optimize, since_optimize: parsed_args.since_optimize,
threads: NotesHolderStorage::default(), threads: NotesHolderStorage::default(),

View File

@@ -84,7 +84,7 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> Option<Rend
ui, ui,
), ),
Route::Accounts(amr) => { Route::Accounts(amr) => {
render_accounts_route( let action = render_accounts_route(
ui, ui,
&app.ndb, &app.ndb,
col, col,
@@ -94,6 +94,8 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> Option<Rend
&mut app.view_state.login, &mut app.view_state.login,
*amr, *amr,
); );
let txn = Transaction::new(&app.ndb).expect("txn");
action.process_action(&mut app.unknown_ids, &app.ndb, &txn);
None None
} }
Route::Relays => { Route::Relays => {

View File

@@ -6,7 +6,7 @@ use tracing::{debug, info, warn};
use crate::{ use crate::{
actionbar::NotesHolderResult, multi_subscriber::MultiSubscriber, note::NoteRef, actionbar::NotesHolderResult, multi_subscriber::MultiSubscriber, note::NoteRef,
notecache::NoteCache, timeline::TimelineTab, Error, Result, notecache::NoteCache, timeline::TimelineTab, unknowns::NoteRefsUnkIdAction, Error, Result,
}; };
pub struct NotesHolderStorage<M: NotesHolder> { pub struct NotesHolderStorage<M: NotesHolder> {
@@ -109,19 +109,21 @@ pub trait NotesHolder {
notes: Vec<NoteRef>, notes: Vec<NoteRef>,
) -> Self; ) -> Self;
#[must_use = "UnknownIds::update_from_note_refs should be used on this result"] fn poll_notes_into_view(
fn poll_notes_into_view(&mut self, txn: &Transaction, ndb: &Ndb) -> Result<()> { &mut self,
txn: &Transaction,
ndb: &Ndb,
) -> Result<NoteRefsUnkIdAction> {
if let Some(multi_subscriber) = self.get_multi_subscriber() { if let Some(multi_subscriber) = self.get_multi_subscriber() {
let reversed = true; let reversed = true;
let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?; let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?;
self.get_view().insert(&note_refs, reversed); self.get_view().insert(&note_refs, reversed);
Ok(NoteRefsUnkIdAction::new(note_refs))
} else { } else {
return Err(Error::Generic( Err(Error::Generic(
"NotesHolder unexpectedly has no MultiSubscriber".to_owned(), "NotesHolder unexpectedly has no MultiSubscriber".to_owned(),
)); ))
} }
Ok(())
} }
/// Look for new thread notes since our last fetch /// Look for new thread notes since our last fetch

View File

@@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use enostr::{FullKeypair, Pubkey, RelayPool}; use enostr::{FullKeypair, Pubkey, RelayPool};
use nostrdb::ProfileRecord; use nostrdb::{ProfileRecord, Transaction};
use crate::{user_account::UserAccount, Damus}; use crate::{user_account::UserAccount, Damus};
@@ -100,8 +100,11 @@ pub fn test_app() -> Damus {
let mut app = Damus::mock(path); let mut app = Damus::mock(path);
let accounts = get_test_accounts(); let accounts = get_test_accounts();
let txn = Transaction::new(&app.ndb).expect("txn");
for account in accounts { for account in accounts {
app.accounts_mut().add_account(account); app.accounts_mut()
.add_account(account)
.process_action(&mut app.unknown_ids, &app.ndb, &txn)
} }
app app

View File

@@ -12,6 +12,74 @@ use std::collections::HashSet;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tracing::error; use tracing::error;
#[must_use = "process_action should be used on this result"]
pub enum SingleUnkIdAction {
NoAction,
NeedsProcess(UnknownId),
}
#[must_use = "process_action should be used on this result"]
pub enum NoteRefsUnkIdAction {
NoAction,
NeedsProcess(Vec<NoteRef>),
}
impl NoteRefsUnkIdAction {
pub fn new(refs: Vec<NoteRef>) -> Self {
NoteRefsUnkIdAction::NeedsProcess(refs)
}
pub fn no_action() -> Self {
Self::NoAction
}
pub fn process_action(
&self,
txn: &Transaction,
ndb: &Ndb,
unk_ids: &mut UnknownIds,
note_cache: &mut NoteCache,
) {
match self {
Self::NoAction => {}
Self::NeedsProcess(refs) => {
UnknownIds::update_from_note_refs(txn, ndb, unk_ids, note_cache, refs);
}
}
}
}
impl SingleUnkIdAction {
pub fn new(id: UnknownId) -> Self {
SingleUnkIdAction::NeedsProcess(id)
}
pub fn no_action() -> Self {
Self::NoAction
}
pub fn pubkey(pubkey: Pubkey) -> Self {
SingleUnkIdAction::new(UnknownId::Pubkey(pubkey))
}
pub fn note_id(note_id: NoteId) -> Self {
SingleUnkIdAction::new(UnknownId::Id(note_id))
}
/// Some functions may return unknown id actions that need to be processed.
/// For example, when we add a new account we need to make sure we have the
/// profile for that account. This function ensures we add this to the
/// unknown id tracker without adding side effects to functions.
pub fn process_action(&self, ids: &mut UnknownIds, ndb: &Ndb, txn: &Transaction) {
match self {
Self::NeedsProcess(id) => {
ids.add_unknown_id_if_missing(ndb, txn, id);
}
Self::NoAction => {}
}
}
}
/// Unknown Id searcher /// Unknown Id searcher
#[derive(Default)] #[derive(Default)]
pub struct UnknownIds { pub struct UnknownIds {
@@ -121,6 +189,33 @@ impl UnknownIds {
} }
} }
pub fn add_unknown_id_if_missing(&mut self, ndb: &Ndb, txn: &Transaction, unk_id: &UnknownId) {
match unk_id {
UnknownId::Pubkey(pk) => self.add_pubkey_if_missing(ndb, txn, pk),
UnknownId::Id(note_id) => self.add_note_id_if_missing(ndb, txn, note_id),
}
}
pub fn add_pubkey_if_missing(&mut self, ndb: &Ndb, txn: &Transaction, pubkey: &Pubkey) {
// we already have this profile, skip
if ndb.get_profile_by_pubkey(txn, pubkey).is_ok() {
return;
}
self.ids.insert(UnknownId::Pubkey(*pubkey));
self.mark_updated();
}
pub fn add_note_id_if_missing(&mut self, ndb: &Ndb, txn: &Transaction, note_id: &NoteId) {
// we already have this note, skip
if ndb.get_note_by_id(txn, note_id.bytes()).is_ok() {
return;
}
self.ids.insert(UnknownId::Id(*note_id));
self.mark_updated();
}
pub fn update( pub fn update(
txn: &Transaction, txn: &Transaction,
unknown_ids: &mut UnknownIds, unknown_ids: &mut UnknownIds,