close #12 Implement config commands
This commit is contained in:
parent
f495af68fc
commit
ab05bd10cd
13 changed files with 149 additions and 65 deletions
|
@ -45,9 +45,9 @@ impl TryFrom<PartialConfig> for Config {
|
|||
type Error = crate::error::Error;
|
||||
fn try_from(value: PartialConfig) -> Result<Self, Self::Error> {
|
||||
Ok(Self{
|
||||
rpc: value.rpc.try_into()?,
|
||||
p2p: value.p2p.try_into()?,
|
||||
storage: value.storage.try_into()?
|
||||
rpc: value.rpc.ok_or(crate::error::Error::MissingConfig("rpc"))?.try_into()?,
|
||||
p2p: value.p2p.ok_or(crate::error::Error::MissingConfig("p2p"))?.try_into()?,
|
||||
storage: value.storage.ok_or(crate::error::Error::MissingConfig("storage"))?.try_into()?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -56,19 +56,19 @@ impl TryFrom<PartialConfig> for Config {
|
|||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct PartialConfig {
|
||||
#[cfg_attr(feature="desktop", command(flatten))]
|
||||
pub p2p: PartialP2pConfig,
|
||||
pub p2p: Option<PartialP2pConfig>,
|
||||
#[cfg_attr(feature="desktop", command(flatten))]
|
||||
pub storage: PartialStorageConfig,
|
||||
pub storage: Option<PartialStorageConfig>,
|
||||
#[cfg_attr(feature="desktop", command(flatten))]
|
||||
pub rpc: PartialRpcConfig,
|
||||
pub rpc: Option<PartialRpcConfig>,
|
||||
}
|
||||
|
||||
impl PartialConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
p2p : PartialP2pConfig::empty().with_new_secret(),
|
||||
storage: PartialStorageConfig::empty(),
|
||||
rpc: PartialRpcConfig::empty(),
|
||||
p2p : Some(PartialP2pConfig::empty().with_new_private_key()),
|
||||
storage: Some(PartialStorageConfig::empty()),
|
||||
rpc: Some(PartialRpcConfig::empty()),
|
||||
}
|
||||
}
|
||||
pub fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
||||
|
@ -90,6 +90,12 @@ impl PartialConfig {
|
|||
where
|
||||
T: AsRef<Path>
|
||||
{
|
||||
if !path.as_ref().exists() {
|
||||
if let Some(x) = path.as_ref().parent() {
|
||||
std::fs::create_dir_all(x)?;
|
||||
};
|
||||
let _ = File::create(&path).await?;
|
||||
}
|
||||
let mut file = File::open(path.as_ref()).await?;
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content).await?;
|
||||
|
@ -113,9 +119,19 @@ impl PartialConfig {
|
|||
#[cfg(not(any(target_os="android", target_os="ios")))]
|
||||
pub fn default_desktop(app_name: &'static str) -> Self {
|
||||
Self {
|
||||
p2p: PartialP2pConfig::default(),
|
||||
rpc: PartialRpcConfig::default(),
|
||||
storage: PartialStorageConfig::default(app_name),
|
||||
p2p: Some(PartialP2pConfig::default()),
|
||||
rpc: Some(PartialRpcConfig::default()),
|
||||
storage: Some(PartialStorageConfig::default(app_name)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Config> for PartialConfig {
|
||||
fn from(value: Config) -> Self {
|
||||
Self {
|
||||
p2p: Some(value.p2p.into()),
|
||||
storage: Some(value.storage.into()),
|
||||
rpc: Some(value.rpc.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,9 +139,9 @@ impl PartialConfig {
|
|||
impl Emptiable for PartialConfig {
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
p2p: PartialP2pConfig::empty(),
|
||||
storage: PartialStorageConfig::empty(),
|
||||
rpc: PartialRpcConfig::empty()
|
||||
p2p: None,
|
||||
storage: None,
|
||||
rpc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,14 +33,14 @@ fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct P2pConfig {
|
||||
pub secret: Keypair,
|
||||
pub private_key: Keypair,
|
||||
pub listen_ips: Vec<IpAddr>,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl P2pConfig {
|
||||
async fn try_into_swarm (self) -> Result<Swarm<p2p::Behaviour>, Error> {
|
||||
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret)
|
||||
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.private_key)
|
||||
.with_tokio()
|
||||
.with_tcp(
|
||||
tcp::Config::default(),
|
||||
|
@ -74,7 +74,7 @@ impl TryFrom<PartialP2pConfig> for P2pConfig {
|
|||
type Error = crate::error::Error;
|
||||
fn try_from(raw: PartialP2pConfig) -> Result<P2pConfig, Self::Error> {
|
||||
Ok(P2pConfig {
|
||||
secret: base64_to_keypair(&raw.secret.ok_or(Error::MissingConfig("secret"))?)?,
|
||||
private_key: base64_to_keypair(&raw.private_key.ok_or(Error::MissingConfig("secret"))?)?,
|
||||
listen_ips: raw.listen_ips.ok_or(Error::MissingConfig("listen_ips"))?,
|
||||
port: raw.port.ok_or(Error::MissingConfig("port"))?
|
||||
})
|
||||
|
@ -85,23 +85,26 @@ impl TryFrom<PartialP2pConfig> for P2pConfig {
|
|||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct PartialP2pConfig {
|
||||
#[cfg_attr(feature="desktop",arg(long))]
|
||||
pub secret: Option<String>,
|
||||
pub private_key: Option<String>,
|
||||
#[cfg_attr(feature="desktop",arg(long))]
|
||||
pub listen_ips: Option<Vec<IpAddr>>,
|
||||
#[cfg_attr(feature="desktop",arg(long))]
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
impl PartialP2pConfig {
|
||||
pub fn with_new_secret(mut self) -> Self {
|
||||
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
||||
pub fn with_new_private_key(mut self) -> Self {
|
||||
self.private_key = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
||||
self
|
||||
}
|
||||
pub fn init_private_key(&mut self) {
|
||||
let _ = self.private_key.insert(keypair_to_base64(&Keypair::generate_ed25519()));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2pConfig> for PartialP2pConfig {
|
||||
fn from(config: P2pConfig) -> Self {
|
||||
Self {
|
||||
secret: Some(keypair_to_base64(&config.secret)),
|
||||
private_key: Some(keypair_to_base64(&config.private_key)),
|
||||
listen_ips: Some(config.listen_ips),
|
||||
port: Some(config.port)
|
||||
}
|
||||
|
@ -111,7 +114,7 @@ impl From<P2pConfig> for PartialP2pConfig {
|
|||
impl Default for PartialP2pConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
secret: None,
|
||||
private_key: None,
|
||||
listen_ips: Some(Vec::from(DEFAULT_P2P_LISTEN_IPS)),
|
||||
port: Some(DEFAULT_P2P_PORT),
|
||||
}
|
||||
|
@ -121,21 +124,21 @@ impl Default for PartialP2pConfig {
|
|||
impl Emptiable for PartialP2pConfig {
|
||||
fn empty() -> Self {
|
||||
Self{
|
||||
secret: None,
|
||||
private_key: None,
|
||||
listen_ips: None,
|
||||
port: None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none()
|
||||
self.private_key.is_none() && self.listen_ips.is_none() && self.port.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mergeable for PartialP2pConfig {
|
||||
fn merge(&mut self, mut other: Self) {
|
||||
if let Some(x) = other.secret.take() {
|
||||
let _ = self.secret.insert(x);
|
||||
if let Some(x) = other.private_key.take() {
|
||||
let _ = self.private_key.insert(x);
|
||||
};
|
||||
if let Some(x) = other.listen_ips.take() {
|
||||
let _ = self.listen_ips.insert(x);
|
||||
|
@ -145,6 +148,20 @@ impl Mergeable for PartialP2pConfig {
|
|||
};
|
||||
}
|
||||
}
|
||||
impl Mergeable for Option<PartialP2pConfig> {
|
||||
fn merge(&mut self, mut other: Self) {
|
||||
match other.take() {
|
||||
Some(x) => {
|
||||
if let Some(y) = self.as_mut() {
|
||||
y.merge(x);
|
||||
} else {
|
||||
let _ = self.insert(x);
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -63,4 +63,19 @@ impl Mergeable for PartialRpcConfig {
|
|||
self.socket_path = Some(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mergeable for Option<PartialRpcConfig> {
|
||||
fn merge(&mut self, mut other: Self) {
|
||||
match other.take() {
|
||||
Some(x) => {
|
||||
if let Some(y) = self.as_mut() {
|
||||
y.merge(x);
|
||||
} else {
|
||||
let _ = self.insert(x);
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -108,4 +108,19 @@ impl Mergeable for PartialStorageConfig {
|
|||
let _ = self.cache_directory.insert(x);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Mergeable for Option<PartialStorageConfig> {
|
||||
fn merge(&mut self, mut other: Self) {
|
||||
match other.take() {
|
||||
Some(x) => {
|
||||
if let Some(y) = self.as_mut() {
|
||||
y.merge(x);
|
||||
} else {
|
||||
let _ = self.insert(x);
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -45,14 +45,14 @@ impl GlobalDatabaseConnections {
|
|||
where
|
||||
T: AsRef<StorageConfig>
|
||||
{
|
||||
config.as_ref().data_directory.join("data.db")
|
||||
config.as_ref().data_directory.join("data.sqlite")
|
||||
}
|
||||
|
||||
fn get_cache_file_path<T>(config: &T) -> PathBuf
|
||||
where
|
||||
T: AsRef<StorageConfig>
|
||||
{
|
||||
config.as_ref().cache_directory.join("cache.db")
|
||||
config.as_ref().cache_directory.join("cache.sqlite")
|
||||
}
|
||||
|
||||
fn get_url_unchecked<T>(path: T) -> String
|
||||
|
|
|
@ -14,7 +14,7 @@ pub static TEST_CONFIG: LazyLock<Config> = LazyLock::new(|| {
|
|||
|
||||
|
||||
Config {
|
||||
p2p: PartialP2pConfig::default().with_new_secret().try_into().unwrap(),
|
||||
p2p: PartialP2pConfig::default().with_new_private_key().try_into().unwrap(),
|
||||
storage: StorageConfig {
|
||||
data_directory: data_dir,
|
||||
cache_directory: cache_dir,
|
||||
|
|
|
@ -2,15 +2,4 @@
|
|||
pub use caretta_macros::Mergeable;
|
||||
pub trait Mergeable: Sized {
|
||||
fn merge(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl<T> Mergeable for Option<T> {
|
||||
fn merge(&mut self, mut other: Self) {
|
||||
match other.take() {
|
||||
Some(x) => {
|
||||
let _ = self.insert(x);
|
||||
},
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -3,43 +3,67 @@ use std::{net::IpAddr, path::PathBuf, sync::LazyLock};
|
|||
use clap::Args;
|
||||
use caretta_core::{
|
||||
config::{Config, ConfigError, PartialConfig, PartialP2pConfig, PartialStorageConfig},
|
||||
utils::mergeable::Mergeable
|
||||
utils::{emptiable::Emptiable, mergeable::Mergeable}
|
||||
};
|
||||
|
||||
use libp2p::identity::Keypair;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct ConfigArgs {
|
||||
#[arg(short = 'c', long = "config")]
|
||||
pub file_path: Option<PathBuf>,
|
||||
#[arg(skip)]
|
||||
pub file_content: Option<PartialConfig>,
|
||||
pub file_content: OnceCell<PartialConfig>,
|
||||
#[command(flatten)]
|
||||
pub args: PartialConfig,
|
||||
}
|
||||
|
||||
|
||||
impl ConfigArgs {
|
||||
pub fn get_file_path_or_default(&self, app_name: &'static str) -> PathBuf {
|
||||
fn get_file_path_or_default(&self, app_name: &'static str) -> PathBuf {
|
||||
self.file_path.clone().unwrap_or(
|
||||
dirs::config_local_dir()
|
||||
.unwrap()
|
||||
.expect("Config user directory should be set")
|
||||
.join(app_name)
|
||||
.join(app_name.to_string() + ".conf")
|
||||
.join("config.toml")
|
||||
)
|
||||
}
|
||||
pub async fn get_or_read_file_content(&mut self, app_name: &'static str) -> PartialConfig {
|
||||
self.file_content.get_or_insert(
|
||||
PartialConfig::read_from(self.get_file_path_or_default(app_name)).await.unwrap()
|
||||
).clone()
|
||||
async fn get_or_read_file_content(&self, app_name: &'static str) -> PartialConfig {
|
||||
self.file_content.get_or_init(|| async {
|
||||
PartialConfig::read_from(self.get_file_path_or_default(app_name)).await.expect("Config file should be invalid!")
|
||||
}).await.clone()
|
||||
}
|
||||
pub async fn into_config_unchecked(mut self, app_name: &'static str) -> Config {
|
||||
pub async fn to_partial_config_with_default(&self, app_name: &'static str) -> PartialConfig {
|
||||
let mut default = PartialConfig::default_desktop(app_name);
|
||||
let file_content = self.get_or_read_file_content(app_name).await;
|
||||
let args = self.args;
|
||||
default.merge(file_content);
|
||||
default.merge(args);
|
||||
default.try_into().unwrap()
|
||||
|
||||
default.merge(self.to_partial_config_without_default(app_name).await);
|
||||
default
|
||||
}
|
||||
pub async fn to_partial_config_without_default(&self, app_name: &'static str) -> PartialConfig {
|
||||
let mut file_content = self.get_or_read_file_content(app_name).await;
|
||||
let args = self.args.clone();
|
||||
file_content.merge(args);
|
||||
file_content
|
||||
}
|
||||
async fn has_p2p_private_key(&self, app_name: &'static str) -> bool {
|
||||
let merged = self.to_partial_config_with_default(app_name).await;
|
||||
match merged.p2p {
|
||||
Some(p2p) => p2p.private_key.is_some(),
|
||||
None => false
|
||||
}
|
||||
}
|
||||
pub async fn into_config(mut self, app_name: &'static str) -> Config {
|
||||
if !self.has_p2p_private_key(app_name).await {
|
||||
let path = self.get_file_path_or_default(app_name);
|
||||
let mut content = self.file_content.get_mut().unwrap();
|
||||
if let Some(p2p) = content.p2p.as_mut() {
|
||||
p2p.init_private_key();
|
||||
} else {
|
||||
content.p2p.insert(PartialP2pConfig::empty().with_new_private_key());
|
||||
}
|
||||
content.write_to(path).await.expect("Config file should be writable first time to initialize secret");
|
||||
}
|
||||
self.to_partial_config_with_default(app_name).await.try_into().expect("Some configurations are missing!")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ pub struct ConfigCheckCommandArgs{
|
|||
|
||||
impl Runnable for ConfigCheckCommandArgs {
|
||||
async fn run(self, app_name: &'static str) {
|
||||
todo!()
|
||||
let _ = self.config.into_config(app_name).await;
|
||||
println!("Ok");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use clap::Args;
|
||||
use caretta_core::utils::runnable::Runnable;
|
||||
use caretta_core::{config::PartialConfig, utils::runnable::Runnable};
|
||||
use crate::cli::ConfigArgs;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
|
@ -12,6 +12,12 @@ pub struct ConfigListCommandArgs{
|
|||
|
||||
impl Runnable for ConfigListCommandArgs {
|
||||
async fn run(self, app_name: &'static str) {
|
||||
todo!()
|
||||
let config: PartialConfig = if self.all {
|
||||
self.config.into_config(app_name).await.into()
|
||||
} else {
|
||||
self.config.to_partial_config_without_default(app_name).await
|
||||
};
|
||||
println!("{}", config.into_toml().unwrap())
|
||||
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ impl ServerTrait for Server {
|
|||
where
|
||||
T: AsRef<P2pConfig>
|
||||
{
|
||||
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(config.as_ref().secret.clone())
|
||||
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(config.as_ref().private_key.clone())
|
||||
.with_tokio()
|
||||
.with_tcp(
|
||||
tcp::Config::default(),
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct ServerCommandArgs {
|
|||
}
|
||||
impl Runnable for ServerCommandArgs {
|
||||
async fn run(self, app_name: &'static str) {
|
||||
let config = CONFIG.get_or_init::<Config>(self.config.into_config_unchecked(app_name).await).await;
|
||||
let config = CONFIG.get_or_init::<Config>(self.config.into_config(app_name).await).await;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use caretta::utils::runnable::Runnable;
|
||||
use caretta_example_core::global::APP_NAME;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
@ -8,6 +10,5 @@ mod ipc;
|
|||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Cli::parse();
|
||||
|
||||
|
||||
args.run(APP_NAME).await;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue