split AccountStorage into reader & writer
Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
Reference in New Issue
Block a user