split AccountStorage into reader & writer

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-06-29 16:39:41 -04:00
parent 329385bd90
commit 11edde45f4
4 changed files with 85 additions and 50 deletions

View File

@@ -3,6 +3,7 @@ use tracing::{debug, info};
use crate::account::cache::AccountCache; use crate::account::cache::AccountCache;
use crate::account::mute::AccountMutedData; use crate::account::mute::AccountMutedData;
use crate::account::relay::{AccountRelayData, RelayDefaults}; use crate::account::relay::{AccountRelayData, RelayDefaults};
use crate::storage::AccountStorageWriter;
use crate::user_account::UserAccountSerializable; use crate::user_account::UserAccountSerializable;
use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount}; use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount};
use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool};
@@ -16,7 +17,7 @@ use std::sync::Arc;
/// Represents all user-facing operations related to account management. /// Represents all user-facing operations related to account management.
pub struct Accounts { pub struct Accounts {
pub cache: AccountCache, pub cache: AccountCache,
key_store: Option<AccountStorage>, storage_writer: Option<AccountStorageWriter>,
relay_defaults: RelayDefaults, relay_defaults: RelayDefaults,
needs_relay_config: bool, needs_relay_config: bool,
} }
@@ -40,8 +41,10 @@ impl Accounts {
unknown_id.process_action(unknown_ids, ndb, txn); unknown_id.process_action(unknown_ids, ndb, txn);
if let Some(keystore) = &key_store { let mut storage_writer = None;
match keystore.get_accounts() { if let Some(keystore) = key_store {
let (reader, writer) = keystore.rw();
match reader.get_accounts() {
Ok(accounts) => { Ok(accounts) => {
for account in accounts { for account in accounts {
add_account_from_storage(&mut cache, ndb, txn, account).process_action( add_account_from_storage(&mut cache, ndb, txn, account).process_action(
@@ -55,16 +58,18 @@ impl Accounts {
tracing::error!("could not get keys: {e}"); 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); cache.select(selected);
} }
storage_writer = Some(writer);
}; };
let relay_defaults = RelayDefaults::new(forced_relays); let relay_defaults = RelayDefaults::new(forced_relays);
Accounts { Accounts {
cache, cache,
key_store, storage_writer,
relay_defaults, relay_defaults,
needs_relay_config: true, needs_relay_config: true,
} }
@@ -75,7 +80,7 @@ impl Accounts {
return; 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) { if let Err(e) = key_store.remove_key(&removed.key) {
tracing::error!("Could not remove account {pk}: {e}"); 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()) { if let Err(e) = key_store.write_account(&acc.get_acc().into()) {
tracing::error!("Could not add key for {:?}: {e}", kp.pubkey); tracing::error!("Could not add key for {:?}: {e}", kp.pubkey);
} }
@@ -139,7 +144,7 @@ impl Accounts {
let cur_acc = self.get_selected_account(); 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; return false;
}; };
@@ -190,7 +195,7 @@ impl Accounts {
return; 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)) { if let Err(e) = key_store.select_key(Some(*pk)) {
tracing::error!("Could not select key {:?}: {e}", pk); tracing::error!("Could not select key {:?}: {e}", pk);
} }

View File

@@ -7,7 +7,7 @@ use super::file_storage::{delete_file, write_file, Directory};
static SELECTED_PUBKEY_FILE_NAME: &str = "selected_pubkey"; static SELECTED_PUBKEY_FILE_NAME: &str = "selected_pubkey";
/// An OS agnostic file key storage implementation /// An OS agnostic file key storage implementation
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct AccountStorage { pub struct AccountStorage {
accounts_directory: Directory, accounts_directory: Directory,
selected_key_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<()> { pub fn write_account(&self, account: &UserAccountSerializable) -> Result<()> {
let mut writer = TokenWriter::new("\t"); let mut writer = TokenWriter::new("\t");
account.serialize_tokens(&mut writer); account.serialize_tokens(&mut writer);
write_file( write_file(
&self.accounts_directory.file_path, &self.storage.accounts_directory.file_path,
account.key.pubkey.hex(), account.key.pubkey.hex(),
writer.str(), 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<Pubkey>) -> 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<Vec<UserAccountSerializable>> { pub fn get_accounts(&self) -> Result<Vec<UserAccountSerializable>> {
let keys = self let keys = self
.storage
.accounts_directory .accounts_directory
.get_files()? .get_files()?
.values() .values()
@@ -41,12 +96,9 @@ impl AccountStorage {
Ok(keys) 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<Option<Pubkey>> { pub fn get_selected_key(&self) -> Result<Option<Pubkey>> {
match self match self
.storage
.selected_key_directory .selected_key_directory
.get_file(SELECTED_PUBKEY_FILE_NAME.to_owned()) .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned())
{ {
@@ -55,28 +107,6 @@ impl AccountStorage {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
pub fn select_key(&self, pubkey: Option<Pubkey>) -> 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<UserAccountSerializable> { fn deserialize_storage(serialized: &str) -> Result<UserAccountSerializable> {
@@ -119,14 +149,14 @@ mod tests {
#[test] #[test]
fn test_basic() { fn test_basic() {
let kp = enostr::FullKeypair::generate().to_keypair(); let kp = enostr::FullKeypair::generate().to_keypair();
let storage = AccountStorage::mock().unwrap(); let (reader, writer) = AccountStorage::mock().unwrap().rw();
let resp = storage.write_account(&UserAccountSerializable::new(kp.clone())); let resp = writer.write_account(&UserAccountSerializable::new(kp.clone()));
assert!(resp.is_ok()); 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!(writer.remove_key(&kp).is_ok());
assert_num_storage(&storage.get_accounts(), 0); assert_num_storage(&reader.get_accounts(), 0);
} }
fn assert_num_storage(keys_response: &Result<Vec<UserAccountSerializable>>, n: usize) { fn assert_num_storage(keys_response: &Result<Vec<UserAccountSerializable>>, n: usize) {
@@ -144,21 +174,21 @@ mod tests {
fn test_select_key() { fn test_select_key() {
let kp = enostr::FullKeypair::generate().to_keypair(); let kp = enostr::FullKeypair::generate().to_keypair();
let storage = AccountStorage::mock().unwrap(); let (reader, writer) = AccountStorage::mock().unwrap().rw();
let _ = storage.write_account(&UserAccountSerializable::new(kp.clone())); let _ = writer.write_account(&UserAccountSerializable::new(kp.clone()));
assert_num_storage(&storage.get_accounts(), 1); 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()); assert!(resp.is_ok());
let resp = storage.get_selected_key(); let resp = reader.get_selected_key();
assert!(resp.is_ok()); assert!(resp.is_ok());
} }
#[test] #[test]
fn test_get_selected_key_when_no_file() { 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 // Should return Ok(None) when no key has been selected
match storage.get_selected_key() { match storage.get_selected_key() {

View File

@@ -59,7 +59,7 @@ pub enum DataPathType {
Cache, Cache,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct Directory { pub struct Directory {
pub file_path: PathBuf, pub file_path: PathBuf,
} }

View File

@@ -1,5 +1,5 @@
mod account_storage; mod account_storage;
mod file_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}; pub use file_storage::{delete_file, write_file, DataPath, DataPathType, Directory};