From 8eb86397c80d82a5ba5f43430a739f4c8e00d0c5 Mon Sep 17 00:00:00 2001
From: "Lucas @ Ales" <you@example.com>
Date: Tue, 17 Mar 2020 13:10:18 +0100
Subject: [PATCH] [feat] bc-db-reader: forks tree implemented and added in dbex

---
 lib/core/core/src/commands/dbex.rs            |  18 +-
 .../bc-db-reader/src/blocks/fork_tree.rs      | 188 ++++++++++++++++++
 lib/modules/blockchain/blockchain/src/dbex.rs |  18 +-
 3 files changed, 206 insertions(+), 18 deletions(-)

diff --git a/lib/core/core/src/commands/dbex.rs b/lib/core/core/src/commands/dbex.rs
index f406bb75..b41dead8 100644
--- a/lib/core/core/src/commands/dbex.rs
+++ b/lib/core/core/src/commands/dbex.rs
@@ -47,6 +47,10 @@ pub enum DbExSubCommand {
     #[structopt(name = "distance", setting(structopt::clap::AppSettings::ColoredHelp))]
     DistanceOpt(DistanceOpt),
     /// Forks tree explorer
+    ///
+    /// Blocks are printed from the root to the newer ones (from top to bottom). Main branch blocks are printed on the left (the main branch is represented by ║ characters). A block is represented by the character # and is followed by its block stamp (block number + hash).
+    ///
+    /// By default, blocks inside a branch, those with only one child, are ommited and hashs are truncated to 10 characters. If some blocks are ommited, then the number of them is indicated between parantheses. In verbose mode, all blocks and complete hashs are printed.
     #[structopt(name = "forks", setting(structopt::clap::AppSettings::ColoredHelp))]
     ForksOpt(ForksOpt),
     /// Member explorer
@@ -67,7 +71,11 @@ pub struct DistanceOpt {
 
 #[derive(StructOpt, Debug, Copy, Clone)]
 /// ForksOpt
-pub struct ForksOpt {}
+pub struct ForksOpt {
+    /// Print complete tree and complete hashs
+    #[structopt(short = "v", long = "verbose")]
+    pub verbose: bool,
+}
 
 #[derive(StructOpt, Debug, Copy, Clone)]
 /// MembersOpt
@@ -113,9 +121,11 @@ impl DursExecutableCoreCommand for DbExOpt {
                 self.csv,
                 &DbExQuery::WotQuery(DbExWotQuery::AllDistances(distance_opts.reverse)),
             ),
-            DbExSubCommand::ForksOpt(_forks_opts) => {
-                dbex(profile_path, self.csv, &DbExQuery::ForkTreeQuery)
-            }
+            DbExSubCommand::ForksOpt(forks_opts) => dbex(
+                profile_path,
+                self.csv,
+                &DbExQuery::ForkTreeQuery(forks_opts.verbose),
+            ),
             DbExSubCommand::MemberOpt(member_opts) => dbex(
                 profile_path,
                 self.csv,
diff --git a/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs b/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs
index c70cb88c..b7226d38 100644
--- a/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs
+++ b/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs
@@ -18,8 +18,10 @@
 use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp};
 use serde::de::{self, Deserializer, Visitor};
 use serde::{Deserialize, Serialize, Serializer};
+use std::cmp::Ordering;
 use std::collections::{HashMap, HashSet};
 use std::fmt;
+use std::io::Write;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 /// unique identifier of tree node
@@ -140,6 +142,170 @@ impl ForkTree {
             sheets: HashSet::new(),
         }
     }
+
+    /// Auxilliary function of print.
+    /// Push children of a given node in the heap 'to_treat' so that the main branch child (if it exists) is pushed first.
+    fn print_aux_push_children(
+        &self,
+        to_treat: &mut Vec<(TreeNodeId, usize)>,
+        node_id: TreeNodeId,
+        node: &TreeNode,
+        offset: usize,
+    ) {
+        let mut to_treat_after = Vec::new();
+        let mut child_main_branch_found = false;
+
+        for &child_id in node.children.iter() {
+            if let Some(Some(child)) = self.nodes.get(child_id.0) {
+                if self.is_main_branch_node(child) {
+                    to_treat.push((child_id, 0));
+                    child_main_branch_found = true;
+                    if !self.is_main_branch_node(node) {
+                        durs_common_tools::fatal_error!(
+                                                "Dev error: fork tree : block #{} ({:?}) is on main branch while it is a child of block #{} ({:?}) which is not on main branch.",
+                                                child.data.id, child_id, node.data.id, node_id
+                                            );
+                    }
+                } else {
+                    to_treat_after.push(child_id);
+                }
+            } else {
+                durs_common_tools::fatal_error!(
+                    "Dev error: fork tree : cannot get {:?} in tree nodes.",
+                    child_id
+                );
+            }
+        }
+        // treat other childs after the main branch child
+        for (i, &child_id) in to_treat_after.iter().enumerate() {
+            to_treat.push((
+                child_id,
+                offset + i + (if child_main_branch_found { 1 } else { 0 }),
+            ));
+        }
+    }
+
+    /// Print fork tree in writer
+    pub fn print<F>(&self, mut writer: F, verbose: bool)
+    where
+        F: Write,
+    {
+        if let Some(root_id) = self.root {
+            // Heap of nodes id to treat. The second parameter is the offset of the character # in the printing line
+            let mut to_treat = Vec::new();
+            to_treat.push((root_id, 0));
+
+            while let Some((node_id, offset)) = to_treat.pop() {
+                if let Some(Some(node)) = self.nodes.get(node_id.0) {
+                    // result string that will be written at the end of the process of node
+                    let mut result_string = "".to_string();
+
+                    // print informations on the node
+                    let mut hash = node.data.hash.to_hex();
+                    if !verbose {
+                        hash.truncate(10);
+                    }
+                    if offset > 0 {
+                        result_string.push_str(&format!("║ {}", "│ ".repeat(offset - 1)));
+                    }
+                    result_string.push_str(&format!("# {}-{}\n", node.data.id, hash));
+
+                    match node.children.len().cmp(&1) {
+                        Ordering::Greater => {
+                            // if node has > 1 childs, then print a fork
+                            if offset == 0 {
+                                result_string.push_str(&format!(
+                                    "╟{}─┐\n",
+                                    "─┬".repeat(node.children.len() - 2)
+                                ));
+                            } else {
+                                result_string.push_str(&format!(
+                                    "║ {}├{}─┐\n",
+                                    "│ ".repeat(offset - 1),
+                                    "─┬".repeat(node.children.len() - 2)
+                                ));
+                            }
+
+                            // push children in to_treat
+                            // push first the main branch child (if it exists) so that it will be on the left
+                            self.print_aux_push_children(&mut to_treat, node_id, node, offset);
+                        }
+                        Ordering::Equal => {
+                            // there is only one child
+                            result_string += &format!("║ {}\n", "│ ".repeat(offset));
+
+                            // non verbose case
+                            // omit next childs with outdegree 1 only if there are > 1 of such type
+                            if !verbose {
+                                let mut father_id = node_id;
+                                let mut next_child_id = None;
+                                for &child_id in node.children.iter() {
+                                    next_child_id = Some(child_id);
+                                }
+                                let mut nb_omissions = 0;
+
+                                while let Some(child_id) = next_child_id {
+                                    if let Some(Some(child)) = self.nodes.get(child_id.0) {
+                                        if child.children.iter().count() != 1 {
+                                            // child has > 1 or == 0 subchildren
+                                            if nb_omissions == 1 {
+                                                to_treat.push((father_id, offset));
+                                            } else {
+                                                to_treat.push((child_id, offset));
+                                            }
+                                            break;
+                                        } else {
+                                            // child has 1 subchild
+                                            for &sub_child_id in child.children.iter() {
+                                                father_id = child_id;
+                                                next_child_id = Some(sub_child_id);
+                                                nb_omissions += 1;
+                                            }
+                                        }
+                                    } else {
+                                        durs_common_tools::fatal_error!(
+                                            "Dev error: fork tree : cannot get {:?} in tree nodes.",
+                                            child_id
+                                        );
+                                    }
+                                }
+                                if nb_omissions > 1 {
+                                    if offset > 0 {
+                                        result_string += &format!("║ {}", "│ ".repeat(offset - 1));
+                                    }
+                                    result_string +=
+                                        &format!("┆ ({} blocks ommited)\n", nb_omissions);
+                                    result_string += &format!("║ {}\n", "│ ".repeat(offset));
+                                }
+                            } else {
+                                // verbose case
+                                for (i, &child_id) in node.children.iter().enumerate() {
+                                    to_treat.push((child_id, offset + i));
+                                }
+                            }
+                        }
+                        Ordering::Less => {
+                            // if there is no child (node is a leaf)
+                            if !to_treat.is_empty() {
+                                // if there are still nodes to treat
+                                result_string += &format!("║ {}\n", "│ ".repeat(offset - 1));
+                            }
+                        }
+                    }
+
+                    if writer.write(result_string.as_bytes()).is_err() {
+                        durs_common_tools::fatal_error!("Print fork: write error");
+                    }
+                } else {
+                    durs_common_tools::fatal_error!(
+                        "Dev error: fork tree : cannot get {:?} in tree nodes.",
+                        node_id
+                    );
+                }
+            }
+        }
+    }
+
     /// Set max depth
     #[inline]
     pub fn set_max_depth(&mut self, max_depth: usize) {
@@ -500,6 +666,28 @@ mod tests {
     use super::*;
     use dubp_currency_params::constants::DEFAULT_FORK_WINDOW_SIZE;
 
+    #[test]
+    fn print() {
+        let mut tree = ForkTree::default();
+        let blockstamps =
+            dubp_blocks_tests_tools::mocks::generate_blockstamps(*DEFAULT_FORK_WINDOW_SIZE + 2);
+
+        tree.insert_new_node(blockstamps[0], None, true);
+        tree.insert_new_node(blockstamps[1], Some(TreeNodeId(0)), false);
+        tree.insert_new_node(blockstamps[2], Some(TreeNodeId(0)), true);
+        tree.insert_new_node(blockstamps[3], Some(TreeNodeId(1)), false);
+        tree.insert_new_node(blockstamps[4], Some(TreeNodeId(3)), false);
+        tree.insert_new_node(blockstamps[5], Some(TreeNodeId(4)), false);
+        tree.insert_new_node(blockstamps[6], Some(TreeNodeId(5)), false);
+        tree.insert_new_node(blockstamps[7], Some(TreeNodeId(3)), false);
+        tree.insert_new_node(blockstamps[8], Some(TreeNodeId(3)), false);
+        tree.insert_new_node(blockstamps[9], Some(TreeNodeId(6)), false);
+        tree.insert_new_node(blockstamps[10], Some(TreeNodeId(2)), true);
+        tree.insert_new_node(blockstamps[11], Some(TreeNodeId(10)), true);
+
+        tree.print(std::io::stdout(), false);
+    }
+
     #[test]
     fn insert_root_nodes() {
         let mut tree = ForkTree::default();
diff --git a/lib/modules/blockchain/blockchain/src/dbex.rs b/lib/modules/blockchain/blockchain/src/dbex.rs
index 2888c1f5..2a00f623 100644
--- a/lib/modules/blockchain/blockchain/src/dbex.rs
+++ b/lib/modules/blockchain/blockchain/src/dbex.rs
@@ -87,7 +87,7 @@ pub enum DbExQuery {
     /// Blockchain query
     BcQuery(DbExBcQuery),
     /// Fork tree query
-    ForkTreeQuery,
+    ForkTreeQuery(bool),
     /// Tx query
     TxQuery(DbExTxQuery),
     /// Wot query
@@ -114,7 +114,7 @@ fn open_bc_db_ro(profile_path: PathBuf) -> Option<BcDbRo> {
 /// Execute DbExQuery
 pub fn dbex(profile_path: PathBuf, csv: bool, query: &DbExQuery) {
     match *query {
-        DbExQuery::ForkTreeQuery => dbex_fork_tree(profile_path, csv),
+        DbExQuery::ForkTreeQuery(verbose) => dbex_fork_tree(profile_path, csv, verbose),
         DbExQuery::BcQuery(bc_query) => {
             dbex_bc(profile_path, csv, bc_query).expect("Error: fail to open DB.")
         }
@@ -193,7 +193,7 @@ pub fn dbex_bc(profile_path: PathBuf, _csv: bool, _query: DbExBcQuery) -> Result
 }
 
 /// Print fork tree
-pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool) {
+pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool, verbose: bool) {
     // Open DB
     let load_db_begin = SystemTime::now();
     let db = if let Some(db) = open_bc_db_ro(profile_path) {
@@ -213,17 +213,7 @@ pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool) {
         .r(|db_r| durs_bc_db_reader::current_metadata::get_fork_tree(db_r))
         .expect("fail to get fork tree");
     // Print all fork branches
-    for (tree_node_id, blockstamp) in fork_tree.get_sheets() {
-        debug!(
-            "fork_tree.get_fork_branch({:?}, {})",
-            tree_node_id, blockstamp
-        );
-        let branch = fork_tree.get_fork_branch(tree_node_id);
-        if !branch.is_empty() {
-            println!("Fork branch #{}:", blockstamp);
-            println!("{:#?}", branch);
-        }
-    }
+    fork_tree.print(std::io::stdout(), verbose);
 }
 
 /// Execute DbExTxQuery
-- 
GitLab