commit 11edde45f434160634e54e3b195a0e2d34ae3ade
parent 329385bd900d98899d9608d5142d63e2153cf1ea
Author: kernelkind <kernelkind@gmail.com>
Date: Sun, 29 Jun 2025 16:39:41 -0400
split `AccountStorage` into reader & writer
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
4 files changed, 83 insertions(+), 48 deletions(-)
diff --git 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<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);
}
diff --git 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,56 +21,53 @@ 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 get_accounts(&self) -> Result<Vec<UserAccountSerializable>> {
- let keys = self
- .accounts_directory
- .get_files()?
- .values()
- .filter_map(|serialized| deserialize_storage(serialized).ok())
- .collect();
- 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
- .selected_key_directory
- .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned())
- {
- Ok(pubkey_str) => Ok(Some(serde_json::from_str(&pubkey_str)?)),
- Err(crate::Error::Io(_)) => Ok(None),
- Err(e) => Err(e),
- }
+ 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.selected_key_directory.file_path,
+ &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.selected_key_directory.file_path,
+ &self.storage.selected_key_directory.file_path,
SELECTED_PUBKEY_FILE_NAME.to_owned(),
)?)
} else {
@@ -79,6 +76,39 @@ impl AccountStorage {
}
}
+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()
+ .filter_map(|serialized| deserialize_storage(serialized).ok())
+ .collect();
+ Ok(keys)
+ }
+
+ pub fn get_selected_key(&self) -> Result<Option<Pubkey>> {
+ match self
+ .storage
+ .selected_key_directory
+ .get_file(SELECTED_PUBKEY_FILE_NAME.to_owned())
+ {
+ Ok(pubkey_str) => Ok(Some(serde_json::from_str(&pubkey_str)?)),
+ Err(crate::Error::Io(_)) => Ok(None),
+ Err(e) => Err(e),
+ }
+ }
+}
+
fn deserialize_storage(serialized: &str) -> Result<UserAccountSerializable> {
let data = serialized.split("\t").collect::<Vec<&str>>();
let mut parser = TokenParser::new(&data);
@@ -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() {
diff --git 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
@@ -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};