diff --git a/conf/lib.rs b/conf/lib.rs index a19b649fd7c9d212ca00f57e64a83be67d94899d..eed6f9218623e2a0600a39a3f98783b226925637 100644 --- a/conf/lib.rs +++ b/conf/lib.rs @@ -36,6 +36,7 @@ use duniter_crypto::keys::*; use duniter_module::{Currency, DuniterConf, ModuleId, RequiredKeys, RequiredKeysContent}; use rand::Rng; use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::collections::HashSet; use std::env; use std::fs; use std::fs::File; @@ -47,6 +48,19 @@ static USER_DATAS_FOLDER: &'static str = "durs-dev"; /// If no currency is specified by the user, is the currency will be chosen by default pub static DEFAULT_CURRRENCY: &'static str = "g1"; +#[derive(Debug, Clone)] +/// User request on global conf +pub enum ChangeGlobalConf { + /// Change currency + ChangeCurrency(Currency), + /// Disable module + DisableModule(ModuleId), + /// Enable module + EnableModule(ModuleId), + /// None + None(), +} + #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] /// Duniter configuration v1 pub struct DuRsConfV1 { @@ -57,9 +71,9 @@ pub struct DuRsConfV1 { /// Configuration of modules in json format (obtained from the conf.json file) pub modules: serde_json::Value, /// Disabled modules - pub disabled: Vec<ModuleId>, + pub disabled: HashSet<ModuleId>, /// Enabled modules - pub enabled: Vec<ModuleId>, + pub enabled: HashSet<ModuleId>, } impl Default for DuRsConfV1 { @@ -68,8 +82,8 @@ impl Default for DuRsConfV1 { currency: Currency::Str(String::from("g1")), my_node_id: generate_random_node_id(), modules: serde_json::Value::Null, - disabled: Vec::with_capacity(0), - enabled: Vec::with_capacity(0), + disabled: HashSet::with_capacity(0), + enabled: HashSet::with_capacity(0), } } } @@ -114,13 +128,31 @@ impl DuniterConf for DuRsConf { _ => panic!("Fail to load duniter conf : conf version not supported !"), } } - fn disabled_modules(&self) -> Vec<ModuleId> { + fn disable(&mut self, module: ModuleId) { + match *self { + DuRsConf::V1(ref mut conf_v1) => { + conf_v1.disabled.insert(module.clone()); + conf_v1.enabled.remove(&module); + } + _ => panic!("Fail to load duniter conf : conf version not supported !"), + } + } + fn enable(&mut self, module: ModuleId) { + match *self { + DuRsConf::V1(ref mut conf_v1) => { + conf_v1.disabled.remove(&module); + conf_v1.enabled.insert(module); + } + _ => panic!("Fail to load duniter conf : conf version not supported !"), + } + } + fn disabled_modules(&self) -> HashSet<ModuleId> { match *self { DuRsConf::V1(ref conf_v1) => conf_v1.disabled.clone(), _ => panic!("Fail to load duniter conf : conf version not supported !"), } } - fn enabled_modules(&self) -> Vec<ModuleId> { + fn enabled_modules(&self) -> HashSet<ModuleId> { match *self { DuRsConf::V1(ref conf_v1) => conf_v1.enabled.clone(), _ => panic!("Fail to load duniter conf : conf version not supported !"), diff --git a/core/change_conf.rs b/core/change_conf.rs new file mode 100644 index 0000000000000000000000000000000000000000..935b0ed52652c4defbd9e4810c1318f729df5c38 --- /dev/null +++ b/core/change_conf.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Crate containing Duniter-rust core. + +use duniter_conf::ChangeGlobalConf; +use duniter_module::DuniterConf; + +/// Change global configuration +pub fn change_global_conf<DC: DuniterConf>( + profile: &str, + mut conf: DC, + user_request: ChangeGlobalConf, +) { + match user_request { + ChangeGlobalConf::ChangeCurrency(_) => {} + ChangeGlobalConf::DisableModule(module_id) => conf.disable(module_id), + ChangeGlobalConf::EnableModule(module_id) => conf.enable(module_id), + ChangeGlobalConf::None() => {} + } + + // Write new conf + super::duniter_conf::write_conf_file(profile, &conf).expect("IOError : Fail to update conf "); + + println!("Configuration successfully updated."); +} diff --git a/core/cli/en.yml b/core/cli/en.yml index 56657e63a56d771b255a543f46bf2f10572e2e8d..a65cdb10a65a95e49b7fdb9007cb1dd0935136b1 100644 --- a/core/cli/en.yml +++ b/core/cli/en.yml @@ -23,6 +23,45 @@ args: debug : print a lot of debug informations\n trace : print all traces (highly verbose)" subcommands: + - enable: + about: Enable some module + version: "0.1.0" + author: Elois L. <elois@duniter.org> + args: + - MODULE_NAME: + help: The module name to enable + index: 1 + required: true + - disable: + about: Disable some module + version: "0.1.0" + author: Elois L. <elois@duniter.org> + args: + - MODULE_NAME: + help: The module name to disable + index: 1 + required: true + - modules: + about: list module + version: "0.1.0" + author: Elois L. <elois@duniter.org> + args: + - disabled: + short: d + long: disabled + help: list only disabled modules + - enabled: + short: e + long: enabled + help: list only enabled modules + - network: + short: n + long: network + help: list only network modules + - secret: + short: s + long: secret + help: list only modules having access to the secret member key - start: about: start duniter server version: "0.1.0" diff --git a/core/lib.rs b/core/lib.rs index 6eb52d9e4df0211f271bf09255e829af1d6eb176..91f98d8ae8a363bc226f9af9a80c0965b0f7d713 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -16,6 +16,7 @@ //! Crate containing Duniter-rust core. #![cfg_attr(feature = "strict", deny(warnings))] +#![cfg_attr(feature = "cargo-clippy", allow(implicit_hasher))] #![deny( missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, @@ -40,14 +41,17 @@ extern crate simplelog; extern crate sqlite; extern crate threadpool; +pub mod change_conf; + use clap::{App, ArgMatches}; use duniter_blockchain::{BlockchainModule, DBExQuery, DBExTxQuery, DBExWotQuery}; -pub use duniter_conf::{DuRsConf, DuniterKeyPairs}; +pub use duniter_conf::{ChangeGlobalConf, DuRsConf, DuniterKeyPairs}; use duniter_message::DuniterMessage; use duniter_module::*; use duniter_network::{NetworkModule, SyncEndpoint}; use log::Level; use simplelog::*; +use std::collections::HashSet; use std::env; use std::fs; use std::fs::{File, OpenOptions}; @@ -63,6 +67,8 @@ pub enum UserCommand { Start(), /// Sync (SyncEndpoint) Sync(SyncEndpoint), + /// List modules + ListModules(HashSet<ModulesFilter>), /// Other command Other(), } @@ -137,7 +143,46 @@ impl DuniterCore<DuRsConf> { conf: conf.clone(), }; - if let Some(_matches) = cli_args.subcommand_matches("start") { + /* + * COMMAND LINE PROCESSING + */ + if let Some(matches) = cli_args.subcommand_matches("disable") { + let module_name = matches + .value_of("MODULE_NAME") + .expect("disable: you must enter a module name !") + .to_string(); + change_conf::change_global_conf( + &profile, + conf, + ChangeGlobalConf::DisableModule(ModuleId(module_name)), + ); + None + } else if let Some(matches) = cli_args.subcommand_matches("enable") { + let module_name = matches + .value_of("MODULE_NAME") + .expect("enable: you must enter a module name !") + .to_string(); + change_conf::change_global_conf( + &profile, + conf, + ChangeGlobalConf::EnableModule(ModuleId(module_name)), + ); + None + } else if let Some(matches) = cli_args.subcommand_matches("modules") { + let mut filters = HashSet::new(); + if matches.is_present("disabled") { + filters.insert(ModulesFilter::Enabled(false)); + } else if matches.is_present("enabled") { + filters.insert(ModulesFilter::Enabled(true)); + } + if matches.is_present("network") { + filters.insert(ModulesFilter::Network()); + } + if matches.is_present("secret") { + filters.insert(ModulesFilter::RequireMemberPrivKey()); + } + Some(list_modules(soft_meta_datas, keypairs, filters)) + } else if let Some(_matches) = cli_args.subcommand_matches("start") { Some(start( soft_meta_datas, keypairs, @@ -312,77 +357,109 @@ impl DuniterCore<DuRsConf> { } /// Plug a network module pub fn plug_network<NM: NetworkModule<DuRsConf, DuniterMessage>>(&mut self) { - if let UserCommand::Start() = self.user_command { - self.network_modules_count += 1; - self.plug::<NM>(); - } else if let UserCommand::Sync(ref sync_endpoint) = self.user_command { - self.network_modules_count += 1; - // Start module in a new thread - let rooter_sender = self.rooter_sender.clone(); - let soft_meta_datas = self.soft_meta_datas.clone(); - let module_conf_json = self - .soft_meta_datas - .conf - .clone() - .modules() - .get(&NM::id().to_string().as_str()) - .cloned(); - let keypairs = self.keypairs; - let sync_endpoint = sync_endpoint.clone(); - self.thread_pool.execute(move || { - // Load module conf and keys - let (module_conf, required_keys) = - load_module_conf_and_keys::<NM>(module_conf_json, keypairs); - NM::sync( - &soft_meta_datas, - required_keys, - module_conf, - rooter_sender, - sync_endpoint, - ).unwrap_or_else(|_| { - panic!( - "Fatal error : fail to load {} Module !", - NM::id().to_string() - ) + let enabled = enabled::<DuRsConf, DuniterMessage, NM>(&self.soft_meta_datas.conf); + if enabled { + if let UserCommand::Start() = self.user_command { + self.network_modules_count += 1; + self.plug::<NM>(); + } else if let UserCommand::Sync(ref sync_endpoint) = self.user_command { + self.network_modules_count += 1; + // Start module in a new thread + let rooter_sender = self.rooter_sender.clone(); + let soft_meta_datas = self.soft_meta_datas.clone(); + let module_conf_json = self + .soft_meta_datas + .conf + .clone() + .modules() + .get(&NM::id().to_string().as_str()) + .cloned(); + let keypairs = self.keypairs; + let sync_endpoint = sync_endpoint.clone(); + self.thread_pool.execute(move || { + // Load module conf and keys + let (module_conf, required_keys) = + load_module_conf_and_keys::<NM>(module_conf_json, keypairs); + NM::sync( + &soft_meta_datas, + required_keys, + module_conf, + rooter_sender, + sync_endpoint, + ).unwrap_or_else(|_| { + panic!( + "Fatal error : fail to load {} Module !", + NM::id().to_string() + ) + }); }); - }); - self.modules_count += 1; - info!("Success to load {} module.", NM::id().to_string()); + self.modules_count += 1; + info!("Success to load {} module.", NM::id().to_string()); + } + } + if let UserCommand::ListModules(ref filters) = self.user_command { + if module_valid_filters::<DuRsConf, DuniterMessage, NM>( + &self.soft_meta_datas.conf, + filters, + true, + ) { + if enabled { + println!("{}", NM::id().to_string()); + } else { + println!("{} (disabled)", NM::id().to_string()); + } + } } } /// Plug a module pub fn plug<M: DuniterModule<DuRsConf, DuniterMessage>>(&mut self) { - if let UserCommand::Start() = self.user_command { - // Start module in a new thread - let rooter_sender_clone = self.rooter_sender.clone(); - let soft_meta_datas = self.soft_meta_datas.clone(); - let module_conf_json = self - .soft_meta_datas - .conf - .clone() - .modules() - .get(&M::id().to_string().as_str()) - .cloned(); - let keypairs = self.keypairs; - self.thread_pool.execute(move || { - // Load module conf and keys - let (module_conf, required_keys) = - load_module_conf_and_keys::<M>(module_conf_json, keypairs); - M::start( - &soft_meta_datas, - required_keys, - module_conf, - rooter_sender_clone, - false, - ).unwrap_or_else(|_| { - panic!( - "Fatal error : fail to load {} Module !", - M::id().to_string() - ) + let enabled = enabled::<DuRsConf, DuniterMessage, M>(&self.soft_meta_datas.conf); + if enabled { + if let UserCommand::Start() = self.user_command { + // Start module in a new thread + let rooter_sender_clone = self.rooter_sender.clone(); + let soft_meta_datas = self.soft_meta_datas.clone(); + let module_conf_json = self + .soft_meta_datas + .conf + .clone() + .modules() + .get(&M::id().to_string().as_str()) + .cloned(); + let keypairs = self.keypairs; + self.thread_pool.execute(move || { + // Load module conf and keys + let (module_conf, required_keys) = + load_module_conf_and_keys::<M>(module_conf_json, keypairs); + M::start( + &soft_meta_datas, + required_keys, + module_conf, + rooter_sender_clone, + false, + ).unwrap_or_else(|_| { + panic!( + "Fatal error : fail to load {} Module !", + M::id().to_string() + ) + }); }); - }); - self.modules_count += 1; - info!("Success to load {} module.", M::id().to_string()); + self.modules_count += 1; + info!("Success to load {} module.", M::id().to_string()); + } + } + if let UserCommand::ListModules(ref filters) = self.user_command { + if module_valid_filters::<DuRsConf, DuniterMessage, M>( + &self.soft_meta_datas.conf, + filters, + false, + ) { + if enabled { + println!("{}", M::id().to_string()); + } else { + println!("{} (disabled)", M::id().to_string()); + } + } } } } @@ -409,6 +486,28 @@ pub fn match_profile(cli_args: &ArgMatches) -> String { String::from(cli_args.value_of("profile").unwrap_or("default")) } +/// List modules +pub fn list_modules<DC: DuniterConf>( + soft_meta_datas: SoftwareMetaDatas<DC>, + keypairs: DuniterKeyPairs, + modules_filter: HashSet<ModulesFilter>, +) -> DuniterCore<DC> { + // Start rooter thread + let rooter_sender = start_rooter::<DC>(0, vec![]); + + // Instanciate DuniterCore + DuniterCore { + user_command: UserCommand::ListModules(modules_filter), + soft_meta_datas, + keypairs, + run_duration_in_secs: 0, + rooter_sender, + modules_count: 0, + network_modules_count: 0, + thread_pool: ThreadPool::new(2), + } +} + /// Start rooter thread pub fn start_rooter<DC: DuniterConf>( run_duration_in_secs: u64, diff --git a/module/lib.rs b/module/lib.rs index 3c5636c9a0f8b517ccf264dc1bbb69f1d5bf5387..bd332484d3c2d6d6b519449ee321aa8358a935c1 100644 --- a/module/lib.rs +++ b/module/lib.rs @@ -33,6 +33,7 @@ extern crate serde_json; use duniter_crypto::keys::{KeyPair, KeyPairEnum}; use serde::de::DeserializeOwned; use serde::ser::{Serialize, Serializer}; +use std::collections::HashSet; use std::fmt::Debug; use std::sync::mpsc; @@ -98,10 +99,14 @@ pub trait DuniterConf: Clone + Debug + Default + PartialEq + Serialize + Deseria fn set_currency(&mut self, new_currency: Currency); /// Get node id fn my_node_id(&self) -> u32; + /// Disable a module + fn disable(&mut self, module: ModuleId); + /// Enable a module + fn enable(&mut self, module: ModuleId); /// Get disabled modules - fn disabled_modules(&self) -> Vec<ModuleId>; + fn disabled_modules(&self) -> HashSet<ModuleId>; /// Get enabled modules - fn enabled_modules(&self) -> Vec<ModuleId>; + fn enabled_modules(&self) -> HashSet<ModuleId>; /// Get modules conf fn modules(&self) -> serde_json::Value; } @@ -182,6 +187,54 @@ pub enum ModulePriority { Optional(), } +/// Determines if a module is activated or not +pub fn enabled<DC: DuniterConf, Mess: ModuleMessage, M: DuniterModule<DC, Mess>>( + conf: &DC, +) -> bool { + let disabled_modules = conf.disabled_modules(); + let enabled_modules = conf.enabled_modules(); + match M::priority() { + ModulePriority::Essential() => true, + ModulePriority::Recommended() => !disabled_modules.contains(&M::id()), + ModulePriority::Optional() => enabled_modules.contains(&M::id()), + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Modules filter +/// If bool = false, the meaning of the filter is reversed. +pub enum ModulesFilter { + /// Enabled modules + Enabled(bool), + /// Network modules + Network(), + /// Modules that require member private key + RequireMemberPrivKey(), +} + +/// Returns true only if the module checks all filters +pub fn module_valid_filters<DC: DuniterConf, Mess: ModuleMessage, M: DuniterModule<DC, Mess>>( + conf: &DC, + filters: &HashSet<ModulesFilter>, + network_module: bool, +) -> bool { + if filters.contains(&ModulesFilter::Network()) && !network_module { + return false; + } + if filters.contains(&ModulesFilter::RequireMemberPrivKey()) + && M::ask_required_keys() != RequiredKeys::MemberKeyPair() + { + return false; + } + if filters.contains(&ModulesFilter::Enabled(true)) && !enabled::<DC, Mess, M>(conf) { + return false; + } + if filters.contains(&ModulesFilter::Enabled(false)) && enabled::<DC, Mess, M>(conf) { + return false; + } + true +} + /// All Duniter-rs modules must implement this trait. pub trait DuniterModule<DC: DuniterConf, M: ModuleMessage> { /// Module configuration