From 9b098712bd330a73da956e4cc93278ff4639770c Mon Sep 17 00:00:00 2001
From: librelois <elois@ifee.fr>
Date: Mon, 14 May 2018 16:41:47 +0200
Subject: [PATCH] [enh] #68 add core crate

---
 core/Cargo.toml |  28 +++
 core/cli/en.yml |  64 +++++++
 core/lib.rs     | 440 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 532 insertions(+)
 create mode 100644 core/Cargo.toml
 create mode 100644 core/cli/en.yml
 create mode 100644 core/lib.rs

diff --git a/core/Cargo.toml b/core/Cargo.toml
new file mode 100644
index 00000000..c25218b5
--- /dev/null
+++ b/core/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "duniter-core"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Duniter-rs core."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+clap = {version = "2.31.2", features = ["yaml"]}
+duniter-blockchain = { path = "../blockchain" }
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+lazy_static = "1.0.0"
+log = "0.4.1"
+rand = "0.4.2"
+regex = "0.2.6"
+rust-crypto = "0.2.36"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+simplelog = "0.5.1"
+sqlite = "0.23.9"
+threadpool = "1.7.1"
\ No newline at end of file
diff --git a/core/cli/en.yml b/core/cli/en.yml
new file mode 100644
index 00000000..6c0c983b
--- /dev/null
+++ b/core/cli/en.yml
@@ -0,0 +1,64 @@
+name: durs
+version: "0.1.0"
+author: Elois L. <elois@duniter.org>
+about: Rust implementation of Duniter
+args:
+    - profile:
+        short: p
+        long: profile
+        value_name: CUSTOM_PROFILE
+        help: Set a custom datas folder
+        takes_value: true
+    - logs:
+        short: l
+        long : logs
+        value_name: LOGS_LEVEL
+        takes_value: true
+        possible_values: ["e", "w", "i", "d", "t", "error", "warn", "info", "debug", "trace"]
+        help: Set the level of logs verbosity
+        long_help: "Set the level of logs verbosity :\n
+          error : print serious errors\n
+          warn : print hazardous situations\n
+          info : default level\n
+          debug : print a lot of debug informations\n
+          trace : print all traces (highly verbose)"
+subcommands:
+    - start:
+        about: start duniter server
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+    - sync_ts:
+        about: synchronization via a duniter-ts database
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - TS_PROFILE:
+                help: Set the ts profile to use
+                index: 1
+            - cautious:
+                short: c
+                long: cautious
+                help: cautious mode (check all protocol rules, very slow)
+    - msync_ts:
+        about: synchronization in memory mode via a duniter-ts database
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - TS_PROFILE:
+                help: Set the ts profile to use
+                index: 1
+            - cautious:
+                short: c
+                long: cautious
+                help: cautious mode (check all protocol rules, very slow)
+    - reset:
+        about: reset data or conf or all
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - DATAS_TYPE:
+                help : choose type datas to reset
+                index: 1
+                possible_values: ["data","conf","all"]
+                required: true
+            
\ No newline at end of file
diff --git a/core/lib.rs b/core/lib.rs
new file mode 100644
index 00000000..1d4bd89f
--- /dev/null
+++ b/core/lib.rs
@@ -0,0 +1,440 @@
+//  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.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate clap;
+
+#[macro_use]
+extern crate log;
+
+extern crate duniter_blockchain;
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate serde_json;
+extern crate simplelog;
+extern crate sqlite;
+extern crate threadpool;
+
+use self::threadpool::ThreadPool;
+use clap::{App, ArgMatches};
+use duniter_blockchain::BlockchainModule;
+use duniter_conf::DuniterKeyPairs;
+use duniter_crypto::keys::ed25519;
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use log::Level;
+use simplelog::*;
+use std::env;
+use std::fs;
+use std::fs::{File, OpenOptions};
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
+
+#[derive(Debug)]
+/// Duniter Core Datas
+pub struct DuniterCore {
+    /// Does the entered command require to launch server ?
+    pub start: bool,
+    /// Software name
+    pub soft_name: &'static str,
+    /// Soft version
+    pub soft_version: &'static str,
+    /// Keypairs
+    pub keypairs: DuniterKeyPairs,
+    /// Duniter configuration
+    pub conf: DuniterConf,
+    /// Run duration. Zero = infinite duration.
+    pub run_duration_in_secs: u64,
+    /// Sender channel of rooter thread
+    pub rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+    ///  Count the number of plugged modules
+    pub modules_count: usize,
+    /// ThreadPool that execute plugged modules
+    pub thread_pool: ThreadPool,
+}
+
+impl DuniterCore {
+    /// Instantiate Duniter classic node
+    pub fn new(soft_name: &'static str, soft_version: &'static str) -> Option<DuniterCore> {
+        DuniterCore::new_specialized_node(soft_name, soft_version, 0, vec![], vec![], None)
+    }
+    /// Instantiate Duniter specialize node
+    pub fn new_specialized_node<'a, 'b>(
+        soft_name: &'static str,
+        soft_version: &'static str,
+        run_duration_in_secs: u64,
+        external_followers: Vec<mpsc::Sender<DuniterMessage>>,
+        sup_apps: Vec<App<'a, 'b>>,
+        sup_apps_fn: Option<&Fn(&str, &ArgMatches) -> ()>,
+    ) -> Option<DuniterCore> {
+        // Get cli conf
+        let yaml = load_yaml!("./cli/en.yml");
+        let cli_conf = App::from_yaml(yaml);
+
+        // Math command line arguments
+        let cli_args = if !sup_apps.is_empty() {
+            cli_conf.subcommands(sup_apps).get_matches()
+        } else {
+            cli_conf.get_matches()
+        };
+
+        // Get datas profile name
+        let profile = match_profile(&cli_args);
+
+        // Init logger
+        init_logger(profile.as_str(), soft_name, &cli_args);
+
+        // Load global conf
+        let (conf, keypairs) = duniter_conf::load_conf(profile.as_str());
+        info!("Success to load global conf.");
+
+        if let Some(_matches) = cli_args.subcommand_matches("start") {
+            Some(start(
+                soft_name,
+                soft_version,
+                keypairs,
+                conf,
+                run_duration_in_secs,
+                external_followers,
+            ))
+        } else if let Some(matches) = cli_args.subcommand_matches("sync_ts") {
+            let ts_profile = matches.value_of("TS_PROFILE").unwrap_or("duniter_default");
+            sync_ts(conf, ts_profile, matches.is_present("cautious"));
+            None
+        } else if let Some(matches) = cli_args.subcommand_matches("reset") {
+            let mut profile_path = match env::home_dir() {
+                Some(path) => path,
+                None => panic!("Impossible to get your home dir !"),
+            };
+            profile_path.push(".config");
+            profile_path.push(duniter_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 matches.value_of("DATAS_TYPE").unwrap() {
+                "data" => {
+                    let mut currency_datas_path = profile_path.clone();
+                    currency_datas_path.push("g1");
+                    fs::remove_dir_all(currency_datas_path.as_path())
+                        .expect("Fail to remove all currency datas !");
+                }
+                "conf" => {
+                    let mut conf_file_path = profile_path.clone();
+                    conf_file_path.push("conf.json");
+                    fs::remove_file(conf_file_path.as_path()).expect("Fail to remove conf file !");
+                    let mut conf_keys_path = profile_path.clone();
+                    conf_keys_path.push("keypairs.json");
+                    fs::remove_file(conf_keys_path.as_path())
+                        .expect("Fail to remove keypairs file !");
+                }
+                "all" => {
+                    fs::remove_dir_all(profile_path.as_path())
+                        .expect("Fail to remove all profile datas !");
+                }
+                _ => {}
+            }
+            None
+        } else if let Some(sup_apps_fn) = sup_apps_fn {
+            sup_apps_fn(profile.as_str(), &cli_args);
+            None
+        } else {
+            panic!("unknow sub-command !")
+        }
+    }
+    /// Start blockchain module
+    pub fn start_blockchain(&self) {
+        if self.start {
+            thread::sleep(Duration::from_secs(2));
+            // Create blockchain module channel
+            let (blockchain_sender, blockchain_receiver): (
+                mpsc::Sender<DuniterMessage>,
+                mpsc::Receiver<DuniterMessage>,
+            ) = mpsc::channel();
+
+            // Send blockchain sender to rooter thread
+            self.rooter_sender
+                .send(RooterThreadMessage::ModuleSender(blockchain_sender))
+                .expect("Fatal error: fail to send blockchain sender to rooter thread !");
+
+            // Send modules_count to rooter thread
+            self.rooter_sender
+                .send(RooterThreadMessage::ModulesCount(self.modules_count + 1))
+                .expect("Fatal error: fail to send modules count to rooter thread !");
+
+            // Instantiate blockchain module and load is conf
+            let mut blockchain_module = BlockchainModule::load_blockchain_conf(
+                &self.conf,
+                RequiredKeysContent::MemberKeyPair(None),
+                false,
+            );
+            info!("Success to load Blockchain module.");
+
+            // Start blockchain module in main thread
+            blockchain_module.start_blockchain(blockchain_receiver);
+        }
+    }
+    /// Plug a module
+    pub fn plug<M: DuniterModule<ed25519::PublicKey, ed25519::KeyPair, DuniterMessage>>(&mut self) {
+        if self.start {
+            // Start module in a new thread
+            let soft_name_clone = self.soft_name.clone();
+            let soft_version_clone = self.soft_version.clone();
+            let required_keys = DuniterKeyPairs::get_required_keys_content(
+                M::ask_required_keys(),
+                self.keypairs.clone(),
+            );
+            let module_conf = if let Some(module_conf_) = self
+                .conf
+                .clone()
+                .modules()
+                .get(&M::id().to_string().as_str())
+            {
+                module_conf_.clone()
+            } else {
+                M::default_conf()
+            };
+            let rooter_sender_clone = self.rooter_sender.clone();
+            let conf_clone = self.conf.clone();
+            self.thread_pool.execute(move || {
+                M::start(
+                    soft_name_clone,
+                    soft_version_clone,
+                    required_keys,
+                    &conf_clone,
+                    &module_conf,
+                    rooter_sender_clone,
+                    false,
+                ).expect(&format!(
+                    "Fatal error : fail to load {} Module !",
+                    M::id().to_string()
+                ));
+            });
+            self.modules_count += 1;
+            info!("Success to load {} module.", M::id().to_string());
+        }
+    }
+}
+
+/// Match cli option --profile
+pub fn match_profile(cli_args: &ArgMatches) -> String {
+    String::from(cli_args.value_of("profile").unwrap_or("default"))
+}
+
+/// Launch duniter server
+pub fn start(
+    soft_name: &'static str,
+    soft_version: &'static str,
+    keypairs: DuniterKeyPairs,
+    conf: DuniterConf,
+    run_duration_in_secs: u64,
+    external_followers: Vec<mpsc::Sender<DuniterMessage>>,
+) -> DuniterCore {
+    info!("Starting Duniter-rs...");
+
+    // Create senders channel
+    let (rooter_sender, main_receiver): (
+        mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        mpsc::Receiver<RooterThreadMessage<DuniterMessage>>,
+    ) = mpsc::channel();
+
+    // Create rooter thread
+    thread::spawn(move || {
+        // Wait to receiver modules senders
+        let mut modules_senders: Vec<mpsc::Sender<DuniterMessage>> = Vec::new();
+        let mut modules_count_expected = None;
+        while modules_count_expected.is_none()
+            || modules_senders.len() < modules_count_expected.unwrap() + 1
+        {
+            match main_receiver.recv_timeout(Duration::from_secs(20)) {
+                Ok(mess) => {
+                    match mess {
+                        RooterThreadMessage::ModuleSender(module_sender) => {
+                            // Subscribe this module to all others modules
+                            for other_module in modules_senders.clone() {
+                                if other_module
+                                    .send(DuniterMessage::Followers(vec![module_sender.clone()]))
+                                    .is_err()
+                                {
+                                    panic!("Fatal error : fail to send all modules senders to all modules !");
+                                }
+                            }
+                            // Subcribe this module to all external_followers
+                            for external_follower in external_followers.clone() {
+                                if external_follower
+                                    .send(DuniterMessage::Followers(vec![module_sender.clone()]))
+                                    .is_err()
+                                {
+                                    panic!("Fatal error : fail to send all modules senders to all external_followers !");
+                                }
+                            }
+                            // Subscribe all other modules to this module
+                            if module_sender
+                                .send(DuniterMessage::Followers(modules_senders.clone()))
+                                .is_err()
+                            {
+                                panic!("Fatal error : fail to send all modules senders to all modules !");
+                            }
+                            // Subcribe all external_followers to this module
+                            if module_sender
+                                .send(DuniterMessage::Followers(external_followers.clone()))
+                                .is_err()
+                            {
+                                panic!("Fatal error : fail to send all external_followers to all modules !");
+                            }
+                            // Push this module to modules_senders list
+                            modules_senders.push(module_sender);
+                            // Log the number of modules_senders received
+                            info!(
+                                "Rooter thread receive {} module senders",
+                                modules_senders.len()
+                            );
+                        }
+                        RooterThreadMessage::ModulesCount(modules_count) => {
+                            info!("Rooter thread receive ModulesCount({})", modules_count);
+                            if modules_senders.len() == modules_count {
+                                break;
+                            } else if modules_senders.len() < modules_count {
+                                modules_count_expected = Some(modules_count);
+                            } else {
+                                panic!("Fatal error : Receive more modules_sender than expected !")
+                            }
+                        }
+                    }
+                }
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Timeout => {
+                        panic!("Fatal error : not receive all modules_senders after 20 secs !")
+                    }
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Fatal error : rooter thread disconnnected !")
+                    }
+                },
+            }
+        }
+        info!("Receive all modules senders.");
+        if run_duration_in_secs > 0 {
+            thread::sleep(Duration::from_secs(run_duration_in_secs));
+            // Send DuniterMessage::Stop() to all modules
+            for sender in modules_senders {
+                if sender.send(DuniterMessage::Stop()).is_err() {
+                    panic!("Fail to send Stop() message to one module !")
+                }
+            }
+            thread::sleep(Duration::from_secs(2));
+        }
+    });
+
+    // Instanciate DuniterCore
+    DuniterCore {
+        start: true,
+        soft_name,
+        soft_version,
+        keypairs,
+        conf,
+        run_duration_in_secs,
+        rooter_sender,
+        modules_count: 0,
+        thread_pool: ThreadPool::new(2),
+    }
+}
+
+/// Launch synchronisation from a duniter-ts database
+pub fn sync_ts(conf: DuniterConf, ts_profile: &str, cautious: bool) {
+    // Launch sync-ts
+    BlockchainModule::sync_ts(&conf, ts_profile, cautious);
+}
+
+/// Initialize logger
+pub fn init_logger(profile: &str, soft_name: &'static str, cli_args: &ArgMatches) {
+    // Get datas folder path
+    let mut log_file_path = match env::home_dir() {
+        Some(path) => path,
+        None => panic!("Fatal error : Impossible to get your home dir!"),
+    };
+    log_file_path.push(".config");
+    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(duniter_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 !");
+    }
+
+    // Get log_file_path
+    log_file_path.push(format!("{}.log", soft_name));
+
+    // Get log level
+    let log_level = match cli_args.value_of("logs").unwrap_or("i") {
+        "e" | "error" => Level::Error,
+        "w" | "warn" => Level::Warn,
+        "i" | "info" => Level::Info,
+        "d" | "debug" => Level::Debug,
+        "t" | "trace" => Level::Trace,
+        _ => panic!("Fatal error : unknow log level !"),
+    };
+
+    // Config logger
+    let logger_config = Config {
+        time: Some(Level::Error),
+        level: Some(Level::Error),
+        target: Some(Level::Debug),
+        location: Some(Level::Debug),
+        time_format: Some("%Y-%m-%d %H:%M:%S%:z"),
+    };
+
+    // Create log file if not exist
+    if !log_file_path.as_path().exists() {
+        File::create(
+            log_file_path
+                .to_str()
+                .expect("Fatal error : fail to get log file path !"),
+        ).expect("Fatal error : fail to create log file path !");
+    }
+
+    CombinedLogger::init(vec![
+        TermLogger::new(LevelFilter::Error, logger_config).unwrap(),
+        WriteLogger::new(
+            log_level.to_level_filter(),
+            logger_config,
+            OpenOptions::new()
+                .write(true)
+                .append(true)
+                .open(
+                    log_file_path
+                        .to_str()
+                        .expect("Fatal error : fail to get log file path !"),
+                )
+                .expect("Fatal error : fail to open log file !"),
+        ),
+    ]).expect("Fatal error : fail to init logger !");
+}
-- 
GitLab