From 11edde45f434160634e54e3b195a0e2d34ae3ade Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 29 Jun 2025 16:39:41 -0400 Subject: [PATCH] split `AccountStorage` into reader & writer Signed-off-by: kernelkind --- crates/notedeck/src/account/accounts.rs | 23 ++-- .../notedeck/src/storage/account_storage.rs | 108 +++++++++++------- crates/notedeck/src/storage/file_storage.rs | 2 +- crates/notedeck/src/storage/mod.rs | 2 +- 4 files changed, 85 insertions(+), 50 deletions(-) diff --git a/crates/notedeck/src/account/accounts.rs b/crates/notedeck/src/account/accounts.rs index ae4e1c09..7b1f69b0 100644 --- a/crates/notedeck/src/account/accounts.rs +++ b/crates/notedeck/src/account/accounts.rs @@ -3,6 +3,7 @@ use tracing::{debug, info}; use crate::account::cache::AccountCache; use crate::account::mute::AccountMutedData; use crate::account::relay::{AccountRelayData, RelayDefaults}; +use crate::storage::AccountStorageWriter; use crate::user_account::UserAccountSerializable; use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount}; use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; @@ -16,7 +17,7 @@ use std::sync::Arc; /// Represents all user-facing operations related to account management. pub struct Accounts { pub cache: AccountCache, - key_store: Option, + storage_writer: Option, relay_defaults: RelayDefaults, needs_relay_config: bool, } @@ -40,8 +41,10 @@ impl Accounts { unknown_id.process_action(unknown_ids, ndb, txn); - if let Some(keystore) = &key_store { - match keystore.get_accounts() { + let mut storage_writer = None; + if let Some(keystore) = key_store { + let (reader, writer) = keystore.rw(); + match reader.get_accounts() { Ok(accounts) => { for account in accounts { add_account_from_storage(&mut cache, ndb, txn, account).process_action( @@ -55,16 +58,18 @@ impl Accounts { tracing::error!("could not get keys: {e}"); } } - if let Some(selected) = keystore.get_selected_key().ok().flatten() { + if let Some(selected) = reader.get_selected_key().ok().flatten() { cache.select(selected); } + + storage_writer = Some(writer); }; let relay_defaults = RelayDefaults::new(forced_relays); Accounts { cache, - key_store, + storage_writer, relay_defaults, needs_relay_config: true, } @@ -75,7 +80,7 @@ impl Accounts { return; }; - if let Some(key_store) = &self.key_store { + if let Some(key_store) = &self.storage_writer { if let Err(e) = key_store.remove_key(&removed.key) { tracing::error!("Could not remove account {pk}: {e}"); } @@ -118,7 +123,7 @@ impl Accounts { ) }; - if let Some(key_store) = &self.key_store { + if let Some(key_store) = &self.storage_writer { if let Err(e) = key_store.write_account(&acc.get_acc().into()) { tracing::error!("Could not add key for {:?}: {e}", kp.pubkey); } @@ -139,7 +144,7 @@ impl Accounts { let cur_acc = self.get_selected_account(); - let Some(key_store) = &self.key_store else { + let Some(key_store) = &self.storage_writer else { return false; }; @@ -190,7 +195,7 @@ impl Accounts { return; } - if let Some(key_store) = &self.key_store { + if let Some(key_store) = &self.storage_writer { if let Err(e) = key_store.select_key(Some(*pk)) { tracing::error!("Could not select key {:?}: {e}", pk); } diff --git a/crates/notedeck/src/storage/account_storage.rs b/crates/notedeck/src/storage/account_storage.rs index d300960d..f8962a16 100644 --- a/crates/notedeck/src/storage/account_storage.rs +++ b/crates/notedeck/src/storage/account_storage.rs @@ -7,7 +7,7 @@ use super::file_storage::{delete_file, write_file, Directory}; static SELECTED_PUBKEY_FILE_NAME: &str = "selected_pubkey"; /// An OS agnostic file key storage implementation -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct AccountStorage { accounts_directory: Directory, selected_key_directory: Directory, @@ -21,18 +21,73 @@ impl AccountStorage { } } + pub fn rw(self) -> (AccountStorageReader, AccountStorageWriter) { + ( + AccountStorageReader::new(self.clone()), + AccountStorageWriter::new(self), + ) + } +} + +pub struct AccountStorageWriter { + storage: AccountStorage, +} + +impl AccountStorageWriter { + pub fn new(storage: AccountStorage) -> Self { + Self { storage } + } + pub fn write_account(&self, account: &UserAccountSerializable) -> Result<()> { let mut writer = TokenWriter::new("\t"); account.serialize_tokens(&mut writer); write_file( - &self.accounts_directory.file_path, + &self.storage.accounts_directory.file_path, account.key.pubkey.hex(), writer.str(), ) } + pub fn remove_key(&self, key: &Keypair) -> Result<()> { + delete_file(&self.storage.accounts_directory.file_path, key.pubkey.hex()) + } + + pub fn select_key(&self, pubkey: Option) -> Result<()> { + if let Some(pubkey) = pubkey { + write_file( + &self.storage.selected_key_directory.file_path, + SELECTED_PUBKEY_FILE_NAME.to_owned(), + &serde_json::to_string(&pubkey.hex())?, + ) + } else if self + .storage + .selected_key_directory + .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned()) + .is_ok() + { + // Case where user chose to have no selected pubkey, but one already exists + Ok(delete_file( + &self.storage.selected_key_directory.file_path, + SELECTED_PUBKEY_FILE_NAME.to_owned(), + )?) + } else { + Ok(()) + } + } +} + +pub struct AccountStorageReader { + storage: AccountStorage, +} + +impl AccountStorageReader { + pub fn new(storage: AccountStorage) -> Self { + Self { storage } + } + pub fn get_accounts(&self) -> Result> { let keys = self + .storage .accounts_directory .get_files()? .values() @@ -41,12 +96,9 @@ impl AccountStorage { Ok(keys) } - pub fn remove_key(&self, key: &Keypair) -> Result<()> { - delete_file(&self.accounts_directory.file_path, key.pubkey.hex()) - } - pub fn get_selected_key(&self) -> Result> { match self + .storage .selected_key_directory .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned()) { @@ -55,28 +107,6 @@ impl AccountStorage { Err(e) => Err(e), } } - - pub fn select_key(&self, pubkey: Option) -> Result<()> { - if let Some(pubkey) = pubkey { - write_file( - &self.selected_key_directory.file_path, - SELECTED_PUBKEY_FILE_NAME.to_owned(), - &serde_json::to_string(&pubkey.hex())?, - ) - } else if self - .selected_key_directory - .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned()) - .is_ok() - { - // Case where user chose to have no selected pubkey, but one already exists - Ok(delete_file( - &self.selected_key_directory.file_path, - SELECTED_PUBKEY_FILE_NAME.to_owned(), - )?) - } else { - Ok(()) - } - } } fn deserialize_storage(serialized: &str) -> Result { @@ -119,14 +149,14 @@ mod tests { #[test] fn test_basic() { let kp = enostr::FullKeypair::generate().to_keypair(); - let storage = AccountStorage::mock().unwrap(); - let resp = storage.write_account(&UserAccountSerializable::new(kp.clone())); + let (reader, writer) = AccountStorage::mock().unwrap().rw(); + let resp = writer.write_account(&UserAccountSerializable::new(kp.clone())); assert!(resp.is_ok()); - assert_num_storage(&storage.get_accounts(), 1); + assert_num_storage(&reader.get_accounts(), 1); - assert!(storage.remove_key(&kp).is_ok()); - assert_num_storage(&storage.get_accounts(), 0); + assert!(writer.remove_key(&kp).is_ok()); + assert_num_storage(&reader.get_accounts(), 0); } fn assert_num_storage(keys_response: &Result>, n: usize) { @@ -144,21 +174,21 @@ mod tests { fn test_select_key() { let kp = enostr::FullKeypair::generate().to_keypair(); - let storage = AccountStorage::mock().unwrap(); - let _ = storage.write_account(&UserAccountSerializable::new(kp.clone())); - assert_num_storage(&storage.get_accounts(), 1); + let (reader, writer) = AccountStorage::mock().unwrap().rw(); + let _ = writer.write_account(&UserAccountSerializable::new(kp.clone())); + assert_num_storage(&reader.get_accounts(), 1); - let resp = storage.select_key(Some(kp.pubkey)); + let resp = writer.select_key(Some(kp.pubkey)); assert!(resp.is_ok()); - let resp = storage.get_selected_key(); + let resp = reader.get_selected_key(); assert!(resp.is_ok()); } #[test] fn test_get_selected_key_when_no_file() { - let storage = AccountStorage::mock().unwrap(); + let storage = AccountStorage::mock().unwrap().rw().0; // Should return Ok(None) when no key has been selected match storage.get_selected_key() { diff --git a/crates/notedeck/src/storage/file_storage.rs b/crates/notedeck/src/storage/file_storage.rs index 37bdc2dc..91de258d 100644 --- a/crates/notedeck/src/storage/file_storage.rs +++ b/crates/notedeck/src/storage/file_storage.rs @@ -59,7 +59,7 @@ pub enum DataPathType { Cache, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Directory { pub file_path: PathBuf, } diff --git a/crates/notedeck/src/storage/mod.rs b/crates/notedeck/src/storage/mod.rs index 81fbe3ec..839b8be6 100644 --- a/crates/notedeck/src/storage/mod.rs +++ b/crates/notedeck/src/storage/mod.rs @@ -1,5 +1,5 @@ mod account_storage; mod file_storage; -pub use account_storage::AccountStorage; +pub use account_storage::{AccountStorage, AccountStorageReader, AccountStorageWriter}; pub use file_storage::{delete_file, write_file, DataPath, DataPathType, Directory};