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::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<AccountStorage>,
storage_writer: Option<AccountStorageWriter>,
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);
}

View File

@@ -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<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>> {
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<Option<Pubkey>> {
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<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> {
@@ -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<Vec<UserAccountSerializable>>, 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() {

View File

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

View File

@@ -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};