need to fix pop selection in renderer
This commit is contained in:
parent
97878de2ef
commit
df67c432f3
@ -1,7 +1,7 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::watcher::{RenderLayoutKind, RenderPane};
|
use crate::renderer::RenderLayoutKind;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use clap::*;
|
use clap::*;
|
||||||
@ -55,8 +55,9 @@ pub enum CliCommand {
|
|||||||
|
|
||||||
/// Clear Pane
|
/// Clear Pane
|
||||||
#[command(name = "clear", aliases = ["c"])]
|
#[command(name = "clear", aliases = ["c"])]
|
||||||
Clear { pane: RenderPane },
|
Clear,
|
||||||
|
|
||||||
|
/// Set TUI layout
|
||||||
#[command(name = "layout", aliases = ["lay"])]
|
#[command(name = "layout", aliases = ["lay"])]
|
||||||
Layout { mode: RenderLayoutKind },
|
Layout { mode: RenderLayoutKind },
|
||||||
}
|
}
|
||||||
@ -103,15 +104,15 @@ pub enum CliBlockCommand {
|
|||||||
|
|
||||||
/// Display Block by Hash
|
/// Display Block by Hash
|
||||||
#[command(name = "display", aliases = ["d"])]
|
#[command(name = "display", aliases = ["d"])]
|
||||||
#[group(required = true, multiple = false)]
|
#[group(multiple = false)]
|
||||||
Display{
|
Display {
|
||||||
/// Block Hash
|
/// Block Hash
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
key: Option<String>,
|
key: Option<String>,
|
||||||
/// Block Height
|
/// Block Height
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
height: Option<u64>
|
height: Option<u64>,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use super::event_bus::EventBus;
|
use super::event_bus::EventBus;
|
||||||
use crate::watcher::ExecutorCommand;
|
use crate::executor::ExecutorCommand;
|
||||||
|
|
||||||
static EXECUTOR_EVENT_BUS: Lazy<Arc<EventBus<ExecutorCommand>>> =
|
static EXECUTOR_EVENT_BUS: Lazy<Arc<EventBus<ExecutorCommand>>> =
|
||||||
Lazy::new(|| Arc::new(EventBus::new()));
|
Lazy::new(|| Arc::new(EventBus::new()));
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::broadcast;
|
|
||||||
|
|
||||||
use super::event_bus::EventBus;
|
|
||||||
use crate::watcher::renderer::RenderCommand;
|
|
||||||
|
|
||||||
static RENDER_CHANNEL: Lazy<Arc<EventBus<RenderCommand>>> = Lazy::new(|| Arc::new(EventBus::new()));
|
|
||||||
pub fn publish_render_event(event: RenderCommand) {
|
|
||||||
RENDER_CHANNEL.publish(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe_render_event() -> broadcast::Receiver<RenderCommand> {
|
|
||||||
RENDER_CHANNEL.subscribe()
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
15
node/src/bus/watcher.rs
Normal file
15
node/src/bus/watcher.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use super::event_bus::EventBus;
|
||||||
|
use crate::watcher::WatcherCommand;
|
||||||
|
|
||||||
|
static WATCHER_CHANNEL: Lazy<Arc<EventBus<WatcherCommand>>> = Lazy::new(|| Arc::new(EventBus::new()));
|
||||||
|
pub fn publish_watcher_event(event: WatcherCommand) {
|
||||||
|
WATCHER_CHANNEL.publish(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_watcher_event() -> broadcast::Receiver<WatcherCommand> {
|
||||||
|
WATCHER_CHANNEL.subscribe()
|
||||||
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
use crate::args::*;
|
use crate::args::*;
|
||||||
use crate::core::ChainData;
|
use crate::core::ChainData;
|
||||||
|
use crate::executor::ExecutorCommand;
|
||||||
use crate::node::*;
|
use crate::node::*;
|
||||||
use crate::watcher::{RenderCommand, ExecutorCommand};
|
use crate::renderer::RenderCommand;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
pub fn handle_peer_command(cmd: CliPeerCommand) -> NodeCommand {
|
pub fn handle_peer_command(cmd: CliPeerCommand) -> NodeCommand {
|
||||||
@ -19,12 +20,10 @@ pub fn handle_block_command(cmd: CliBlockCommand) -> NodeCommand {
|
|||||||
CliBlockCommand::List => NodeCommand::ListBlocks,
|
CliBlockCommand::List => NodeCommand::ListBlocks,
|
||||||
CliBlockCommand::Dump { output } => NodeCommand::DumpBlocks(output),
|
CliBlockCommand::Dump { output } => NodeCommand::DumpBlocks(output),
|
||||||
CliBlockCommand::Create => NodeCommand::CreateBlock,
|
CliBlockCommand::Create => NodeCommand::CreateBlock,
|
||||||
CliBlockCommand::Display{key, height} => {
|
CliBlockCommand::Display { key, height } => match (key, height) {
|
||||||
match (key, height) {
|
|
||||||
(Some(k), _) => return NodeCommand::DisplayBlockByKey(k),
|
(Some(k), _) => return NodeCommand::DisplayBlockByKey(k),
|
||||||
(_, Some(h)) => return NodeCommand::DisplayBlockByHeight(h),
|
(_, Some(h)) => return NodeCommand::DisplayBlockByHeight(h),
|
||||||
(None, None) => panic!()
|
(None, None) => return NodeCommand::DisplayBlockInteractive,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,20 +41,19 @@ fn handle_ping(cmd: CliPingCommand) -> NodeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cli(input: &[&str]) -> ExecutorCommand {
|
pub fn cli(input: &str) -> ExecutorCommand {
|
||||||
match Cli::try_parse_from(input) {
|
let argv: Vec<&str> = std::iter::once(" ")
|
||||||
|
.chain(input.split_whitespace())
|
||||||
|
.collect();
|
||||||
|
match Cli::try_parse_from(argv) {
|
||||||
Ok(cmd) => match cmd.command {
|
Ok(cmd) => match cmd.command {
|
||||||
CliCommand::Layout { mode } => {
|
CliCommand::Layout { mode } => ExecutorCommand::Render(RenderCommand::ChangeLayout(mode)),
|
||||||
ExecutorCommand::Render(RenderCommand::ChangeLayout(mode))
|
CliCommand::Clear => ExecutorCommand::Render(RenderCommand::ClearPane),
|
||||||
}
|
|
||||||
CliCommand::Clear { pane } => ExecutorCommand::Render(RenderCommand::ClearPane(pane)),
|
|
||||||
CliCommand::Peer { peer_cmd } => ExecutorCommand::Node(handle_peer_command(peer_cmd)),
|
CliCommand::Peer { peer_cmd } => ExecutorCommand::Node(handle_peer_command(peer_cmd)),
|
||||||
CliCommand::Block { block_cmd } => {
|
CliCommand::Block { block_cmd } => ExecutorCommand::Node(handle_block_command(block_cmd)),
|
||||||
ExecutorCommand::Node(handle_block_command(block_cmd))
|
CliCommand::Transaction(tx) => {
|
||||||
|
ExecutorCommand::Node(NodeCommand::ProcessChainData(ChainData::Transaction(tx)))
|
||||||
}
|
}
|
||||||
CliCommand::Transaction(tx) => ExecutorCommand::Node(NodeCommand::ProcessChainData(
|
|
||||||
ChainData::Transaction(tx),
|
|
||||||
)),
|
|
||||||
CliCommand::DebugShowId => ExecutorCommand::Node(NodeCommand::ShowId),
|
CliCommand::DebugShowId => ExecutorCommand::Node(NodeCommand::ShowId),
|
||||||
CliCommand::StartListner { addr } => {
|
CliCommand::StartListner { addr } => {
|
||||||
ExecutorCommand::Node(NodeCommand::StartListner(addr.parse().unwrap()))
|
ExecutorCommand::Node(NodeCommand::StartListner(addr.parse().unwrap()))
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Default, bincode::Decode, bincode::Encode)]
|
#[derive(
|
||||||
|
Clone, Debug, serde::Deserialize, serde::Serialize, Default, bincode::Decode, bincode::Encode,
|
||||||
|
)]
|
||||||
pub struct BlockHeader {
|
pub struct BlockHeader {
|
||||||
pub previous_hash: String,
|
pub previous_hash: String,
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
@ -8,7 +10,9 @@ pub struct BlockHeader {
|
|||||||
pub height: u64,
|
pub height: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Default, bincode::Decode, bincode::Encode)]
|
#[derive(
|
||||||
|
Clone, Debug, serde::Deserialize, serde::Serialize, Default, bincode::Decode, bincode::Encode,
|
||||||
|
)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub head: BlockHeader,
|
pub head: BlockHeader,
|
||||||
pub data: Vec<String>,
|
pub data: Vec<String>,
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::ChainData;
|
|
||||||
use crate::db::database;
|
|
||||||
use crate::db;
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
|
use crate::core::ChainData;
|
||||||
|
use crate::db;
|
||||||
use crate::db::DatabaseError;
|
use crate::db::DatabaseError;
|
||||||
|
use crate::db::database;
|
||||||
use crate::error::TxError;
|
use crate::error::TxError;
|
||||||
use crate::log;
|
use crate::log;
|
||||||
|
|
||||||
use super::hasher::Hasher;
|
use super::hasher::Hasher;
|
||||||
|
|
||||||
use vlogger::*;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::UNIX_EPOCH;
|
use std::time::UNIX_EPOCH;
|
||||||
|
use vlogger::*;
|
||||||
|
|
||||||
use thiserror::*;
|
use thiserror::*;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -31,8 +31,7 @@ pub enum BlockchainError {
|
|||||||
Validation(#[from] ValidationError),
|
Validation(#[from] ValidationError),
|
||||||
|
|
||||||
#[error("Block Creation Error")]
|
#[error("Block Creation Error")]
|
||||||
BlockCreation
|
BlockCreation,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCKCHAIN_ID: &str = "watch-chain";
|
const BLOCKCHAIN_ID: &str = "watch-chain";
|
||||||
@ -66,7 +65,6 @@ impl Blockchain {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn acc_exists(&self, acc: &Account) -> bool {
|
fn acc_exists(&self, acc: &Account) -> bool {
|
||||||
self.balances.iter().find(|(k, _)| *k == acc).is_some()
|
self.balances.iter().find(|(k, _)| *k == acc).is_some()
|
||||||
}
|
}
|
||||||
@ -76,7 +74,8 @@ impl Blockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hash_transaction_pool(&self) -> Vec<String> {
|
fn hash_transaction_pool(&self) -> Vec<String> {
|
||||||
self.mempool
|
self
|
||||||
|
.mempool
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tx| Hasher::hash_chain_data(tx))
|
.map(|tx| Hasher::hash_chain_data(tx))
|
||||||
.collect()
|
.collect()
|
||||||
@ -120,7 +119,7 @@ impl Blockchain {
|
|||||||
self.add_block(new_block.clone())?;
|
self.add_block(new_block.clone())?;
|
||||||
Ok(new_block)
|
Ok(new_block)
|
||||||
}
|
}
|
||||||
Err(_) => Err(BlockchainError::BlockCreation)
|
Err(_) => Err(BlockchainError::BlockCreation),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,11 +162,11 @@ impl Blockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Blockchain {
|
impl Blockchain {
|
||||||
pub fn list_blocks(&self) -> Result<String, BlockchainError> {
|
pub fn list_blocks(&self) -> Result<Vec<String>, BlockchainError> {
|
||||||
let mut ret = String::from("Blocks List\n-------------------\n");
|
let mut ret = Vec::new();
|
||||||
let blocks = self.blocks()?;
|
let blocks = self.blocks()?;
|
||||||
for (i, b) in blocks.iter().enumerate() {
|
for b in blocks.iter() {
|
||||||
ret.push_str(format!("Block Hash #{i}: {}\n", b.head.block_hash()).as_str())
|
ret.push(b.head.block_hash().to_string())
|
||||||
}
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
@ -185,7 +184,7 @@ impl Blockchain {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_block(&mut self, block: Arc<core::Block>) -> Result<(), BlockchainError>{
|
pub fn add_block(&mut self, block: Arc<core::Block>) -> Result<(), BlockchainError> {
|
||||||
match self.validate_block(&block) {
|
match self.validate_block(&block) {
|
||||||
Ok(()) => Ok(self.insert_block(&block)?),
|
Ok(()) => Ok(self.insert_block(&block)?),
|
||||||
Err(e) => Err(BlockchainError::Validation(e)),
|
Err(e) => Err(BlockchainError::Validation(e)),
|
||||||
@ -223,7 +222,7 @@ impl Blockchain {
|
|||||||
fn validate_chain(&self) -> Result<(), ValidationError> {
|
fn validate_chain(&self) -> Result<(), ValidationError> {
|
||||||
if let Ok(blocks) = self.blocks() {
|
if let Ok(blocks) = self.blocks() {
|
||||||
if let Some(mut prev_block) = blocks.first() {
|
if let Some(mut prev_block) = blocks.first() {
|
||||||
for (i, block) in blocks.iter().enumerate() {
|
for (i, block) in blocks.iter().skip(1).enumerate() {
|
||||||
let head = block.head();
|
let head = block.head();
|
||||||
let hash = Hasher::calculate_block_hash(block.head());
|
let hash = Hasher::calculate_block_hash(block.head());
|
||||||
|
|
||||||
@ -246,9 +245,11 @@ impl Blockchain {
|
|||||||
balances: HashMap::new(),
|
balances: HashMap::new(),
|
||||||
mempool: vec![],
|
mempool: vec![],
|
||||||
id: BLOCKCHAIN_ID.to_string(),
|
id: BLOCKCHAIN_ID.to_string(),
|
||||||
db
|
db,
|
||||||
};
|
};
|
||||||
chain.validate_chain().or_else(|e| return Err(BlockchainError::Validation(e)))?;
|
chain
|
||||||
|
.validate_chain()
|
||||||
|
.or_else(|e| return Err(BlockchainError::Validation(e)))?;
|
||||||
Ok(chain)
|
Ok(chain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use sha2::Sha256;
|
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
use sha2::Sha256;
|
||||||
|
|
||||||
use super::{ChainData, BlockHeader};
|
use super::{BlockHeader, ChainData};
|
||||||
|
|
||||||
pub struct Hasher {}
|
pub struct Hasher {}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use crate::core::Account;
|
use crate::core::Account;
|
||||||
use crate::error::TxError;
|
use crate::error::TxError;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone, bincode::Encode, bincode::Decode)]
|
#[derive(
|
||||||
|
serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone, bincode::Encode, bincode::Decode,
|
||||||
|
)]
|
||||||
pub struct Tx {
|
pub struct Tx {
|
||||||
from: Account,
|
from: Account,
|
||||||
to: Account,
|
to: Account,
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
|
use crate::{
|
||||||
|
core::{self, Block, ChainData, Hasher},
|
||||||
|
db::error::DatabaseError,
|
||||||
|
error::print_error_chain,
|
||||||
|
log,
|
||||||
|
};
|
||||||
use bincode::{self, config::Configuration};
|
use bincode::{self, config::Configuration};
|
||||||
use sled::{self, Batch};
|
use sled::{self, Batch};
|
||||||
use crate::{core::{self, Block, ChainData, Hasher}, db::error::DatabaseError, error::print_error_chain, log};
|
|
||||||
use vlogger::*;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use vlogger::*;
|
||||||
|
|
||||||
static BINCODE_CONFIG: Configuration = bincode::config::standard();
|
static BINCODE_CONFIG: Configuration = bincode::config::standard();
|
||||||
|
|
||||||
@ -10,55 +15,66 @@ const DB_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/database");
|
|||||||
|
|
||||||
const DB_TREE: &str = "blocks";
|
const DB_TREE: &str = "blocks";
|
||||||
|
|
||||||
const BLOCK_INDEX: &str = "blocks:";
|
const BLOCK_INDEX: &str = "blocks";
|
||||||
const CHAIN_DATA_INDEX: &str = "chain_data:";
|
const CHAIN_DATA_INDEX: &str = "chain_data";
|
||||||
const DATA_TO_BLOCK_INDEX: &str = "data_to_block:";
|
const DATA_TO_BLOCK_INDEX: &str = "data_to_block";
|
||||||
const METADATA_INDEX: &str= "metadata:";
|
const METADATA_INDEX: &str = "metadata";
|
||||||
|
|
||||||
const TIP_KEY: &str = "chain_tip";
|
const TIP_KEY: &str = "chain_tip";
|
||||||
const HEIGHT_KEY: &str = "chain_height";
|
const HEIGHT_KEY: &str = "chain_height";
|
||||||
|
|
||||||
|
const HEIGHT_TO_HASH_INDEX: &str = "height_to_hash";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ChainDb {
|
pub struct ChainDb {
|
||||||
db: sled::Tree,
|
db: sled::Tree,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_index(key: &str) -> String {
|
fn data_index(key: &str) -> String {
|
||||||
format!("{}{}", CHAIN_DATA_INDEX, key)
|
format!("{}:{}", CHAIN_DATA_INDEX, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_to_block_index(key: &str) -> String {
|
fn data_to_block_index(key: &str) -> String {
|
||||||
format!("{}{}", DATA_TO_BLOCK_INDEX, key)
|
format!("{}:{}", DATA_TO_BLOCK_INDEX, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_index(key: &str) -> String {
|
fn block_index(key: &str) -> String {
|
||||||
format!("{}{}", BLOCK_INDEX, key)
|
format!("{}:{}", BLOCK_INDEX, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata_index(key: &str) -> String {
|
fn metadata_index(key: &str) -> String {
|
||||||
format!("{}{}", METADATA_INDEX, key)
|
format!("{}:{}", METADATA_INDEX, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn height_to_hash_index(height: u64) -> String {
|
||||||
|
format!("{}:{:020}", HEIGHT_TO_HASH_INDEX, height)
|
||||||
|
}
|
||||||
|
|
||||||
impl ChainDb {
|
impl ChainDb {
|
||||||
pub fn new(path: Option<String>) ->Result<ChainDb, DatabaseError> {
|
pub fn new(path: Option<String>) -> Result<ChainDb, DatabaseError> {
|
||||||
let path = if path.is_some() {
|
let path = if path.is_some() {
|
||||||
&path.unwrap()
|
&path.unwrap()
|
||||||
} else {
|
} else {
|
||||||
DB_PATH
|
DB_PATH
|
||||||
};
|
};
|
||||||
match sled::open(&path) {
|
let config = sled::Config::new()
|
||||||
|
.cache_capacity(512 * 1024)
|
||||||
|
.segment_size(1024 * 1024)
|
||||||
|
.path(&path);
|
||||||
|
|
||||||
|
match config.open() {
|
||||||
Ok(db) => {
|
Ok(db) => {
|
||||||
if db.was_recovered() {
|
if db.was_recovered() {
|
||||||
log(msg!(INFO, "Loaded Database from Previous state at: {}", path));
|
log(msg!(
|
||||||
|
INFO,
|
||||||
|
"Loaded Database from Previous state at: {}",
|
||||||
|
path
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
log(msg!(INFO, "Created Database at {}", path));
|
log(msg!(INFO, "Created Database at {}", path));
|
||||||
}
|
}
|
||||||
let db = db
|
let db = db.open_tree(DB_TREE)?;
|
||||||
.open_tree(DB_TREE)?;
|
Ok(ChainDb { db })
|
||||||
Ok(ChainDb {
|
|
||||||
db
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
print_error_chain(&err.clone().into());
|
print_error_chain(&err.clone().into());
|
||||||
@ -78,7 +94,10 @@ impl ChainDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block_by_height(&self, height: u64) -> Result<Option<Arc<core::Block>>, DatabaseError> {
|
pub fn get_block_by_height(
|
||||||
|
&self,
|
||||||
|
height: u64,
|
||||||
|
) -> Result<Option<Arc<core::Block>>, DatabaseError> {
|
||||||
for result in self.db.scan_prefix(BLOCK_INDEX) {
|
for result in self.db.scan_prefix(BLOCK_INDEX) {
|
||||||
let (_key, value) = result?;
|
let (_key, value) = result?;
|
||||||
let (block, _size) = bincode::decode_from_slice::<core::Block, _>(&value, BINCODE_CONFIG)?;
|
let (block, _size) = bincode::decode_from_slice::<core::Block, _>(&value, BINCODE_CONFIG)?;
|
||||||
@ -107,7 +126,9 @@ impl ChainDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_blocks(&self) -> Result<Vec<Arc<core::Block>>, DatabaseError> {
|
pub fn get_all_blocks(&self) -> Result<Vec<Arc<core::Block>>, DatabaseError> {
|
||||||
self.db.scan_prefix(BLOCK_INDEX)
|
self
|
||||||
|
.db
|
||||||
|
.scan_prefix(BLOCK_INDEX)
|
||||||
.map(|res| -> Result<Arc<core::Block>, DatabaseError> {
|
.map(|res| -> Result<Arc<core::Block>, DatabaseError> {
|
||||||
let (_key, value) = res?;
|
let (_key, value) = res?;
|
||||||
let (block, _size) = bincode::decode_from_slice::<core::Block, _>(&value, BINCODE_CONFIG)
|
let (block, _size) = bincode::decode_from_slice::<core::Block, _>(&value, BINCODE_CONFIG)
|
||||||
@ -128,11 +149,21 @@ impl ChainDb {
|
|||||||
let mut db_batch = Batch::default();
|
let mut db_batch = Batch::default();
|
||||||
let bin_block = bincode::encode_to_vec(block, BINCODE_CONFIG)?;
|
let bin_block = bincode::encode_to_vec(block, BINCODE_CONFIG)?;
|
||||||
db_batch.insert(block_index(block.head().block_hash()).as_str(), bin_block);
|
db_batch.insert(block_index(block.head().block_hash()).as_str(), bin_block);
|
||||||
|
db_batch.insert(
|
||||||
|
height_to_hash_index(block.head().height).as_str(),
|
||||||
|
block.head().block_hash(),
|
||||||
|
);
|
||||||
for data in block.data() {
|
for data in block.data() {
|
||||||
db_batch.insert(data_to_block_index(data.as_str()).as_str(), block.head().block_hash());
|
db_batch.insert(
|
||||||
|
data_to_block_index(data.as_str()).as_str(),
|
||||||
|
block.head().block_hash(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
db_batch.insert(metadata_index(TIP_KEY).as_str(), block.head().block_hash());
|
db_batch.insert(metadata_index(TIP_KEY).as_str(), block.head().block_hash());
|
||||||
db_batch.insert(metadata_index(HEIGHT_KEY).as_str(), &block.head().height.to_be_bytes());
|
db_batch.insert(
|
||||||
|
metadata_index(HEIGHT_KEY).as_str(),
|
||||||
|
&block.head().height.to_be_bytes(),
|
||||||
|
);
|
||||||
self.db.apply_batch(db_batch)?;
|
self.db.apply_batch(db_batch)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,5 +18,5 @@ pub enum DatabaseError {
|
|||||||
Decode(#[from] bincode::error::DecodeError),
|
Decode(#[from] bincode::error::DecodeError),
|
||||||
|
|
||||||
#[error("Missing chain data for hash: {0}")]
|
#[error("Missing chain data for hash: {0}")]
|
||||||
MissingData(String)
|
MissingData(String),
|
||||||
}
|
}
|
||||||
|
|||||||
15
node/src/executor/command.rs
Normal file
15
node/src/executor/command.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use crate::node::NodeCommand;
|
||||||
|
use crate::renderer::RenderCommand;
|
||||||
|
use crate::watcher::WatcherCommand;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ExecutorCommand {
|
||||||
|
NodeResponse(String),
|
||||||
|
Echo(Vec<String>),
|
||||||
|
Print(String),
|
||||||
|
InvalidCommand(String),
|
||||||
|
Node(NodeCommand),
|
||||||
|
Render(RenderCommand),
|
||||||
|
Watcher(WatcherCommand),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
92
node/src/executor/executor.rs
Normal file
92
node/src/executor/executor.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use crate::{
|
||||||
|
bus::{SystemEvent, publish_watcher_event, publish_system_event},
|
||||||
|
log,
|
||||||
|
node::NodeCommand,
|
||||||
|
renderer::RenderTarget,
|
||||||
|
watcher::WatcherCommand,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use vlogger::*;
|
||||||
|
|
||||||
|
use super::ExecutorCommand;
|
||||||
|
use crate::RenderCommand;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InProcessError {
|
||||||
|
#[error("TODO: {0}")]
|
||||||
|
TODO(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Executor {
|
||||||
|
node_tx: mpsc::Sender<NodeCommand>,
|
||||||
|
rx: mpsc::Receiver<ExecutorCommand>,
|
||||||
|
exit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Executor {
|
||||||
|
pub fn new(node_tx: mpsc::Sender<NodeCommand>, rx: mpsc::Receiver<ExecutorCommand>) -> Self {
|
||||||
|
Self {
|
||||||
|
node_tx,
|
||||||
|
rx,
|
||||||
|
exit: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) {
|
||||||
|
publish_system_event(SystemEvent::ExecutorStarted);
|
||||||
|
while !self.exit {
|
||||||
|
self.listen().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exit(&mut self) {
|
||||||
|
log(msg!(DEBUG, "Executor Exit"));
|
||||||
|
self.exit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn listen(&mut self) {
|
||||||
|
if let Some(cmd) = self.rx.recv().await {
|
||||||
|
let _ = self.execute(cmd).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_node_cmd(&self, cmd: NodeCommand) {
|
||||||
|
self.node_tx.send(cmd).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_node_cmd(&self, cmd: NodeCommand) {
|
||||||
|
self.send_node_cmd(cmd).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn echo(&self, s: Vec<String>) {
|
||||||
|
let mut str = s.join(" ");
|
||||||
|
str.push_str("\n");
|
||||||
|
let rd_cmd = WatcherCommand::Render(RenderCommand::RenderStringToPaneId {
|
||||||
|
str,
|
||||||
|
pane: RenderTarget::CliOutput,
|
||||||
|
});
|
||||||
|
publish_watcher_event(rd_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn invalid_command(&self, str: String) {
|
||||||
|
let rd_cmd = WatcherCommand::Render(RenderCommand::RenderStringToPaneId {
|
||||||
|
str,
|
||||||
|
pane: RenderTarget::CliOutput,
|
||||||
|
});
|
||||||
|
publish_watcher_event(rd_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&mut self, cmd: ExecutorCommand) {
|
||||||
|
match cmd {
|
||||||
|
ExecutorCommand::NodeResponse(resp) => log(resp),
|
||||||
|
ExecutorCommand::Node(n) => self.handle_node_cmd(n).await,
|
||||||
|
ExecutorCommand::Render(p) => publish_watcher_event(WatcherCommand::Render(p)),
|
||||||
|
ExecutorCommand::Watcher(w) => publish_watcher_event(w),
|
||||||
|
ExecutorCommand::Echo(s) => self.echo(s).await,
|
||||||
|
ExecutorCommand::Print(s) => log(s),
|
||||||
|
ExecutorCommand::InvalidCommand(str) => self.invalid_command(str).await,
|
||||||
|
ExecutorCommand::Exit => self.exit().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,32 +17,48 @@ pub mod db {
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub use database::*;
|
pub use database::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod bus {
|
pub mod bus {
|
||||||
pub mod executor;
|
|
||||||
pub mod network;
|
|
||||||
pub mod render;
|
|
||||||
pub mod system;
|
|
||||||
pub mod event_bus;
|
pub mod event_bus;
|
||||||
|
pub mod network;
|
||||||
|
pub mod watcher;
|
||||||
|
pub mod system;
|
||||||
|
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
|
pub mod executor;
|
||||||
pub use network::*;
|
pub use network::*;
|
||||||
pub use render::*;
|
pub use watcher::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod watcher {
|
pub mod executor {
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod parser;
|
pub use executor::*;
|
||||||
|
|
||||||
|
pub mod command;
|
||||||
|
pub use command::*;
|
||||||
|
}
|
||||||
|
pub mod renderer {
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
pub use renderer::*;
|
||||||
|
|
||||||
|
pub mod pane;
|
||||||
|
pub use pane::*;
|
||||||
|
|
||||||
|
pub mod layout;
|
||||||
|
pub use layout::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod watcher {
|
||||||
|
pub mod builder;
|
||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
|
|
||||||
pub use executor::*;
|
pub use builder::*;
|
||||||
pub use parser::*;
|
|
||||||
pub use renderer::*;
|
|
||||||
pub use watcher::*;
|
pub use watcher::*;
|
||||||
|
|
||||||
|
pub mod command;
|
||||||
|
pub use command::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod protocol {
|
pub mod protocol {
|
||||||
@ -75,11 +91,11 @@ pub mod core {
|
|||||||
|
|
||||||
pub mod seeds_constants;
|
pub mod seeds_constants;
|
||||||
|
|
||||||
use crate::watcher::renderer::{RenderCommand, RenderPane};
|
use crate::renderer::{RenderCommand, RenderTarget};
|
||||||
|
|
||||||
pub fn log(msg: String) {
|
pub fn log(msg: String) {
|
||||||
crate::bus::publish_render_event(RenderCommand::RenderStringToPane {
|
crate::bus::publish_watcher_event(watcher::WatcherCommand::Render(RenderCommand::RenderStringToPaneId {
|
||||||
pane: RenderPane::CliOutput,
|
pane: RenderTarget::CliOutput,
|
||||||
str: msg,
|
str: msg,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,26 +21,9 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
.start()
|
.start()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
crossterm::execute!(
|
watcher.run().await?;
|
||||||
std::io::stdout(),
|
|
||||||
crossterm::event::EnableBracketedPaste,
|
|
||||||
crossterm::event::EnableFocusChange,
|
|
||||||
crossterm::event::EnableMouseCapture,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if !watcher.poll().await.is_ok_and(|b| b) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crossterm::execute!(
|
|
||||||
std::io::stdout(),
|
|
||||||
crossterm::event::DisableBracketedPaste,
|
|
||||||
crossterm::event::DisableFocusChange,
|
|
||||||
crossterm::event::DisableMouseCapture
|
|
||||||
)?;
|
|
||||||
ratatui::restore();
|
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,5 @@ use thiserror::Error;
|
|||||||
#[derive(Debug, Clone, Error)]
|
#[derive(Debug, Clone, Error)]
|
||||||
pub enum NetworkError {
|
pub enum NetworkError {
|
||||||
#[error("Implement NetworkError Enum: ({})", file!())]
|
#[error("Implement NetworkError Enum: ({})", file!())]
|
||||||
TODO
|
TODO,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
|
use crate::bus::{publish_system_event, publish_watcher_event, SystemEvent};
|
||||||
use crate::core::{self, Blockchain, BlockchainError, ChainData, ValidationError};
|
use crate::core::{self, Blockchain, BlockchainError, ChainData, ValidationError};
|
||||||
use crate::error::print_error_chain;
|
use crate::error::print_error_chain;
|
||||||
use crate::bus::{SystemEvent, publish_system_event};
|
use crate::executor::ExecutorCommand;
|
||||||
|
use crate::log;
|
||||||
use crate::protocol::ProtocolMessage;
|
use crate::protocol::ProtocolMessage;
|
||||||
use crate::protocol::{Connector, ConnectorCommand};
|
use crate::protocol::{Connector, ConnectorCommand};
|
||||||
use crate::seeds_constants::SEED_NODES;
|
use crate::seeds_constants::SEED_NODES;
|
||||||
use crate::watcher::executor::ExecutorCommand;
|
use crate::watcher::{WatcherCommand, WatcherMode};
|
||||||
use crate::log;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use thiserror::*;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vlogger::*;
|
use vlogger::*;
|
||||||
use thiserror::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TcpPeer {
|
pub struct TcpPeer {
|
||||||
@ -49,7 +50,7 @@ pub struct Node {
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum NodeError {
|
pub enum NodeError {
|
||||||
#[error("Block chain error")]
|
#[error("Block chain error")]
|
||||||
ChainError(#[from] BlockchainError)
|
ChainError(#[from] BlockchainError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -67,6 +68,7 @@ pub enum NodeCommand {
|
|||||||
PingAddr(String),
|
PingAddr(String),
|
||||||
PingId(String),
|
PingId(String),
|
||||||
CreateBlock,
|
CreateBlock,
|
||||||
|
DisplayBlockInteractive,
|
||||||
DisplayBlockByKey(String),
|
DisplayBlockByKey(String),
|
||||||
DisplayBlockByHeight(u64),
|
DisplayBlockByHeight(u64),
|
||||||
ListBlocks,
|
ListBlocks,
|
||||||
@ -118,7 +120,7 @@ impl Node {
|
|||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||||
addr: Option<SocketAddr>,
|
addr: Option<SocketAddr>,
|
||||||
chain: Blockchain
|
chain: Blockchain,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (tx, rx) = mpsc::channel::<NodeCommand>(100);
|
let (tx, rx) = mpsc::channel::<NodeCommand>(100);
|
||||||
Self {
|
Self {
|
||||||
@ -165,23 +167,24 @@ impl Node {
|
|||||||
let resp = ProtocolMessage::BootstrapResponse {
|
let resp = ProtocolMessage::BootstrapResponse {
|
||||||
blocks: {
|
blocks: {
|
||||||
if let Ok(blocks) = self.get_blocks() {
|
if let Ok(blocks) = self.get_blocks() {
|
||||||
serde_json::to_string(&blocks
|
serde_json::to_string(
|
||||||
|
&blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| (**f).clone())
|
.map(|f| (**f).clone())
|
||||||
.collect::<Vec<core::Block>>()
|
.collect::<Vec<core::Block>>(),
|
||||||
).map_err(
|
)
|
||||||
|e| {
|
.map_err(|e| {
|
||||||
log(msg!(
|
log(msg!(
|
||||||
ERROR,
|
ERROR,
|
||||||
"Failed to serde Chain for BootstrapResponse: {e}"
|
"Failed to serde Chain for BootstrapResponse: {e}"
|
||||||
));
|
));
|
||||||
e
|
e
|
||||||
},
|
})
|
||||||
).ok()
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
peer.sender.send(resp).await.unwrap();
|
peer.sender.send(resp).await.unwrap();
|
||||||
log(msg!(DEBUG, "Send BootstrapResponse to {peer_id}"));
|
log(msg!(DEBUG, "Send BootstrapResponse to {peer_id}"));
|
||||||
@ -213,7 +216,12 @@ impl Node {
|
|||||||
ProtocolMessage::Block { block, .. } => {
|
ProtocolMessage::Block { block, .. } => {
|
||||||
log(msg!(DEBUG, "Received Block from {peer_id}"));
|
log(msg!(DEBUG, "Received Block from {peer_id}"));
|
||||||
if let Err(_e) = self.chain.add_block(block.into()) {
|
if let Err(_e) = self.chain.add_block(block.into()) {
|
||||||
log(msg!(DEBUG, "TODO: implement error handling in {}:{}", file!(), line!()));
|
log(msg!(
|
||||||
|
DEBUG,
|
||||||
|
"TODO: implement error handling in {}:{}",
|
||||||
|
file!(),
|
||||||
|
line!()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolMessage::ChainData { data, .. } => {
|
ProtocolMessage::ChainData { data, .. } => {
|
||||||
@ -335,7 +343,8 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_to_seed(&mut self) {
|
async fn connect_to_seed(&mut self) {
|
||||||
self.connector_cmd(ConnectorCommand::ConnectToTcpSeed(SEED_NODES[0]))
|
self
|
||||||
|
.connector_cmd(ConnectorCommand::ConnectToTcpSeed(SEED_NODES[0]))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +352,8 @@ impl Node {
|
|||||||
if let Some(addr) = self.addr {
|
if let Some(addr) = self.addr {
|
||||||
self.start_connection_listner(addr).await;
|
self.start_connection_listner(addr).await;
|
||||||
} else {
|
} else {
|
||||||
self.start_connection_listner(SocketAddr::new(
|
self
|
||||||
|
.start_connection_listner(SocketAddr::new(
|
||||||
std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
|
std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
8080,
|
8080,
|
||||||
))
|
))
|
||||||
@ -405,20 +415,33 @@ impl Node {
|
|||||||
NodeCommand::CreateBlock => {
|
NodeCommand::CreateBlock => {
|
||||||
log(msg!(DEBUG, "Received CreateBlock Command"));
|
log(msg!(DEBUG, "Received CreateBlock Command"));
|
||||||
if let Ok(block) = self.chain.create_block() {
|
if let Ok(block) = self.chain.create_block() {
|
||||||
log(msg!(INFO, "Created Block with hash {}", block.head().block_hash()));
|
log(msg!(
|
||||||
|
INFO,
|
||||||
|
"Created Block with hash {}",
|
||||||
|
block.head().block_hash()
|
||||||
|
));
|
||||||
self.broadcast_block(&block).await;
|
self.broadcast_block(&block).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeCommand::DisplayBlockByKey(key) => {
|
NodeCommand::DisplayBlockInteractive => {
|
||||||
self.chain.display_block_by_key(key)
|
let blocks = match self.chain.list_blocks() {
|
||||||
}
|
Ok(b) => b,
|
||||||
NodeCommand::DisplayBlockByHeight(height) => {
|
Err(e) => return print_error_chain(&e.into()),
|
||||||
self.chain.display_block_by_height(height)
|
};
|
||||||
|
let wat_cmd = WatcherCommand::SetMode(WatcherMode::Select {
|
||||||
|
content: blocks.into(),
|
||||||
|
title: "Select Block to display".to_string(),
|
||||||
|
callback: Box::new(ExecutorCommand::Node(NodeCommand::DisplayBlockByKey("".to_string()))),
|
||||||
|
index: 0
|
||||||
|
});
|
||||||
|
publish_watcher_event(wat_cmd);
|
||||||
}
|
}
|
||||||
|
NodeCommand::DisplayBlockByKey(key) => self.chain.display_block_by_key(key),
|
||||||
|
NodeCommand::DisplayBlockByHeight(height) => self.chain.display_block_by_height(height),
|
||||||
NodeCommand::ListBlocks => {
|
NodeCommand::ListBlocks => {
|
||||||
log(msg!(DEBUG, "Received DebugListBlocks command"));
|
log(msg!(DEBUG, "Received DebugListBlocks command"));
|
||||||
match self.chain.list_blocks() {
|
match self.chain.list_blocks() {
|
||||||
Ok(s) => log(s),
|
Ok(s) => log(s.join("\n")),
|
||||||
Err(e) => print_error_chain(&e.into()),
|
Err(e) => print_error_chain(&e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
use crate::executor::ExecutorCommand;
|
||||||
|
use crate::log;
|
||||||
use crate::node::node;
|
use crate::node::node;
|
||||||
use crate::protocol::ProtocolMessage;
|
use crate::protocol::ProtocolMessage;
|
||||||
use crate::watcher::ExecutorCommand;
|
|
||||||
use tokio::net;
|
use tokio::net;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use crate::log;
|
|
||||||
|
|
||||||
use super::Connector;
|
use super::Connector;
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,10 @@ use crate::log;
|
|||||||
|
|
||||||
use super::Connection;
|
use super::Connection;
|
||||||
use crate::bus::*;
|
use crate::bus::*;
|
||||||
use crate::node::{error, NetworkError};
|
use crate::executor::ExecutorCommand;
|
||||||
use crate::node::node;
|
use crate::node::node;
|
||||||
|
use crate::node::{NetworkError, error};
|
||||||
use crate::protocol::ProtocolMessage;
|
use crate::protocol::ProtocolMessage;
|
||||||
use crate::watcher::ExecutorCommand;
|
|
||||||
use thiserror::*;
|
use thiserror::*;
|
||||||
|
|
||||||
pub enum ConnectorCommand {
|
pub enum ConnectorCommand {
|
||||||
@ -246,30 +246,20 @@ impl Connector {
|
|||||||
stream: &mut net::TcpStream,
|
stream: &mut net::TcpStream,
|
||||||
message: &ProtocolMessage,
|
message: &ProtocolMessage,
|
||||||
) -> Result<(), NetworkError> {
|
) -> Result<(), NetworkError> {
|
||||||
let json = serde_json::to_string(message)
|
let json = serde_json::to_string(message).map_err(|_e| NetworkError::TODO)?;
|
||||||
.map_err(|_e| {
|
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
let data = json.as_bytes();
|
let data = json.as_bytes();
|
||||||
|
|
||||||
let len = data.len() as u32;
|
let len = data.len() as u32;
|
||||||
stream
|
stream
|
||||||
.write_all(&len.to_be_bytes())
|
.write_all(&len.to_be_bytes())
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(|_e| NetworkError::TODO)?;
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.write_all(data)
|
.write_all(data)
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(|_e| NetworkError::TODO)?;
|
||||||
NetworkError::TODO
|
stream.flush().await.map_err(|_e| NetworkError::TODO)?;
|
||||||
})?;
|
|
||||||
stream.flush().await
|
|
||||||
.map_err(|_e| {
|
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,9 +270,7 @@ impl Connector {
|
|||||||
stream
|
stream
|
||||||
.read_exact(&mut len_bytes)
|
.read_exact(&mut len_bytes)
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(|_e| NetworkError::TODO)?;
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let len = u32::from_be_bytes(len_bytes) as usize;
|
let len = u32::from_be_bytes(len_bytes) as usize;
|
||||||
|
|
||||||
@ -294,19 +282,11 @@ impl Connector {
|
|||||||
stream
|
stream
|
||||||
.read_exact(&mut data)
|
.read_exact(&mut data)
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(|_e| NetworkError::TODO)?;
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let json = String::from_utf8(data)
|
let json = String::from_utf8(data).map_err(|_e| NetworkError::TODO)?;
|
||||||
.map_err(|_e| {
|
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let message: ProtocolMessage = serde_json::from_str(&json)
|
let message: ProtocolMessage = serde_json::from_str(&json).map_err(|_e| NetworkError::TODO)?;
|
||||||
.map_err(|_e| {
|
|
||||||
NetworkError::TODO
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,11 @@ impl fmt::Display for ProtocolMessage {
|
|||||||
write!(f, "BootstrapRequest from {} (v{})", peer_id, version)
|
write!(f, "BootstrapRequest from {} (v{})", peer_id, version)
|
||||||
}
|
}
|
||||||
ProtocolMessage::BootstrapResponse { blocks } => {
|
ProtocolMessage::BootstrapResponse { blocks } => {
|
||||||
write!(f, "BootstrapResponse with {:?} blocks", blocks.clone().unwrap_or_default().len())
|
write!(
|
||||||
|
f,
|
||||||
|
"BootstrapResponse with {:?} blocks",
|
||||||
|
blocks.clone().unwrap_or_default().len()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ProtocolMessage::GetPeersRequest { peer_id } => {
|
ProtocolMessage::GetPeersRequest { peer_id } => {
|
||||||
write!(f, "GetPeersRequest from {}", peer_id)
|
write!(f, "GetPeersRequest from {}", peer_id)
|
||||||
|
|||||||
93
node/src/renderer/layout.rs
Normal file
93
node/src/renderer/layout.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use ratatui::layout::{ Rect, Flex };
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
|
||||||
|
use super::{Pane, RenderBuffer, RenderTarget};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||||
|
pub enum RenderLayoutKind {
|
||||||
|
#[value(name = "horizontal", aliases = ["h"])]
|
||||||
|
CliHorizontal,
|
||||||
|
#[value(name = "vertical", aliases = ["v"])]
|
||||||
|
CliVertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLI_INPUT_PREFIX: &str = "> ";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RenderLayout {
|
||||||
|
layout: Layout,
|
||||||
|
pub panes: Vec<Pane>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &mut RenderLayout {
|
||||||
|
fn render(self, area: Rect, buffer: &mut Buffer) {
|
||||||
|
let rects = self.rects(area);
|
||||||
|
|
||||||
|
for (p, r) in self.panes.iter_mut().zip(rects.iter()) {
|
||||||
|
p.render(*r, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
|
||||||
|
let [area] = Layout::horizontal([horizontal])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(area);
|
||||||
|
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
|
||||||
|
area
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderLayout {
|
||||||
|
pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> {
|
||||||
|
self.layout.split(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(kind: RenderLayoutKind) -> RenderLayout {
|
||||||
|
match kind {
|
||||||
|
RenderLayoutKind::CliVertical => RenderLayout {
|
||||||
|
layout: Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(70), Constraint::Percentage(30)]),
|
||||||
|
panes: vec![
|
||||||
|
Pane::new(
|
||||||
|
Some(" Input Pane ".to_string()),
|
||||||
|
RenderTarget::CliInput,
|
||||||
|
RenderBuffer::List {
|
||||||
|
list: vec![String::new()],
|
||||||
|
index: 0,
|
||||||
|
prefix: CLI_INPUT_PREFIX,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
Pane::new(
|
||||||
|
Some(" Output Pane ".to_string()),
|
||||||
|
RenderTarget::CliOutput,
|
||||||
|
RenderBuffer::String(String::new()),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
RenderLayoutKind::CliHorizontal => RenderLayout {
|
||||||
|
layout: Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(70), Constraint::Percentage(30)]),
|
||||||
|
panes: vec![
|
||||||
|
Pane::new(
|
||||||
|
Some(" Output Pane ".to_string()),
|
||||||
|
RenderTarget::CliOutput,
|
||||||
|
RenderBuffer::String(String::new()),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
Pane::new(
|
||||||
|
Some(" Input Pane ".to_string()),
|
||||||
|
RenderTarget::CliInput,
|
||||||
|
RenderBuffer::List {
|
||||||
|
list: vec![String::new()],
|
||||||
|
index: 0,
|
||||||
|
prefix: CLI_INPUT_PREFIX,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
node/src/renderer/pane.rs
Normal file
172
node/src/renderer/pane.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
use ratatui::widgets::{Clear, Wrap};
|
||||||
|
use ratatui::{
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::Rect,
|
||||||
|
symbols::border,
|
||||||
|
widgets::{Block, List, Paragraph, Widget},
|
||||||
|
};
|
||||||
|
use vlogger::{msg, DEBUG};
|
||||||
|
|
||||||
|
use crate::log;
|
||||||
|
|
||||||
|
use super::center;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RenderBuffer {
|
||||||
|
List {
|
||||||
|
list: Vec<String>,
|
||||||
|
index: usize,
|
||||||
|
prefix: &'static str,
|
||||||
|
},
|
||||||
|
String(String),
|
||||||
|
Select(Arc<Vec<String>>, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum RenderTarget {
|
||||||
|
All,
|
||||||
|
|
||||||
|
CliInput,
|
||||||
|
|
||||||
|
CliOutput,
|
||||||
|
|
||||||
|
PopUp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Pane {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub target: RenderTarget,
|
||||||
|
pub buffer: RenderBuffer,
|
||||||
|
pub focused: bool,
|
||||||
|
pub scroll: i16,
|
||||||
|
pub max_scroll: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pane {
|
||||||
|
pub fn new(
|
||||||
|
title: Option<String>,
|
||||||
|
target: RenderTarget,
|
||||||
|
buffer: RenderBuffer,
|
||||||
|
focused: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
target,
|
||||||
|
buffer,
|
||||||
|
focused,
|
||||||
|
scroll: 0,
|
||||||
|
max_scroll: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &mut Pane {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title({
|
||||||
|
if let Some(t) = &self.title {
|
||||||
|
t.clone()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.border_set(border::PLAIN)
|
||||||
|
.border_style({
|
||||||
|
if self.focused {
|
||||||
|
Style::new().green()
|
||||||
|
} else {
|
||||||
|
Style::new().white()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let inner_area = block.inner(area);
|
||||||
|
let content_width = inner_area.width as usize;
|
||||||
|
let content_height = inner_area.height as usize;
|
||||||
|
match &self.buffer {
|
||||||
|
RenderBuffer::String(s) => {
|
||||||
|
let wrapped_lines = s
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.is_empty() {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
(line.len() + content_width - 1) / { content_width + (content_width == 0) as usize }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum::<usize>();
|
||||||
|
|
||||||
|
self.max_scroll = if wrapped_lines > content_height {
|
||||||
|
(wrapped_lines - content_height) as i16
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let scroll_offset = self.max_scroll.saturating_sub(self.scroll) as u16;
|
||||||
|
|
||||||
|
Paragraph::new(s.clone())
|
||||||
|
.wrap(Wrap::default())
|
||||||
|
.left_aligned()
|
||||||
|
.block(block)
|
||||||
|
.scroll((scroll_offset as u16, 0))
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
RenderBuffer::Select(list, idx) => {
|
||||||
|
let rect = center(area, Constraint::Percentage(60), Constraint::Percentage(60));
|
||||||
|
Clear.render(rect, buf);
|
||||||
|
self.max_scroll = if list.len() > content_height {
|
||||||
|
(list.len() - content_height) as i16
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let scroll_offset = self.max_scroll.saturating_sub(self.scroll) as u16;
|
||||||
|
log(msg!(DEBUG, "idx {idx}"));
|
||||||
|
let list_w = List::new(
|
||||||
|
list
|
||||||
|
.iter()
|
||||||
|
.skip(scroll_offset as usize)
|
||||||
|
.take(content_height)
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
Line::from(format!(
|
||||||
|
"{}{}",
|
||||||
|
"",
|
||||||
|
textwrap::fill(s, content_width.saturating_sub(2))
|
||||||
|
)).style(if i + scroll_offset as usize == *idx {
|
||||||
|
Style::new().fg(Color::Blue).bg(Color::Green)
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.block(block);
|
||||||
|
Widget::render(list_w, rect, buf);
|
||||||
|
}
|
||||||
|
RenderBuffer::List { list, prefix, .. } => {
|
||||||
|
self.max_scroll = if list.len() > content_height {
|
||||||
|
(list.len() - content_height) as i16
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let scroll_offset = self.max_scroll.saturating_sub(self.scroll) as u16;
|
||||||
|
let list_w = List::new(
|
||||||
|
list
|
||||||
|
.iter()
|
||||||
|
.skip(scroll_offset as usize)
|
||||||
|
.take(content_height)
|
||||||
|
.map(|s| {
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
prefix,
|
||||||
|
textwrap::fill(s, content_width.saturating_sub(2))
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.block(block);
|
||||||
|
Widget::render(list_w, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
307
node/src/renderer/renderer.rs
Normal file
307
node/src/renderer/renderer.rs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use ratatui::{Frame, buffer::Buffer, layout::Rect, widgets::Widget};
|
||||||
|
|
||||||
|
use vlogger::*;
|
||||||
|
use tokio::time::{Duration, timeout};
|
||||||
|
|
||||||
|
use crate::log;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InputMode {
|
||||||
|
Input,
|
||||||
|
PopUp(Arc<Vec<String>>, String, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Renderer {
|
||||||
|
buffer: String,
|
||||||
|
exit: bool,
|
||||||
|
layout: RenderLayout,
|
||||||
|
mode: InputMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RenderCommand {
|
||||||
|
RenderStringToPaneId {
|
||||||
|
str: String,
|
||||||
|
pane: RenderTarget,
|
||||||
|
},
|
||||||
|
RenderStringToPaneFocused {
|
||||||
|
str: String,
|
||||||
|
},
|
||||||
|
RenderKeyInput(KeyCode),
|
||||||
|
ListMove {
|
||||||
|
pane: RenderTarget,
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
|
ChangeLayout(RenderLayoutKind),
|
||||||
|
ClearPane,
|
||||||
|
|
||||||
|
/// Mouse Events
|
||||||
|
MouseClickLeft(u16, u16),
|
||||||
|
MouseScrollUp,
|
||||||
|
MouseScrollDown,
|
||||||
|
|
||||||
|
SetMode(InputMode),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new(layout: RenderLayoutKind) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: String::new(),
|
||||||
|
exit: false,
|
||||||
|
layout: RenderLayout::generate(layout),
|
||||||
|
mode: InputMode::Input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&mut self, msg: String) {
|
||||||
|
self.buffer.push_str(&msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, frame: &mut Frame) {
|
||||||
|
frame.render_widget(self, frame.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(&mut self) {
|
||||||
|
log!(DEBUG, "Renderer Exit");
|
||||||
|
self.exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_extend<S: AsRef<str>>(&mut self, input: S) {
|
||||||
|
self.buffer.push_str(input.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_pane(&mut self) -> Option<&mut Pane> {
|
||||||
|
self
|
||||||
|
.layout
|
||||||
|
.panes
|
||||||
|
.iter_mut()
|
||||||
|
.find(|p| p.target == RenderTarget::CliInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pane(&mut self, pane: RenderTarget) -> Option<&mut Pane> {
|
||||||
|
self.layout.panes.iter_mut().find(|p| p.target == pane)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focused(&mut self) -> Option<&mut Pane> {
|
||||||
|
self.layout.panes.iter_mut().find(|p| p.focused == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> {
|
||||||
|
self.layout.rects(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_mouse_click_left(&mut self, x: u16, y: u16, rects: std::rc::Rc<[Rect]>) {
|
||||||
|
for (i, r) in rects.iter().enumerate() {
|
||||||
|
if r.contains(ratatui::layout::Position { x, y }) {
|
||||||
|
self.layout.panes[i].focused = true;
|
||||||
|
} else {
|
||||||
|
self.layout.panes[i].focused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_scroll_up(&mut self) {
|
||||||
|
if let Some(p) = self.focused() {
|
||||||
|
if p.scroll < p.max_scroll {
|
||||||
|
p.scroll += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_scroll_down(&mut self) {
|
||||||
|
if let Some(p) = self.focused() {
|
||||||
|
if p.scroll > i16::MIN {
|
||||||
|
p.scroll -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_char_input(&mut self, c: char) {
|
||||||
|
if let Some(p) = self.input_pane() {
|
||||||
|
if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
|
||||||
|
list[*index].push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_backspace(&mut self) {
|
||||||
|
if let Some(p) = self.input_pane() {
|
||||||
|
if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
|
||||||
|
list[*index].pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_enter(&mut self) {
|
||||||
|
if let Some(p) = self.input_pane() {
|
||||||
|
if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
|
||||||
|
list.push(String::new());
|
||||||
|
*index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_arrow_key(&mut self, key: KeyCode) {
|
||||||
|
match &mut self.mode {
|
||||||
|
InputMode::Input => {}
|
||||||
|
InputMode::PopUp(content, .., idx) => {
|
||||||
|
log(msg!(DEBUG, "Received keycode: {key}"));
|
||||||
|
log(msg!(DEBUG, "idx before: {idx}"));
|
||||||
|
match key {
|
||||||
|
KeyCode::Up => { *idx = idx.saturating_sub(1) }
|
||||||
|
KeyCode::Down => {
|
||||||
|
if *idx < content.len().saturating_sub(1) {
|
||||||
|
*idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
log(msg!(DEBUG, "idx after: {idx}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(pane) = self.focused() {
|
||||||
|
match &pane.target {
|
||||||
|
RenderTarget::CliInput => {}
|
||||||
|
RenderTarget::CliOutput => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_move(&mut self, pane: RenderTarget, index: usize) {
|
||||||
|
if let Some(p) = self.get_pane(pane) {
|
||||||
|
if let RenderBuffer::List {
|
||||||
|
list, index: idx, ..
|
||||||
|
} = &mut p.buffer
|
||||||
|
{
|
||||||
|
if index > 0 && index < list.len() {
|
||||||
|
list[*idx] = list[index].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_string_to_focused(&mut self, str: String) {
|
||||||
|
if let Some(p) = self.focused() {
|
||||||
|
match &mut p.buffer {
|
||||||
|
RenderBuffer::List { list, index, .. } => {
|
||||||
|
list.push(str);
|
||||||
|
*index += 1;
|
||||||
|
}
|
||||||
|
RenderBuffer::String(s) => s.push_str(&str),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_string_to_id(&mut self, str: String, pane: RenderTarget) {
|
||||||
|
if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
||||||
|
match &mut p.buffer {
|
||||||
|
RenderBuffer::List { list, index, .. } => {
|
||||||
|
list.push(str);
|
||||||
|
*index += 1;
|
||||||
|
}
|
||||||
|
RenderBuffer::String(s) => s.push_str(&str),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_pane(&mut self, pane: RenderTarget) {
|
||||||
|
if matches!(pane, RenderTarget::All) {
|
||||||
|
for p in self.layout.panes.iter_mut() {
|
||||||
|
match &mut p.buffer {
|
||||||
|
RenderBuffer::List { list, index, .. } => {
|
||||||
|
list.clear();
|
||||||
|
*index = 0;
|
||||||
|
list.push(String::new());
|
||||||
|
}
|
||||||
|
RenderBuffer::String(s) => s.clear(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
||||||
|
match &mut p.buffer {
|
||||||
|
RenderBuffer::List { list, index, .. } => {
|
||||||
|
list.clear();
|
||||||
|
*index = 0;
|
||||||
|
list.push(String::new());
|
||||||
|
}
|
||||||
|
RenderBuffer::String(s) => s.clear(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&mut self, mes: RenderCommand, area: Rect) {
|
||||||
|
let rects = self.layout.rects(area);
|
||||||
|
match mes {
|
||||||
|
RenderCommand::MouseClickLeft(x, y) => self.handle_mouse_click_left(x, y, rects),
|
||||||
|
RenderCommand::MouseScrollUp => self.handle_scroll_up(),
|
||||||
|
RenderCommand::MouseScrollDown => self.handle_scroll_down(),
|
||||||
|
RenderCommand::RenderKeyInput(k) => match k {
|
||||||
|
KeyCode::Char(c) => self.handle_char_input(c),
|
||||||
|
KeyCode::Backspace => self.handle_backspace(),
|
||||||
|
KeyCode::Enter => self.handle_enter(),
|
||||||
|
KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => self.handle_arrow_key(k),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
RenderCommand::ListMove { pane, index } => self.list_move(pane, index),
|
||||||
|
RenderCommand::RenderStringToPaneFocused { str } => self.render_string_to_focused(str),
|
||||||
|
RenderCommand::RenderStringToPaneId { str, pane } => self.render_string_to_id(str, pane),
|
||||||
|
RenderCommand::Exit => self.exit(),
|
||||||
|
RenderCommand::ChangeLayout(l) => self.layout = RenderLayout::generate(l),
|
||||||
|
RenderCommand::ClearPane => self.clear_pane(RenderTarget::All),
|
||||||
|
RenderCommand::SetMode(mode) => {
|
||||||
|
match &mode {
|
||||||
|
InputMode::Input => {
|
||||||
|
if let InputMode::PopUp(..) = self.mode {
|
||||||
|
self.layout.panes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputMode::PopUp(content, title, ..) => {
|
||||||
|
let pane = Pane::new(
|
||||||
|
Some(title.to_string()),
|
||||||
|
RenderTarget::PopUp,
|
||||||
|
RenderBuffer::Select(content.clone(), 0),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.layout.panes.push(pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.mode = mode
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn listen(
|
||||||
|
&mut self,
|
||||||
|
rx: &mut tokio::sync::broadcast::Receiver<RenderCommand>,
|
||||||
|
) -> Result<RenderCommand, ()> {
|
||||||
|
if let Ok(Ok(mes)) = timeout(Duration::from_millis(400), rx.recv()).await {
|
||||||
|
return Ok(mes);
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &mut Renderer {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let rects = self.layout.rects(area);
|
||||||
|
for (i, p) in self.layout.panes.iter_mut().enumerate() {
|
||||||
|
if p.target == RenderTarget::PopUp {
|
||||||
|
p.render(area, buf);
|
||||||
|
} else {
|
||||||
|
p.render(rects[i], buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
node/src/watcher/builder.rs
Normal file
150
node/src/watcher/builder.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use vlogger::*;
|
||||||
|
|
||||||
|
use crate::bus::{NetworkEvent, SystemEvent, subscribe_system_event};
|
||||||
|
use crate::core;
|
||||||
|
use crate::executor::{Executor, ExecutorCommand};
|
||||||
|
use crate::log;
|
||||||
|
use crate::node::{Node, NodeCommand};
|
||||||
|
use crate::renderer::{RenderLayoutKind, Renderer};
|
||||||
|
use crate::watcher::Watcher;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct WatcherBuilder {
|
||||||
|
addr: Option<SocketAddr>,
|
||||||
|
database: Option<String>,
|
||||||
|
bootstrap: bool,
|
||||||
|
debug: bool,
|
||||||
|
seed: bool,
|
||||||
|
render: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatcherBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addr(mut self, addr: Option<SocketAddr>) -> Self {
|
||||||
|
self.addr = addr;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn database(mut self, database: Option<String>) -> Self {
|
||||||
|
self.database = database;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(mut self, debug: bool) -> Self {
|
||||||
|
self.debug = debug;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bootstrap(mut self, bootstrap: bool) -> Self {
|
||||||
|
self.bootstrap = bootstrap;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(mut self, render: bool) -> Self {
|
||||||
|
self.render = render;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seed(mut self, seed: bool) -> Self {
|
||||||
|
self.seed = seed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(mut self) -> Watcher {
|
||||||
|
let (exec_tx, exec_rx) = mpsc::channel::<ExecutorCommand>(100);
|
||||||
|
let mut sys_event = subscribe_system_event();
|
||||||
|
|
||||||
|
if self.debug {
|
||||||
|
Watcher::log_memory().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let renderer = Renderer::new(RenderLayoutKind::CliHorizontal);
|
||||||
|
|
||||||
|
log(msg!(DEBUG, "Database Location: {:?}", self.database));
|
||||||
|
if self.seed {
|
||||||
|
self.addr = Some(crate::seeds_constants::SEED_NODES[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chain = core::Blockchain::build(None).unwrap();
|
||||||
|
let mut node = Node::new(self.addr.clone(), exec_tx.clone(), chain);
|
||||||
|
log(msg!(INFO, "Built Node"));
|
||||||
|
|
||||||
|
let executor_handle = tokio::spawn({
|
||||||
|
let node_tx = node.tx();
|
||||||
|
async move {
|
||||||
|
let _ = Executor::new(node_tx, exec_rx).run().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 0..3 {
|
||||||
|
if let Ok(ev) = sys_event.recv().await {
|
||||||
|
match ev {
|
||||||
|
SystemEvent::ExecutorStarted => {
|
||||||
|
log(msg!(INFO, "Executor Started"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node_tx = node.tx();
|
||||||
|
let node_handle = tokio::spawn({
|
||||||
|
async move {
|
||||||
|
node.run().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 0..3 {
|
||||||
|
if let Ok(ev) = sys_event.recv().await {
|
||||||
|
match ev {
|
||||||
|
SystemEvent::NodeStarted => {
|
||||||
|
log(msg!(INFO, "Executor Started"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.bootstrap {
|
||||||
|
let exec_tx = exec_tx.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let seed_cmd = ExecutorCommand::Node(NodeCommand::ConnectToSeeds);
|
||||||
|
let mut ev_rx = crate::bus::subscribe_network_event();
|
||||||
|
let _ = exec_tx.send(seed_cmd).await;
|
||||||
|
|
||||||
|
while let Ok(e) = ev_rx.recv().await {
|
||||||
|
match e {
|
||||||
|
NetworkEvent::SeedConnected(_) => {
|
||||||
|
let bootstrap_cmd = ExecutorCommand::Node(NodeCommand::BootStrap);
|
||||||
|
let _ = exec_tx.send(bootstrap_cmd).await;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd_history = Vec::new();
|
||||||
|
let history_index = 0;
|
||||||
|
let cmd_buffer = String::new();
|
||||||
|
let handles = vec![executor_handle, node_handle];
|
||||||
|
Watcher::new(
|
||||||
|
node_tx,
|
||||||
|
exec_tx,
|
||||||
|
cmd_buffer,
|
||||||
|
cmd_history,
|
||||||
|
history_index,
|
||||||
|
handles,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
node/src/watcher/command.rs
Normal file
20
node/src/watcher/command.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{executor::ExecutorCommand, renderer::RenderCommand};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WatcherCommand {
|
||||||
|
Render(RenderCommand),
|
||||||
|
SetMode(WatcherMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WatcherMode {
|
||||||
|
Input,
|
||||||
|
Select{
|
||||||
|
content: Arc<Vec<String>>,
|
||||||
|
title: String,
|
||||||
|
callback: Box<ExecutorCommand>,
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,101 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
bus::{SystemEvent, publish_render_event, publish_system_event},
|
|
||||||
log,
|
|
||||||
node::node::NodeCommand,
|
|
||||||
watcher::renderer::*,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use vlogger::*;
|
|
||||||
|
|
||||||
use super::RenderCommand;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum InProcessError {
|
|
||||||
#[error("TODO: {0}")]
|
|
||||||
TODO(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ExecutorCommand {
|
|
||||||
NodeResponse(String),
|
|
||||||
Echo(Vec<String>),
|
|
||||||
Print(String),
|
|
||||||
InvalidCommand(String),
|
|
||||||
Node(NodeCommand),
|
|
||||||
Render(RenderCommand),
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Executor {
|
|
||||||
node_tx: mpsc::Sender<NodeCommand>,
|
|
||||||
rx: mpsc::Receiver<ExecutorCommand>,
|
|
||||||
exit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Executor {
|
|
||||||
pub fn new(node_tx: mpsc::Sender<NodeCommand>, rx: mpsc::Receiver<ExecutorCommand>) -> Self {
|
|
||||||
Self {
|
|
||||||
node_tx,
|
|
||||||
rx,
|
|
||||||
exit: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) {
|
|
||||||
publish_system_event(SystemEvent::ExecutorStarted);
|
|
||||||
while !self.exit {
|
|
||||||
self.listen().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn exit(&mut self) {
|
|
||||||
log(msg!(DEBUG, "Executor Exit"));
|
|
||||||
self.exit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn listen(&mut self) {
|
|
||||||
if let Some(cmd) = self.rx.recv().await {
|
|
||||||
let _ = self.execute(cmd).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_node_cmd(&self, cmd: NodeCommand) {
|
|
||||||
self.node_tx.send(cmd).await.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_node_cmd(&self, cmd: NodeCommand) {
|
|
||||||
self.send_node_cmd(cmd).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn echo(&self, s: Vec<String>) {
|
|
||||||
let mut str = s.join(" ");
|
|
||||||
str.push_str("\n");
|
|
||||||
let rd_cmd = RenderCommand::RenderStringToPane {
|
|
||||||
str,
|
|
||||||
pane: RenderPane::CliOutput,
|
|
||||||
};
|
|
||||||
publish_render_event(rd_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn invalid_command(&self, str: String) {
|
|
||||||
let rd_cmd = RenderCommand::RenderStringToPane {
|
|
||||||
str,
|
|
||||||
pane: RenderPane::CliOutput,
|
|
||||||
};
|
|
||||||
publish_render_event(rd_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute(&mut self, cmd: ExecutorCommand) {
|
|
||||||
match cmd {
|
|
||||||
ExecutorCommand::NodeResponse(resp) => log(resp),
|
|
||||||
ExecutorCommand::Node(n) => self.handle_node_cmd(n).await,
|
|
||||||
ExecutorCommand::Render(p) => publish_render_event(p),
|
|
||||||
ExecutorCommand::Echo(s) => self.echo(s).await,
|
|
||||||
ExecutorCommand::Print(s) => log(s),
|
|
||||||
ExecutorCommand::InvalidCommand(str) => self.invalid_command(str).await,
|
|
||||||
ExecutorCommand::Exit => self.exit().await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
use crate::cli::cli;
|
|
||||||
use crate::watcher::executor::ExecutorCommand;
|
|
||||||
use vlogger::*;
|
|
||||||
|
|
||||||
use tokio::time::{Duration, timeout};
|
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Parser {
|
|
||||||
rx: mpsc::Receiver<ParserCommand>,
|
|
||||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
|
||||||
exit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ParserCommand {
|
|
||||||
ParseCmdString(String),
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parser {
|
|
||||||
pub fn new(rx: mpsc::Receiver<ParserCommand>, exec_tx: mpsc::Sender<ExecutorCommand>) -> Self {
|
|
||||||
Self {
|
|
||||||
rx,
|
|
||||||
exec_tx,
|
|
||||||
exit: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn exit(&mut self) {
|
|
||||||
self.log(msg!(DEBUG, "Parser Exit")).await;
|
|
||||||
self.exit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) {
|
|
||||||
self.log(msg!(INFO, "Started Parser")).await;
|
|
||||||
while !self.exit {
|
|
||||||
self.listen().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn log(&self, msg: String) {
|
|
||||||
if let Err(e) = self.exec_tx.send(ExecutorCommand::Print(msg)).await {
|
|
||||||
log!(ERROR, "Error response from exec: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn listen(&mut self) {
|
|
||||||
if let Ok(Some(mes)) = timeout(Duration::from_millis(400), self.rx.recv()).await {
|
|
||||||
match mes {
|
|
||||||
ParserCommand::ParseCmdString(s) => {
|
|
||||||
let argv: Vec<&str> =
|
|
||||||
std::iter::once(" ").chain(s.split_whitespace()).collect();
|
|
||||||
let cmd = cli(&argv);
|
|
||||||
let _ = self.exec_tx.send(cmd).await;
|
|
||||||
}
|
|
||||||
ParserCommand::Exit => {
|
|
||||||
self.exit().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,405 +0,0 @@
|
|||||||
use crossterm::event::KeyCode;
|
|
||||||
use ratatui::prelude::*;
|
|
||||||
use ratatui::widgets::Wrap;
|
|
||||||
use ratatui::{
|
|
||||||
Frame,
|
|
||||||
buffer::Buffer,
|
|
||||||
layout::Rect,
|
|
||||||
symbols::border,
|
|
||||||
widgets::{Block, List, Paragraph, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
use vlogger::*;
|
|
||||||
|
|
||||||
use crate::bus::{SystemEvent, publish_system_event, subscribe_render_event};
|
|
||||||
use std::io;
|
|
||||||
use tokio::time::{Duration, interval, timeout};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Renderer {
|
|
||||||
buffer: String,
|
|
||||||
exit: bool,
|
|
||||||
layout: RenderLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Pane {
|
|
||||||
title: Option<String>,
|
|
||||||
target: RenderPane,
|
|
||||||
buffer: RenderBuffer,
|
|
||||||
focused: bool,
|
|
||||||
scroll: i16,
|
|
||||||
max_scroll: i16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, clap::ValueEnum)]
|
|
||||||
pub enum RenderPane {
|
|
||||||
#[value(name = "all", aliases = ["a"])]
|
|
||||||
All,
|
|
||||||
#[value(aliases = ["i", "in"])]
|
|
||||||
CliInput,
|
|
||||||
#[value(aliases = ["o", "out"])]
|
|
||||||
CliOutput,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum RenderBuffer {
|
|
||||||
List { list: Vec<String>, index: usize },
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pane {
|
|
||||||
fn render(&mut self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let block = Block::bordered()
|
|
||||||
.title({
|
|
||||||
if let Some(t) = &self.title {
|
|
||||||
t.clone()
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.border_set(border::PLAIN)
|
|
||||||
.border_style({
|
|
||||||
if self.focused {
|
|
||||||
Style::new().green()
|
|
||||||
} else {
|
|
||||||
Style::new().white()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let inner_area = block.inner(area);
|
|
||||||
let content_width = inner_area.width as usize;
|
|
||||||
let content_height = inner_area.height as usize;
|
|
||||||
match &self.buffer {
|
|
||||||
RenderBuffer::String(s) => {
|
|
||||||
let wrapped_lines = s
|
|
||||||
.lines()
|
|
||||||
.map(|line| {
|
|
||||||
if line.is_empty() {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
(line.len() + content_width - 1) / {
|
|
||||||
content_width + (content_width == 0) as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum::<usize>();
|
|
||||||
|
|
||||||
self.max_scroll = if wrapped_lines > content_height {
|
|
||||||
(wrapped_lines - content_height) as i16
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let scroll_offset = self.max_scroll.saturating_sub(self.scroll) as u16;
|
|
||||||
|
|
||||||
Paragraph::new(s.clone())
|
|
||||||
.wrap(Wrap::default())
|
|
||||||
.left_aligned()
|
|
||||||
.block(block)
|
|
||||||
.scroll((scroll_offset as u16, 0))
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
RenderBuffer::List { list, .. } => {
|
|
||||||
let list_w =
|
|
||||||
List::new(list.iter().map(|s| {
|
|
||||||
format!("> {}", textwrap::fill(s, content_width.saturating_sub(2)))
|
|
||||||
}))
|
|
||||||
.block(block);
|
|
||||||
Widget::render(list_w, area, buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum RenderCommand {
|
|
||||||
RenderStringToPane {
|
|
||||||
str: String,
|
|
||||||
pane: RenderPane,
|
|
||||||
},
|
|
||||||
RenderInput(KeyCode),
|
|
||||||
ListMove {
|
|
||||||
pane: RenderPane,
|
|
||||||
index: usize,
|
|
||||||
},
|
|
||||||
ChangeLayout(RenderLayoutKind),
|
|
||||||
ClearPane(RenderPane),
|
|
||||||
|
|
||||||
/// Mouse Events
|
|
||||||
MouseClickLeft(u16, u16),
|
|
||||||
MouseScrollUp,
|
|
||||||
MouseScrollDown,
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
|
||||||
pub enum RenderLayoutKind {
|
|
||||||
#[value(name = "horizontal", aliases = ["h"])]
|
|
||||||
CliHorizontal,
|
|
||||||
#[value(name = "vertical", aliases = ["v"])]
|
|
||||||
CliVertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RenderLayout {
|
|
||||||
kind: RenderLayoutKind,
|
|
||||||
panes: Vec<Pane>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderLayoutKind {
|
|
||||||
pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> {
|
|
||||||
match self {
|
|
||||||
Self::CliHorizontal => Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(vec![Constraint::Percentage(70), Constraint::Percentage(30)])
|
|
||||||
.split(area),
|
|
||||||
Self::CliVertical => Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints(vec![Constraint::Percentage(30), Constraint::Percentage(70)])
|
|
||||||
.split(area),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate(&self) -> RenderLayout {
|
|
||||||
match self {
|
|
||||||
RenderLayoutKind::CliVertical => RenderLayout {
|
|
||||||
kind: self.clone(),
|
|
||||||
panes: vec![
|
|
||||||
Pane {
|
|
||||||
title: Some(" Input Pane ".to_string()),
|
|
||||||
target: RenderPane::CliInput,
|
|
||||||
buffer: RenderBuffer::List {
|
|
||||||
list: vec![String::new()],
|
|
||||||
index: 0,
|
|
||||||
},
|
|
||||||
focused: true,
|
|
||||||
scroll: 0,
|
|
||||||
max_scroll: 0,
|
|
||||||
},
|
|
||||||
Pane {
|
|
||||||
title: Some(" Output Pane ".to_string()),
|
|
||||||
target: RenderPane::CliOutput,
|
|
||||||
buffer: RenderBuffer::String(String::new()),
|
|
||||||
focused: false,
|
|
||||||
scroll: 0,
|
|
||||||
max_scroll: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
RenderLayoutKind::CliHorizontal => RenderLayout {
|
|
||||||
kind: self.clone(),
|
|
||||||
panes: vec![
|
|
||||||
Pane {
|
|
||||||
title: Some(" Output Pane ".to_string()),
|
|
||||||
target: RenderPane::CliOutput,
|
|
||||||
buffer: RenderBuffer::String(String::new()),
|
|
||||||
focused: false,
|
|
||||||
scroll: 0,
|
|
||||||
max_scroll: 0,
|
|
||||||
},
|
|
||||||
Pane {
|
|
||||||
title: Some(" Input Pane ".to_string()),
|
|
||||||
target: RenderPane::CliInput,
|
|
||||||
buffer: RenderBuffer::List {
|
|
||||||
list: vec![String::new()],
|
|
||||||
index: 0,
|
|
||||||
},
|
|
||||||
focused: true,
|
|
||||||
scroll: 0,
|
|
||||||
max_scroll: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl Renderer {
|
|
||||||
pub fn new(layout: RenderLayoutKind) -> Self {
|
|
||||||
Self {
|
|
||||||
buffer: String::new(),
|
|
||||||
exit: false,
|
|
||||||
layout: layout.generate(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) -> io::Result<()> {
|
|
||||||
self.log(msg!(INFO, "Started Renderer"));
|
|
||||||
let mut rx = subscribe_render_event();
|
|
||||||
let mut terminal = ratatui::init();
|
|
||||||
publish_system_event(SystemEvent::RendererStarted);
|
|
||||||
|
|
||||||
let mut render_interval = interval(Duration::from_millis(32)); // 60 FPS
|
|
||||||
|
|
||||||
while !self.exit {
|
|
||||||
tokio::select! {
|
|
||||||
_ = render_interval.tick() => {
|
|
||||||
terminal.draw(|frame| self.draw(frame))?;
|
|
||||||
}
|
|
||||||
mes = rx.recv() => {
|
|
||||||
if let Ok(mes) = mes {
|
|
||||||
let frame = terminal.get_frame();
|
|
||||||
let rects = self.layout.kind.rects(frame.area());
|
|
||||||
self.apply(mes, rects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ratatui::restore();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&mut self, msg: String) {
|
|
||||||
self.buffer.push_str(&msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&mut self, frame: &mut Frame) {
|
|
||||||
frame.render_widget(self, frame.area());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit(&mut self) {
|
|
||||||
log!(DEBUG, "Renderer Exit");
|
|
||||||
self.exit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buffer_extend<S: AsRef<str>>(&mut self, input: S) {
|
|
||||||
self.buffer.push_str(input.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_pane(&mut self) -> Option<&mut Pane> {
|
|
||||||
self.layout
|
|
||||||
.panes
|
|
||||||
.iter_mut()
|
|
||||||
.find(|p| p.target == RenderPane::CliInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focused(&mut self) -> Option<&mut Pane> {
|
|
||||||
self.layout.panes.iter_mut().find(|p| p.focused == true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_mouse_click_left(&mut self, x: u16, y: u16, rects: std::rc::Rc<[Rect]>) {
|
|
||||||
for (i, r) in rects.iter().enumerate() {
|
|
||||||
if r.contains(layout::Position { x, y }) {
|
|
||||||
self.layout.panes[i].focused = true;
|
|
||||||
} else {
|
|
||||||
self.layout.panes[i].focused = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(&mut self, mes: RenderCommand, rects: std::rc::Rc<[Rect]>) {
|
|
||||||
match mes {
|
|
||||||
RenderCommand::MouseClickLeft(x, y) => {
|
|
||||||
self.handle_mouse_click_left(x, y, rects);
|
|
||||||
}
|
|
||||||
RenderCommand::MouseScrollUp => {
|
|
||||||
if let Some(p) = self.focused() {
|
|
||||||
if p.scroll < p.max_scroll {
|
|
||||||
p.scroll += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RenderCommand::MouseScrollDown => {
|
|
||||||
if let Some(p) = self.focused() {
|
|
||||||
if p.scroll > i16::MIN {
|
|
||||||
p.scroll -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RenderCommand::RenderInput(k) => {
|
|
||||||
if let Some(p) = self.layout.panes.iter_mut().find(|p| p.focused) {
|
|
||||||
match k {
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let RenderBuffer::List { list, index } = &mut p.buffer {
|
|
||||||
list[*index].push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let RenderBuffer::List { list, index } = &mut p.buffer {
|
|
||||||
list[*index].pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if let RenderBuffer::List { list, index } = &mut p.buffer {
|
|
||||||
list.push(String::new());
|
|
||||||
*index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand::ListMove { pane, index } => {
|
|
||||||
if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
|
||||||
if let RenderBuffer::List { list, index: idx } = &mut p.buffer {
|
|
||||||
if index > 0 && index < list.len() {
|
|
||||||
list[*idx] = list[index].clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand::RenderStringToPane { str, pane } => {
|
|
||||||
if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
|
||||||
match &mut p.buffer {
|
|
||||||
RenderBuffer::List { list, index } => {
|
|
||||||
list.push(str);
|
|
||||||
*index += 1;
|
|
||||||
}
|
|
||||||
RenderBuffer::String(s) => s.push_str(&str),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RenderCommand::Exit => {
|
|
||||||
self.exit();
|
|
||||||
}
|
|
||||||
RenderCommand::ChangeLayout(l) => {
|
|
||||||
self.layout = l.generate();
|
|
||||||
}
|
|
||||||
RenderCommand::ClearPane(pane) => {
|
|
||||||
if matches!(pane, RenderPane::All) {
|
|
||||||
for p in self.layout.panes.iter_mut() {
|
|
||||||
match &mut p.buffer {
|
|
||||||
RenderBuffer::List { list, index } => {
|
|
||||||
list.clear();
|
|
||||||
*index = 0;
|
|
||||||
list.push(String::new());
|
|
||||||
}
|
|
||||||
RenderBuffer::String(s) => s.clear(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
|
||||||
match &mut p.buffer {
|
|
||||||
RenderBuffer::List { list, index } => {
|
|
||||||
list.clear();
|
|
||||||
*index = 0;
|
|
||||||
list.push(String::new());
|
|
||||||
}
|
|
||||||
RenderBuffer::String(s) => s.clear(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn listen(
|
|
||||||
&mut self,
|
|
||||||
rx: &mut tokio::sync::broadcast::Receiver<RenderCommand>,
|
|
||||||
) -> Result<RenderCommand, ()> {
|
|
||||||
if let Ok(Ok(mes)) = timeout(Duration::from_millis(400), rx.recv()).await {
|
|
||||||
return Ok(mes);
|
|
||||||
}
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for &mut Renderer {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let rects = self.layout.kind.rects(area);
|
|
||||||
for (i, p) in self.layout.panes.iter_mut().enumerate() {
|
|
||||||
p.render(rects[i], buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +1,200 @@
|
|||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseButton, MouseEventKind};
|
use crate::{cli::cli, error::print_error_chain, node::node::NodeCommand, watcher::WatcherMode};
|
||||||
|
use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind, MouseButton, MouseEventKind};
|
||||||
|
use futures::StreamExt;
|
||||||
use memory_stats::memory_stats;
|
use memory_stats::memory_stats;
|
||||||
use std::{
|
use ratatui::{layout::Rect, Terminal};
|
||||||
io::{self, Write},
|
use std::io::{self, Stdout, Write};
|
||||||
net::SocketAddr,
|
use tokio::{
|
||||||
time::Duration,
|
select,
|
||||||
};
|
sync::mpsc,
|
||||||
use tokio::sync::mpsc;
|
time::{Duration, interval},
|
||||||
|
|
||||||
use crate::{
|
|
||||||
bus::{subscribe_system_event, NetworkEvent, SystemEvent}, core, node::node::{Node, NodeCommand}
|
|
||||||
};
|
};
|
||||||
use vlogger::*;
|
use vlogger::*;
|
||||||
|
|
||||||
use super::*;
|
use super::{ WatcherBuilder, WatcherCommand };
|
||||||
|
|
||||||
use crate::bus::{ publish_render_event };
|
use crate::bus::subscribe_watcher_event;
|
||||||
|
use crate::executor::*;
|
||||||
use crate::log;
|
use crate::log;
|
||||||
|
|
||||||
|
use crate::renderer::*;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Watcher {
|
pub struct Watcher {
|
||||||
parser_tx: mpsc::Sender<ParserCommand>,
|
|
||||||
node_tx: mpsc::Sender<NodeCommand>,
|
node_tx: mpsc::Sender<NodeCommand>,
|
||||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||||
cmd_buffer: String,
|
cmd_buffer: String,
|
||||||
cmd_history: Vec<String>,
|
cmd_history: Vec<String>,
|
||||||
history_index: usize,
|
history_index: usize,
|
||||||
handles: Vec<tokio::task::JoinHandle<()>>,
|
handles: Vec<tokio::task::JoinHandle<()>>,
|
||||||
|
event_stream: crossterm::event::EventStream,
|
||||||
|
mode: WatcherMode,
|
||||||
|
pub renderer: Renderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Watcher {
|
impl Watcher {
|
||||||
pub fn build() -> WatcherBuilder {
|
pub fn new(
|
||||||
WatcherBuilder::new()
|
node_tx: mpsc::Sender<NodeCommand>,
|
||||||
|
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||||
|
cmd_buffer: String,
|
||||||
|
cmd_history: Vec<String>,
|
||||||
|
history_index: usize,
|
||||||
|
handles: Vec<tokio::task::JoinHandle<()>>,
|
||||||
|
renderer: Renderer,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
node_tx,
|
||||||
|
exec_tx,
|
||||||
|
cmd_buffer,
|
||||||
|
cmd_history,
|
||||||
|
history_index,
|
||||||
|
handles,
|
||||||
|
renderer,
|
||||||
|
mode: WatcherMode::Input,
|
||||||
|
event_stream: EventStream::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parser_tx(&self) -> mpsc::Sender<ParserCommand> {
|
fn init(&self) -> io::Result<()>{
|
||||||
self.parser_tx.clone()
|
crossterm::execute!(
|
||||||
|
std::io::stdout(),
|
||||||
|
crossterm::event::EnableBracketedPaste,
|
||||||
|
crossterm::event::EnableFocusChange,
|
||||||
|
crossterm::event::EnableMouseCapture,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shutdown(&self) -> io::Result<()> {
|
||||||
|
ratatui::restore();
|
||||||
|
crossterm::execute!(
|
||||||
|
std::io::stdout(),
|
||||||
|
crossterm::event::DisableBracketedPaste,
|
||||||
|
crossterm::event::DisableFocusChange,
|
||||||
|
crossterm::event::DisableMouseCapture
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_cmd(&mut self, cmd: WatcherCommand, terminal: &mut Terminal<ratatui::backend::CrosstermBackend<Stdout>>) {
|
||||||
|
match cmd {
|
||||||
|
WatcherCommand::Render(rend_cmd) => {
|
||||||
|
let frame = terminal.get_frame();
|
||||||
|
self.renderer.apply(rend_cmd, frame.area());
|
||||||
|
}
|
||||||
|
WatcherCommand::SetMode(mode) => {
|
||||||
|
match &mode {
|
||||||
|
WatcherMode::Input => {}
|
||||||
|
WatcherMode::Select{content, title, ..} => {
|
||||||
|
let rd_cmd = RenderCommand::SetMode(InputMode::PopUp(content.clone(), title.clone(), 0));
|
||||||
|
let frame = terminal.get_frame();
|
||||||
|
self.renderer.apply(rd_cmd, frame.area());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> std::io::Result<()> {
|
||||||
|
let mut ui_rx = subscribe_watcher_event();
|
||||||
|
let mut render_interval = interval(Duration::from_millis(32));
|
||||||
|
let mut terminal = ratatui::init();
|
||||||
|
|
||||||
|
self.init()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
poll_res = self.poll() => {
|
||||||
|
match poll_res {
|
||||||
|
Ok(event) => {
|
||||||
|
match self.handle_event(event, terminal.get_frame().area()).await {
|
||||||
|
Ok(ret) => if !ret { self.exit(); break }
|
||||||
|
Err(e) => log(msg!(ERROR, "{}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(()) => { log(msg!(ERROR, "Failed to read from Stream")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui_event = ui_rx.recv() => {
|
||||||
|
match ui_event {
|
||||||
|
Ok(cmd) => {
|
||||||
|
self.handle_cmd(cmd, &mut terminal);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log(msg!(ERROR, "{}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = render_interval.tick() => {
|
||||||
|
terminal.draw(|frame| self.renderer.draw(frame))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build() -> WatcherBuilder {
|
||||||
|
WatcherBuilder::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_tx(&self) -> mpsc::Sender<ExecutorCommand> {
|
pub fn exec_tx(&self) -> mpsc::Sender<ExecutorCommand> {
|
||||||
self.exec_tx.clone()
|
self.exec_tx.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exit(self) {
|
pub fn exit(&self) {}
|
||||||
let rd_mes = RenderCommand::Exit;
|
|
||||||
let pr_mes = ParserCommand::Exit;
|
async fn handle_enter(&mut self) {
|
||||||
let exec_mes = ExecutorCommand::Exit;
|
match &self.mode {
|
||||||
let node_mes = NodeCommand::Exit;
|
WatcherMode::Input => {
|
||||||
publish_render_event(rd_mes);
|
if !self.cmd_buffer.is_empty() {
|
||||||
let _ = self.parser_tx.send(pr_mes).await;
|
let exec_event = cli(&self.cmd_buffer);
|
||||||
let _ = self.exec_tx.send(exec_mes).await;
|
let _ = self.exec_tx.send(exec_event).await;
|
||||||
let _ = self.node_tx.send(node_mes).await;
|
self.cmd_buffer.clear();
|
||||||
|
self.renderer.handle_enter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WatcherMode::Select { content, callback, index, .. } => {
|
||||||
|
match &&**callback {
|
||||||
|
&ExecutorCommand::Node(nd_cmd) => {
|
||||||
|
match nd_cmd {
|
||||||
|
NodeCommand::DisplayBlockByKey(_) => {
|
||||||
|
let key = (*content)[*index].clone().to_string();
|
||||||
|
let resp = ExecutorCommand::Node(NodeCommand::DisplayBlockByKey(key));
|
||||||
|
let _ = self.exec_tx.send(resp).await;
|
||||||
|
}
|
||||||
|
_ => {log(msg!(DEBUG, "TODO: Implement callback for {:?}", nd_cmd))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {log(msg!(DEBUG, "TODO: Implement callback for {:?}", *callback))}
|
||||||
|
}
|
||||||
|
self.mode = WatcherMode::Input;
|
||||||
|
let rd_cmd = RenderCommand::SetMode(InputMode::Input);
|
||||||
|
self.renderer.apply(rd_cmd, Rect::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_arrow_key(&mut self, key: KeyCode) {
|
||||||
|
match key {
|
||||||
|
KeyCode::Up => {
|
||||||
|
match &mut self.mode {
|
||||||
|
&mut WatcherMode::Select { ref mut index, .. } => {
|
||||||
|
*index = index.saturating_sub(1);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
match &mut self.mode {
|
||||||
|
&mut WatcherMode::Select { ref mut index, ref content, ..} => {
|
||||||
|
if *index < content.len().saturating_sub(1) {
|
||||||
|
*index = index.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn log_memory() {
|
pub async fn log_memory() {
|
||||||
@ -91,240 +236,59 @@ impl Watcher {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn poll(&mut self) -> io::Result<bool> {
|
pub async fn handle_event(&mut self, event: Event, area: Rect) -> io::Result<bool> {
|
||||||
match event::read()? {
|
match event {
|
||||||
Event::Mouse(event) => match event.kind {
|
Event::Mouse(event) => match event.kind {
|
||||||
MouseEventKind::ScrollUp => {
|
MouseEventKind::ScrollUp => {
|
||||||
publish_render_event(RenderCommand::MouseScrollUp);
|
self.renderer.handle_scroll_up();
|
||||||
}
|
}
|
||||||
MouseEventKind::ScrollDown => {
|
MouseEventKind::ScrollDown => {
|
||||||
publish_render_event(RenderCommand::MouseScrollDown);
|
self.renderer.handle_scroll_down();
|
||||||
}
|
}
|
||||||
MouseEventKind::Down(b) => match b {
|
MouseEventKind::Down(b) => match b {
|
||||||
MouseButton::Left => {
|
MouseButton::Left => {
|
||||||
publish_render_event(RenderCommand::MouseClickLeft(
|
let rects = self.renderer.rects(area);
|
||||||
event.column,
|
self
|
||||||
event.row,
|
.renderer
|
||||||
));
|
.handle_mouse_click_left(event.column, event.row, rects);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::Key(k) if k.kind == KeyEventKind::Press => {
|
Event::Key(k) if k.kind == KeyEventKind::Press => match k.code {
|
||||||
match k.code {
|
KeyCode::Esc => return Ok(false),
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
self.cmd_buffer.push(c);
|
self.cmd_buffer.push(c);
|
||||||
let message = RenderCommand::RenderInput(k.code);
|
self.renderer.handle_char_input(c)
|
||||||
publish_render_event(message);
|
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
self.cmd_buffer.pop();
|
self.cmd_buffer.pop();
|
||||||
let message = RenderCommand::RenderInput(k.code);
|
self.renderer.handle_backspace()
|
||||||
publish_render_event(message);
|
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
let rd_mes = RenderCommand::RenderInput(k.code);
|
self.handle_enter().await;
|
||||||
let pr_mes = ParserCommand::ParseCmdString(self.cmd_buffer.clone());
|
|
||||||
let _ = self.parser_tx.send(pr_mes).await;
|
|
||||||
publish_render_event(rd_mes);
|
|
||||||
self.cmd_buffer.clear();
|
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => {
|
||||||
if self.history_index > 0 {
|
self.handle_arrow_key(k.code);
|
||||||
self.history_index -= 1;
|
self.renderer.handle_arrow_key(k.code)
|
||||||
let rd_mes = RenderCommand::ListMove {
|
|
||||||
pane: RenderPane::CliInput,
|
|
||||||
index: self.history_index,
|
|
||||||
};
|
|
||||||
publish_render_event(rd_mes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
if self.history_index < self.cmd_buffer.len() {
|
|
||||||
self.history_index += 1;
|
|
||||||
let rd_mes = RenderCommand::ListMove {
|
|
||||||
pane: RenderPane::CliInput,
|
|
||||||
index: self.history_index,
|
|
||||||
};
|
|
||||||
publish_render_event(rd_mes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Esc => {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
},
|
||||||
|
Event::Paste(text) => {
|
||||||
|
log(msg!(DEBUG, "Received pasted text: {text}"));
|
||||||
|
self.renderer.render_string_to_focused(text);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
pub async fn poll(&mut self) -> Result<Event, ()> {
|
||||||
pub struct WatcherBuilder {
|
match self.event_stream.next().await {
|
||||||
addr: Option<SocketAddr>,
|
Some(Ok(event)) => Ok(event),
|
||||||
database: Option<String>,
|
Some(Err(e)) => Err(print_error_chain(&e.into())),
|
||||||
bootstrap: bool,
|
None => Err(()),
|
||||||
debug: bool,
|
|
||||||
seed: bool,
|
|
||||||
render: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WatcherBuilder {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr(mut self, addr: Option<SocketAddr>) -> Self {
|
|
||||||
self.addr = addr;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn database(mut self, database: Option<String>) -> Self {
|
|
||||||
self.database = database;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug(mut self, debug: bool) -> Self {
|
|
||||||
self.debug = debug;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bootstrap(mut self, bootstrap: bool) -> Self {
|
|
||||||
self.bootstrap = bootstrap;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(mut self, render: bool) -> Self {
|
|
||||||
self.render = render;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seed(mut self, seed: bool) -> Self {
|
|
||||||
self.seed = seed;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start(mut self) -> Watcher {
|
|
||||||
let (parser_tx, parser_rx) = mpsc::channel::<ParserCommand>(100);
|
|
||||||
let (exec_tx, exec_rx) = mpsc::channel::<ExecutorCommand>(100);
|
|
||||||
let mut sys_event = subscribe_system_event();
|
|
||||||
|
|
||||||
if self.debug {
|
|
||||||
Watcher::log_memory().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_handle = if self.render {
|
|
||||||
Some(tokio::spawn({
|
|
||||||
async move {
|
|
||||||
let _ = Renderer::new(RenderLayoutKind::CliHorizontal).run().await;
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
if let Ok(ev) = sys_event.recv().await {
|
|
||||||
match ev {
|
|
||||||
SystemEvent::RendererStarted => {
|
|
||||||
log(msg!(INFO, "Renderer Started"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let parser_handle = tokio::spawn({
|
|
||||||
let exec_tx = exec_tx.clone();
|
|
||||||
async move {
|
|
||||||
let _ = Parser::new(parser_rx, exec_tx).run().await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
log(msg!(DEBUG, "Database Location: {:?}", self.database));
|
|
||||||
if self.seed {
|
|
||||||
self.addr = Some(crate::seeds_constants::SEED_NODES[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chain = core::Blockchain::build(None).unwrap();
|
|
||||||
let mut node = Node::new(self.addr.clone(), exec_tx.clone(), chain);
|
|
||||||
log(msg!(INFO, "Built Node"));
|
|
||||||
|
|
||||||
let executor_handle = tokio::spawn({
|
|
||||||
let node_tx = node.tx();
|
|
||||||
async move {
|
|
||||||
let _ = Executor::new(node_tx, exec_rx).run().await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
if let Ok(ev) = sys_event.recv().await {
|
|
||||||
match ev {
|
|
||||||
SystemEvent::ExecutorStarted => {
|
|
||||||
log(msg!(INFO, "Executor Started"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let node_tx = node.tx();
|
|
||||||
let node_handle = tokio::spawn({
|
|
||||||
async move {
|
|
||||||
node.run().await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
if let Ok(ev) = sys_event.recv().await {
|
|
||||||
match ev {
|
|
||||||
SystemEvent::NodeStarted => {
|
|
||||||
log(msg!(INFO, "Executor Started"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.bootstrap {
|
|
||||||
let exec_tx = exec_tx.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let seed_cmd = ExecutorCommand::Node(NodeCommand::ConnectToSeeds);
|
|
||||||
let mut ev_rx = crate::bus::subscribe_network_event();
|
|
||||||
let _ = exec_tx.send(seed_cmd).await;
|
|
||||||
|
|
||||||
while let Ok(e) = ev_rx.recv().await {
|
|
||||||
match e {
|
|
||||||
NetworkEvent::SeedConnected(_) => {
|
|
||||||
let bootstrap_cmd = ExecutorCommand::Node(NodeCommand::BootStrap);
|
|
||||||
let _ = exec_tx.send(bootstrap_cmd).await;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Watcher {
|
|
||||||
node_tx,
|
|
||||||
cmd_history: Vec::new(),
|
|
||||||
history_index: 0,
|
|
||||||
parser_tx,
|
|
||||||
exec_tx,
|
|
||||||
cmd_buffer: String::new(),
|
|
||||||
handles: {
|
|
||||||
let mut h = vec![parser_handle, executor_handle, node_handle];
|
|
||||||
if render_handle.is_some() {
|
|
||||||
h.push(render_handle.unwrap());
|
|
||||||
}
|
|
||||||
h
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user