Commit 8d46c378 authored by Éloïs's avatar Éloïs

[feat] durs: add opts --profiles-path & --keypairs-file

parent 82009e1e
......@@ -89,8 +89,12 @@ pub fn show_keys(key_pairs: DuniterKeyPairs) {
}
/// Save keys after a command run
pub fn save_keypairs(profile: &str, key_pairs: DuniterKeyPairs) {
let conf_keys_path = keypairs_filepath(profile);
pub fn save_keypairs(
profiles_path: &Option<PathBuf>,
profile_name: &str,
key_pairs: DuniterKeyPairs,
) {
let conf_keys_path = keypairs_filepath(profiles_path, profile_name);
write_keypairs_file(&conf_keys_path, &key_pairs).expect("could not write keypairs file");
}
......
......@@ -439,8 +439,13 @@ pub fn get_user_datas_folder() -> &'static str {
}
/// Returns the path to the folder containing the currency datas of the running profile
pub fn datas_path(profile: &str, currency: &CurrencyName) -> PathBuf {
let mut datas_path = get_profile_path(profile);
#[inline]
pub fn datas_path(
profiles_path: &Option<PathBuf>,
profile_name: &str,
currency: &CurrencyName,
) -> PathBuf {
let mut datas_path = get_profile_path(profiles_path, profile_name);
datas_path.push(currency.to_string());
if !datas_path.as_path().exists() {
fs::create_dir(datas_path.as_path()).expect("Impossible to create currency dir !");
......@@ -450,29 +455,35 @@ pub fn datas_path(profile: &str, currency: &CurrencyName) -> PathBuf {
#[inline]
/// Return path to configuration file
pub fn get_conf_path(profile: &str) -> PathBuf {
let mut conf_path = get_profile_path(profile);
pub fn get_conf_path(profile_path: &PathBuf) -> PathBuf {
let mut conf_path = profile_path.clone();
conf_path.push(constants::CONF_FILENAME);
conf_path
}
/// Returns the path to the folder containing the user data of the running profile
pub fn get_profile_path(profile: &str) -> PathBuf {
pub fn get_profile_path(profiles_path: &Option<PathBuf>, profile_name: &str) -> PathBuf {
// Define and create datas directory if not exist
let mut profile_path = match dirs::config_dir() {
Some(path) => path,
None => panic!("Impossible to get user config directory !"),
let profiles_path: PathBuf = if let Some(profiles_path) = profiles_path {
profiles_path.clone()
} else {
let mut user_config_path = match dirs::config_dir() {
Some(path) => path,
None => panic!("Impossible to get user config directory !"),
};
user_config_path.push(constants::USER_DATAS_FOLDER);
user_config_path
};
profile_path.push(constants::USER_DATAS_FOLDER);
if !profile_path.as_path().exists() {
fs::create_dir(profile_path.as_path()).unwrap_or_else(|_| {
if !profiles_path.as_path().exists() {
fs::create_dir(profiles_path.as_path()).unwrap_or_else(|_| {
panic!(
"Impossible to create ~/.config/{} dir !",
constants::USER_DATAS_FOLDER
"Impossible to create profiles directory: {:?} !",
profiles_path
)
});
}
profile_path.push(profile);
let mut profile_path = profiles_path;
profile_path.push(profile_name);
if !profile_path.as_path().exists() {
fs::create_dir(profile_path.as_path()).expect("Impossible to create your profile dir !");
}
......@@ -480,19 +491,23 @@ pub fn get_profile_path(profile: &str) -> PathBuf {
}
/// Get keypairs file path
pub fn keypairs_filepath(profile: &str) -> PathBuf {
let profile_path = get_profile_path(profile);
pub fn keypairs_filepath(profiles_path: &Option<PathBuf>, profile: &str) -> PathBuf {
let profile_path = get_profile_path(profiles_path, profile);
let mut conf_keys_path = profile_path.clone();
conf_keys_path.push(constants::KEYPAIRS_FILENAME);
conf_keys_path
}
/// Load configuration.
pub fn load_conf(profile: &str) -> (DuRsConf, DuniterKeyPairs) {
let mut profile_path = get_profile_path(profile);
pub fn load_conf(
profile: &str,
profiles_path: &Option<PathBuf>,
keypairs_file_path: &Option<PathBuf>,
) -> (DuRsConf, DuniterKeyPairs) {
let mut profile_path = get_profile_path(profiles_path, profile);
// Load conf
let (conf, keypairs) = load_conf_at_path(profile_path.clone());
let (conf, keypairs) = load_conf_at_path(profile_path.clone(), keypairs_file_path);
// Create currency dir
profile_path.push(conf.currency().to_string());
......@@ -505,10 +520,18 @@ pub fn load_conf(profile: &str) -> (DuRsConf, DuniterKeyPairs) {
}
/// Load configuration. at specified path
pub fn load_conf_at_path(profile_path: PathBuf) -> (DuRsConf, DuniterKeyPairs) {
pub fn load_conf_at_path(
profile_path: PathBuf,
keypairs_file_path: &Option<PathBuf>,
) -> (DuRsConf, DuniterKeyPairs) {
// Get KeyPairs
let mut keypairs_path = profile_path.clone();
keypairs_path.push(constants::KEYPAIRS_FILENAME);
let keypairs_path = if let Some(ref keypairs_file_path) = keypairs_file_path {
keypairs_file_path.clone()
} else {
let mut keypairs_path = profile_path.clone();
keypairs_path.push(constants::KEYPAIRS_FILENAME);
keypairs_path
};
let keypairs = if keypairs_path.as_path().exists() {
if let Ok(mut f) = File::open(keypairs_path.as_path()) {
let mut contents = String::new();
......@@ -582,8 +605,9 @@ pub fn load_conf_at_path(profile_path: PathBuf) -> (DuRsConf, DuniterKeyPairs) {
network_keypair: generate_random_keypair(KeysAlgo::Ed25519),
member_keypair: None,
};
write_keypairs_file(&keypairs_path, &keypairs)
.expect("Fatal error : fail to write default keypairs file !");
write_keypairs_file(&keypairs_path, &keypairs).unwrap_or_else(|_| {
panic!(dbg!("Fatal error : fail to write default keypairs file !"))
});
keypairs
};
......@@ -601,8 +625,9 @@ pub fn load_conf_at_path(profile_path: PathBuf) -> (DuRsConf, DuniterKeyPairs) {
let (conf, upgraded) = conf.upgrade();
// If conf is upgraded, rewrite conf file
if upgraded {
write_conf_file(conf_path.as_path(), &conf)
.expect("Fatal error : fail to write conf file !");
write_conf_file(conf_path.as_path(), &conf).unwrap_or_else(|_| {
panic!(dbg!("Fatal error : fail to write conf file !"))
});
}
conf
} else {
......@@ -615,7 +640,7 @@ pub fn load_conf_at_path(profile_path: PathBuf) -> (DuRsConf, DuniterKeyPairs) {
// Create conf file with default conf
let conf = DuRsConf::default();
write_conf_file(conf_path.as_path(), &conf)
.expect("Fatal error : fail to write default conf file !");
.unwrap_or_else(|_| panic!(dbg!("Fatal error : fail to write default conf file!")));
conf
};
......@@ -631,7 +656,7 @@ pub fn write_keypairs_file(
let mut f = File::create(file_path.as_path())?;
f.write_all(
serde_json::to_string_pretty(keypairs)
.expect("Fatal error : fail to write default keypairs file !")
.unwrap_or_else(|_| panic!(dbg!("Fatal error : fail to deserialize keypairs !")))
.as_bytes(),
)?;
f.sync_all()?;
......@@ -654,8 +679,16 @@ pub fn write_conf_file<DC: DursConfTrait>(
}
/// Returns the path to the database containing the blockchain
pub fn get_blockchain_db_path(profile: &str, currency: &CurrencyName) -> PathBuf {
let mut db_path = datas_path(profile, &currency);
pub fn get_blockchain_db_path(profile_path: PathBuf, currency: &CurrencyName) -> PathBuf {
let mut db_path = profile_path;
db_path.push(&currency.0);
if !db_path.as_path().exists() {
if let Err(io_error) = fs::create_dir(db_path.as_path()) {
if io_error.kind() != std::io::ErrorKind::AlreadyExists {
fatal_error!("Impossible to create currency dir !");
}
}
}
db_path.push("blockchain/");
if !db_path.as_path().exists() {
if let Err(io_error) = fs::create_dir(db_path.as_path()) {
......@@ -712,7 +745,7 @@ mod tests {
fn load_conf_file_v1() -> std::io::Result<()> {
let profile_path = PathBuf::from("./test/v1/");
save_old_conf(PathBuf::from(profile_path.clone()))?;
let (conf, _keys) = load_conf_at_path(profile_path.clone());
let (conf, _keys) = load_conf_at_path(profile_path.clone(), &None);
assert_eq!(
conf.modules()
.get("ws2p")
......@@ -743,7 +776,7 @@ mod tests {
#[test]
fn load_conf_file_v2() {
let profile_path = PathBuf::from("./test/v2/");
let (conf, _keys) = load_conf_at_path(profile_path);
let (conf, _keys) = load_conf_at_path(profile_path, &None);
assert_eq!(
conf.modules()
.get("ws2p")
......
......@@ -17,11 +17,12 @@
use durs_conf::ChangeGlobalConf;
use durs_module::DursConfTrait;
use std::path::PathBuf;
/// Change global configuration
pub fn change_global_conf<DC: DursConfTrait>(
profile: &str,
mut conf: DC,
profile_path: &PathBuf,
conf: &mut DC,
user_request: ChangeGlobalConf,
) {
match user_request {
......@@ -32,7 +33,7 @@ pub fn change_global_conf<DC: DursConfTrait>(
}
// Write new conf
durs_conf::write_conf_file(&durs_conf::get_conf_path(profile), &conf)
durs_conf::write_conf_file(&durs_conf::get_conf_path(profile_path), conf)
.expect("IOError : Fail to update conf ");
println!("Configuration successfully updated.");
......
......@@ -28,27 +28,34 @@ pub use crate::reset::*;
pub use crate::start::*;
pub use duniter_network::cli::sync::SyncOpt;
use log::Level;
use std::path::PathBuf;
#[derive(StructOpt, Debug)]
#[structopt(
name = "durs",
raw(setting = "structopt::clap::AppSettings::ColoredHelp")
)]
/// Rust implementation of Duniter
/// Durs command line options
pub struct DursOpt {
#[structopt(short = "p", long = "profile")]
/// Set a custom user data folder
profile_name: Option<String>,
#[structopt(short = "l", long = "logs", raw(next_line_help = "true"))]
/// CoreSubCommand
#[structopt(subcommand)]
cmd: CoreSubCommand,
/// Path where user profiles are persisted
#[structopt(long = "profiles-path", parse(from_os_str))]
profiles_path: Option<PathBuf>,
/// Keypairs file path
#[structopt(long = "keypairs-file", parse(from_os_str))]
keypairs_file: Option<PathBuf>,
/// Set log level. (Defaults to INFO).
/// Available levels: [ERROR, WARN, INFO, DEBUG, TRACE]
#[structopt(short = "l", long = "logs", raw(next_line_help = "true"))]
logs_level: Option<Level>,
#[structopt(long = "log-stdout")]
/// Print logs in standard output
#[structopt(long = "log-stdout")]
log_stdout: bool,
#[structopt(subcommand)]
/// CoreSubCommand
cmd: CoreSubCommand,
/// Set a custom user profile name
#[structopt(short = "p", long = "profile-name")]
profile_name: Option<String>,
}
#[derive(StructOpt, Debug)]
......
......@@ -54,6 +54,7 @@ use crate::cli::*;
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
use std::path::PathBuf;
use std::sync::mpsc;
use std::thread;
use structopt::clap::{App, ArgMatches};
......@@ -207,10 +208,12 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
plugins_cli_conf: vec![],
user_command: None,
soft_meta_datas: SoftwareMetaDatas {
conf: DuRsConf::default(),
profiles_path: None,
keypairs_file_path: None,
profile: String::from("default"),
soft_name,
soft_version,
profile: String::from("default"),
conf: DuRsConf::default(),
},
keypairs: None,
run_duration_in_secs,
......@@ -253,19 +256,37 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
cli_conf.get_matches()
});
let cli_args = self.cli_args.clone().expect("cli_args must be Some !");
// Get datas profile name
// Get profile name
let profile = match_profile(&cli_args);
// Get profile path
let profiles_path = match_profiles_path(&cli_args);
// Get keypairs file path
let keypairs_file_path = match_keypairs_file(&cli_args);
// Compute user profile path
let profile_path = durs_conf::get_profile_path(&profiles_path, &profile);
// Init logger
init_logger(profile.as_str(), self.soft_meta_datas.soft_name, &cli_args);
init_logger(
profile_path.clone(),
self.soft_meta_datas.soft_name,
self.soft_meta_datas.soft_version,
&cli_args,
);
// Load global conf
let (conf, keypairs) = durs_conf::load_conf(profile.as_str());
let (conf, keypairs) =
durs_conf::load_conf(profile.as_str(), &profiles_path, &keypairs_file_path);
info!("Success to load global conf.");
// save profile and conf
// Save conf and profile and keypairs file path
self.soft_meta_datas.conf = conf;
self.soft_meta_datas.profiles_path = profiles_path.clone();
self.soft_meta_datas.keypairs_file_path = keypairs_file_path;
self.soft_meta_datas.profile = profile.clone();
self.soft_meta_datas.conf = conf.clone();
// Save keypairs
self.keypairs = Some(keypairs);
......@@ -276,16 +297,16 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
if let Some(matches) = cli_args.subcommand_matches("disable") {
let opts = DisableOpt::from_clap(matches);
change_conf::change_global_conf(
&profile,
conf,
&profile_path,
&mut self.soft_meta_datas.conf,
ChangeGlobalConf::DisableModule(opts.module_name),
);
false
} else if let Some(matches) = cli_args.subcommand_matches("enable") {
let opts = EnableOpt::from_clap(matches);
change_conf::change_global_conf(
&profile,
conf,
&profile_path,
&mut self.soft_meta_datas.conf,
ChangeGlobalConf::EnableModule(opts.module_name),
);
false
......@@ -294,7 +315,12 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
self.user_command = Some(UserCommand::ListModules(ListModulesOpt::from_clap(matches)));
// Start router thread
self.router_sender = Some(router::start_router(0, profile.clone(), conf, vec![]));
self.router_sender = Some(router::start_router(
0,
profile_path.clone(),
self.soft_meta_datas.conf.clone(),
vec![],
));
true
} else if let Some(_matches) = cli_args.subcommand_matches("start") {
// Store user command
......@@ -306,8 +332,8 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
// Start router thread
self.router_sender = Some(router::start_router(
self.run_duration_in_secs,
profile.clone(),
conf,
profile_path.clone(),
self.soft_meta_datas.conf.clone(),
external_followers,
));
true
......@@ -316,7 +342,7 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
match opts.source_type {
SyncSourceType::Network => unimplemented!(),
SyncSourceType::LocalDuniter => {
sync_ts(profile.as_str(), &conf, opts);
sync_ts(profile_path.clone(), &self.soft_meta_datas.conf, opts);
}
}
......@@ -325,37 +351,37 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
let opts = DbExOpt::from_clap(matches);
match opts.subcommand {
DbExSubCommand::DistanceOpt(distance_opts) => dbex(
profile.as_str(),
&conf,
profile_path.clone(),
&self.soft_meta_datas.conf,
opts.csv,
&DBExQuery::WotQuery(DBExWotQuery::AllDistances(distance_opts.reverse)),
),
DbExSubCommand::MemberOpt(member_opts) => dbex(
profile.as_str(),
&conf,
profile_path.clone(),
&self.soft_meta_datas.conf,
opts.csv,
&DBExQuery::WotQuery(DBExWotQuery::MemberDatas(member_opts.uid)),
),
DbExSubCommand::MembersOpt(members_opts) => {
if members_opts.expire {
dbex(
profile.as_str(),
&conf,
profile_path.clone(),
&self.soft_meta_datas.conf,
opts.csv,
&DBExQuery::WotQuery(DBExWotQuery::ExpireMembers(members_opts.reverse)),
);
} else {
dbex(
profile.as_str(),
&conf,
profile_path.clone(),
&self.soft_meta_datas.conf,
opts.csv,
&DBExQuery::WotQuery(DBExWotQuery::ListMembers(members_opts.reverse)),
);
}
}
DbExSubCommand::BalanceOpt(balance_opts) => dbex(
&profile,
&conf,
profile_path.clone(),
&self.soft_meta_datas.conf,
opts.csv,
&DBExQuery::TxQuery(DBExTxQuery::Balance(balance_opts.address)),
),
......@@ -363,15 +389,7 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
false
} else if let Some(matches) = cli_args.subcommand_matches("reset") {
let opts = ResetOpt::from_clap(matches);
let mut profile_path = match dirs::config_dir() {
Some(path) => path,
None => panic!("Impossible to get user config directory !"),
};
profile_path.push(durs_conf::get_user_datas_folder());
profile_path.push(profile.clone());
if !profile_path.as_path().exists() {
panic!(format!("Error : {} profile don't exist !", profile));
}
match opts.reset_type {
ResetType::Datas => {
let mut currency_datas_path = profile_path.clone();
......@@ -399,18 +417,18 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
match opts.subcommand {
KeysSubCommand::Wizard(_wizardopt) => {
let new_keypairs = key_wizard(keypairs).unwrap();
save_keypairs(profile.as_str(), new_keypairs);
save_keypairs(&profiles_path, profile.as_str(), new_keypairs);
}
KeysSubCommand::Modify(modifyopt) => match modifyopt.subcommand {
ModifySubCommand::NetworkSaltPassword(networkopt) => {
let new_keypairs =
modify_network_keys(&networkopt.salt, &networkopt.password, keypairs);
save_keypairs(profile.as_str(), new_keypairs);
save_keypairs(&profiles_path, profile.as_str(), new_keypairs);
}
ModifySubCommand::MemberSaltPassword(memberopt) => {
let new_keypairs =
modify_member_keys(&memberopt.salt, &memberopt.password, keypairs);
save_keypairs(profile.as_str(), new_keypairs);
save_keypairs(&profiles_path, profile.as_str(), new_keypairs);
}
},
KeysSubCommand::Clear(clearopt) => {
......@@ -419,7 +437,7 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
clearopt.member || clearopt.all,
keypairs,
);
save_keypairs(profile.as_str(), new_keypairs);
save_keypairs(&profiles_path, profile.as_str(), new_keypairs);
}
KeysSubCommand::Show(_showopt) => {
show_keys(keypairs);
......@@ -482,10 +500,16 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
))
.expect("Fatal error: fail to send blockchain registration to router thread !");
// Get profile path
let profile_path = durs_conf::get_profile_path(
&self.soft_meta_datas.profiles_path,
&self.soft_meta_datas.profile,
);
// Instantiate blockchain module and load is conf
let mut blockchain_module = BlockchainModule::load_blockchain_conf(
router_sender.clone(),
&self.soft_meta_datas.profile,
profile_path,
&self.soft_meta_datas.conf,
RequiredKeysContent::MemberKeyPair(None),
);
......@@ -663,8 +687,11 @@ impl<'a, 'b: 'a> DuniterCore<'b, 'a, DuRsConf> {
.modules()
.get(&M::name().to_string().as_str())
.cloned();
let (conf, keypairs) =
durs_conf::load_conf(self.soft_meta_datas.profile.as_str());
let (conf, keypairs) = durs_conf::load_conf(
self.soft_meta_datas.profile.as_str(),
&self.soft_meta_datas.profiles_path,
&self.soft_meta_datas.keypairs_file_path,
);
let (module_conf, required_keys) = get_module_conf_and_keys::<M>(
&conf.get_global_conf(),
module_conf_json,
......@@ -725,42 +752,44 @@ pub fn get_module_conf<M: DursModule<DuRsConf, DursMsg>>(
}
/// Match cli option --profile
#[inline]
pub fn match_profile(cli_args: &ArgMatches) -> String {
String::from(cli_args.value_of("profile_name").unwrap_or("default"))
}
/// Match cli option --profiles--path
#[inline]
pub fn match_profiles_path(cli_args: &ArgMatches) -> Option<PathBuf> {
cli_args.value_of_os("profiles_path").map(PathBuf::from)
}
/// Match cli option --keypairs-file
#[inline]
pub fn match_keypairs_file(cli_args: &ArgMatches) -> Option<PathBuf> {
cli_args.value_of_os("keypairs_file").map(PathBuf::from)
}
/// Launch synchronisation from a duniter-ts database
pub fn sync_ts<DC: DursConfTrait>(profile: &str, conf: &DC, sync_opts: SyncOpt) {
pub fn sync_ts<DC: DursConfTrait>(profile_path: PathBuf, conf: &DC, sync_opts: SyncOpt) {
// Launch sync-ts
BlockchainModule::sync_ts(profile, conf, sync_opts);
BlockchainModule::sync_ts(profile_path, conf, sync_opts);
}
/// Launch databases explorer
pub fn dbex<DC: DursConfTrait>(profile: &str, conf: &DC, csv: bool, query: &DBExQuery) {
pub fn dbex<DC: DursConfTrait>(profile_path: PathBuf, conf: &DC, csv: bool, query: &DBExQuery) {
// Launch databases explorer
BlockchainModule::dbex(profile, conf, csv, query);
BlockchainModule::dbex(profile_path, conf, csv, query);
}
/// Initialize logger
/// Warning: This function cannot use the macro fatal_error! because the logger is not yet initialized, so it must use panic !
pub fn init_logger(profile: &str, soft_name: &'static str, cli_args: &ArgMatches) {
// Get datas folder path
let mut log_file_path = match dirs::config_dir() {
Some(path) => path,
None => panic!("Fatal error : Impossible to get user config directory"),
};
if !log_file_path.as_path().exists() {
fs::create_dir(log_file_path.as_path()).expect("Impossible to create ~/.config dir !");
}
log_file_path.push(durs_conf::get_user_datas_folder());
if !log_file_path.as_path().exists() {
fs::create_dir(log_file_path.as_path()).expect("Impossible to create ~/.config/durs dir !");
}
log_file_path.push(profile);
// Create datas folder if not exist
if !log_file_path.as_path().exists() {
fs::create_dir(log_file_path.as_path()).expect("Impossible to create your profile dir !");
}
pub fn init_logger(
profile_path: PathBuf,
soft_name: &'static str,
soft_version: &'static str,
cli_args: &ArgMatches,
) {
let mut log_file_path = profile_path;
// Get log_file_path
log_file_path.push(format!("{}.log", soft_name));
......@@ -820,5 +849,25 @@ pub fn init_logger(profile: &str, soft_name: &'static str, cli_args: &ArgMatches
.expect("Fatal error : fail to init file logger !");
}
info!("Launching {}", get_software_infos(soft_name, soft_version));
info!("Successfully init logger");
}
#[inline]
/// Get sofware informations
pub fn get_software_infos(soft_name: &'static str, soft_version: &'static str) -> String {
if let Some(last_commit_hash) = get_last_commit_hash() {
format!(
"{} v{}-dev (commit {})",
soft_name, soft_version, last_commit_hash
)
} else {
format!("{} v{}", soft_name, soft_version)
}
}
#[inline]
/// Get last commit hash
pub fn get_last_commit_hash() -> Option<&'static str> {
option_env!("LAST_COMMIT_HASH")
}