diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs index 6cd818b..10bcd22 100644 --- a/core/src/config/mod.rs +++ b/core/src/config/mod.rs @@ -45,9 +45,9 @@ impl TryFrom for Config { type Error = crate::error::Error; fn try_from(value: PartialConfig) -> Result { 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 for Config { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct PartialConfig { #[cfg_attr(feature="desktop", command(flatten))] - pub p2p: PartialP2pConfig, + pub p2p: Option, #[cfg_attr(feature="desktop", command(flatten))] - pub storage: PartialStorageConfig, + pub storage: Option, #[cfg_attr(feature="desktop", command(flatten))] - pub rpc: PartialRpcConfig, + pub rpc: Option, } 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 { @@ -90,6 +90,12 @@ impl PartialConfig { where T: AsRef { + 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 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, } } diff --git a/core/src/config/p2p.rs b/core/src/config/p2p.rs index 9c216f5..9fd0e91 100644 --- a/core/src/config/p2p.rs +++ b/core/src/config/p2p.rs @@ -33,14 +33,14 @@ fn base64_to_keypair(base64: &str) -> Result { #[derive(Clone, Debug)] pub struct P2pConfig { - pub secret: Keypair, + pub private_key: Keypair, pub listen_ips: Vec, pub port: u16, } impl P2pConfig { async fn try_into_swarm (self) -> Result, 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 for P2pConfig { type Error = crate::error::Error; fn try_from(raw: PartialP2pConfig) -> Result { 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 for P2pConfig { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct PartialP2pConfig { #[cfg_attr(feature="desktop",arg(long))] - pub secret: Option, + pub private_key: Option, #[cfg_attr(feature="desktop",arg(long))] pub listen_ips: Option>, #[cfg_attr(feature="desktop",arg(long))] pub port: Option, } 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 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 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 { + 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)] diff --git a/core/src/config/rpc.rs b/core/src/config/rpc.rs index 2e390b0..7787348 100644 --- a/core/src/config/rpc.rs +++ b/core/src/config/rpc.rs @@ -63,4 +63,19 @@ impl Mergeable for PartialRpcConfig { self.socket_path = Some(x); } } +} + +impl Mergeable for Option { + 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 => {} + }; + } } \ No newline at end of file diff --git a/core/src/config/storage.rs b/core/src/config/storage.rs index 6b13c8e..62460e1 100644 --- a/core/src/config/storage.rs +++ b/core/src/config/storage.rs @@ -108,4 +108,19 @@ impl Mergeable for PartialStorageConfig { let _ = self.cache_directory.insert(x); }; } +} + +impl Mergeable for Option { + 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 => {} + }; + } } \ No newline at end of file diff --git a/core/src/global/database_connection.rs b/core/src/global/database_connection.rs index c622714..eca41ba 100644 --- a/core/src/global/database_connection.rs +++ b/core/src/global/database_connection.rs @@ -45,14 +45,14 @@ impl GlobalDatabaseConnections { where T: AsRef { - config.as_ref().data_directory.join("data.db") + config.as_ref().data_directory.join("data.sqlite") } fn get_cache_file_path(config: &T) -> PathBuf where T: AsRef { - config.as_ref().cache_directory.join("cache.db") + config.as_ref().cache_directory.join("cache.sqlite") } fn get_url_unchecked(path: T) -> String diff --git a/core/src/tests.rs b/core/src/tests.rs index 29e0624..cefd43c 100644 --- a/core/src/tests.rs +++ b/core/src/tests.rs @@ -14,7 +14,7 @@ pub static TEST_CONFIG: LazyLock = 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, diff --git a/core/src/utils/mergeable.rs b/core/src/utils/mergeable.rs index 8fb52a6..3a9bfcc 100644 --- a/core/src/utils/mergeable.rs +++ b/core/src/utils/mergeable.rs @@ -2,15 +2,4 @@ pub use caretta_macros::Mergeable; pub trait Mergeable: Sized { fn merge(&mut self, other: Self); -} - -impl Mergeable for Option { - fn merge(&mut self, mut other: Self) { - match other.take() { - Some(x) => { - let _ = self.insert(x); - }, - None => {} - }; - } } \ No newline at end of file diff --git a/desktop/src/cli/args/config.rs b/desktop/src/cli/args/config.rs index 90c69f2..f7ea178 100644 --- a/desktop/src/cli/args/config.rs +++ b/desktop/src/cli/args/config.rs @@ -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, #[arg(skip)] - pub file_content: Option, + pub file_content: OnceCell, #[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!") } -} +} \ No newline at end of file diff --git a/desktop/src/cli/config/check.rs b/desktop/src/cli/config/check.rs index 63c86ed..bd4c94d 100644 --- a/desktop/src/cli/config/check.rs +++ b/desktop/src/cli/config/check.rs @@ -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"); } } \ No newline at end of file diff --git a/desktop/src/cli/config/list.rs b/desktop/src/cli/config/list.rs index 732836b..5cce721 100644 --- a/desktop/src/cli/config/list.rs +++ b/desktop/src/cli/config/list.rs @@ -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()) + } } \ No newline at end of file diff --git a/examples/core/src/server.rs b/examples/core/src/server.rs index 8aa5762..25ddf35 100644 --- a/examples/core/src/server.rs +++ b/examples/core/src/server.rs @@ -7,7 +7,7 @@ impl ServerTrait for Server { where T: AsRef { - 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(), diff --git a/examples/desktop/src/cli/server.rs b/examples/desktop/src/cli/server.rs index 2cfc5ac..b031b82 100644 --- a/examples/desktop/src/cli/server.rs +++ b/examples/desktop/src/cli/server.rs @@ -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::(self.config.into_config_unchecked(app_name).await).await; + let config = CONFIG.get_or_init::(self.config.into_config(app_name).await).await; } } \ No newline at end of file diff --git a/examples/desktop/src/main.rs b/examples/desktop/src/main.rs index 82a3a84..3162608 100644 --- a/examples/desktop/src/main.rs +++ b/examples/desktop/src/main.rs @@ -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; }