diff --git a/src/blockchain/blockchain.rs b/src/blockchain/blockchain.rs index a648e7d..8d83a82 100644 --- a/src/blockchain/blockchain.rs +++ b/src/blockchain/blockchain.rs @@ -14,6 +14,7 @@ pub struct Blockchain { pub version: u32, pub blocks: Vec, last_block: Option, + max_height: u64, db: Connection, zones: RefCell> } @@ -24,7 +25,7 @@ impl Blockchain { let version = settings.version; let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); - let mut blockchain = Blockchain{ origin, version, blocks: Vec::new(), last_block: None, db, zones: RefCell::new(HashSet::new()) }; + let mut blockchain = Blockchain{ origin, version, blocks: Vec::new(), last_block: None, max_height: 0, db, zones: RefCell::new(HashSet::new()) }; blockchain.init_db(); blockchain } @@ -243,6 +244,16 @@ impl Blockchain { } } + pub fn max_height(&self) -> u64 { + self.max_height + } + + pub fn update_max_height(&mut self, height: u64) { + if height > self.max_height { + self.max_height = height; + } + } + /*pub fn check(&self) -> bool { let mut prev_block = None; for block in self.blocks.iter() { diff --git a/src/event.rs b/src/event.rs index 4ca0c2a..ea3e247 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,10 +4,13 @@ pub enum Event { MinerStopped, KeyGeneratorStarted, KeyGeneratorStopped, - KeyCreated {path: String, public: String}, - KeyLoaded {path: String, public: String}, - KeySaved {path: String, public: String}, + KeyCreated { path: String, public: String }, + KeyLoaded { path: String, public: String }, + KeySaved { path: String, public: String }, NewBlockReceived, BlockchainChanged, ActionStopMining, + StatsCount { nodes: usize, blocks: u64 }, + SyncStarted { have: u64, height: u64 }, + ActionIdle, } diff --git a/src/main.rs b/src/main.rs index d5137a9..abb6b45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,11 +56,11 @@ fn main() { let program = args[0].clone(); let mut opts = Options::new(); - opts.optflag("h","help", "Print this help menu"); - opts.optflag("n","nogui","Run without graphic user interface"); - opts.optflag("v","verbose","Show more debug messages"); - opts.optflag("d","debug","Show trace messages, more than debug"); - opts.optopt("c","config","Path to config file", ""); + opts.optflag("h", "help", "Print this help menu"); + opts.optflag("n", "nogui", "Run without graphic user interface"); + opts.optflag("v", "verbose", "Show more debug messages"); + opts.optflag("d", "debug", "Show trace messages, more than debug"); + opts.optopt("c", "config", "Path to config file", ""); let opt_matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -161,8 +161,8 @@ fn create_genesis_if_needed(context: &Arc>, miner: &Arc>, miner: Arc>) { let file_content = include_str!("webview/index.html"); - let mut styles= inline_style(include_str!("webview/bulma.css")); - styles.push_str(&inline_style(include_str!("webview/miner.css"))); + let mut styles = inline_style(include_str!("webview/bulma.css")); + styles.push_str(&inline_style(include_str!("webview/busy_indicator.css"))); let scripts = inline_script(include_str!("webview/scripts.js")); let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts)); @@ -189,10 +189,19 @@ fn run_interface(context: Arc>, miner: Arc>) { Event::KeyCreated { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } Event::KeyLoaded { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } Event::KeySaved { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } - Event::MinerStarted => { format!("showMiningIndicator({});", true) } - Event::KeyGeneratorStarted => { format!("showMiningIndicator({});", true) } - Event::MinerStopped => { format!("showMiningIndicator({});", false) } - Event::KeyGeneratorStopped => { format!("showMiningIndicator({});", false) } + Event::MinerStarted => { format!("showMiningIndicator({}, false);", true) } + Event::KeyGeneratorStarted => { format!("showMiningIndicator({}, false);", true) } + Event::MinerStopped => { format!("showMiningIndicator({}, false);", false) } + Event::KeyGeneratorStopped => { format!("showMiningIndicator({}, false);", false) } + Event::SyncStarted { have, height } => { + format!("setLeftStatusBarText('Synchronizing {}/{}'); showMiningIndicator(true, true);", have, height) + } + Event::ActionIdle => { + format!("setLeftStatusBarText('Idle'); showMiningIndicator(false, true);") + } + Event::StatsCount { nodes, blocks } => { + format!("setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) + } _ => { String::new() } }; @@ -216,17 +225,17 @@ fn run_interface(context: Arc>, miner: Arc>) { match Keystore::from_file(&file_name, "") { None => { error!("Error loading keystore '{}'!", &file_name); - }, + } Some(keystore) => { info!("Loaded keystore with key: {:?}", &keystore.get_public()); let mut c = context.lock().unwrap(); - c.bus.post(Event::KeyLoaded {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()}); + c.bus.post(Event::KeyLoaded { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() }); c.set_keystore(keystore); } } } } - }, + } CreateKey {} => { create_key(context.clone()); } @@ -240,11 +249,11 @@ fn run_interface(context: Arc>, miner: Arc>) { let public = c.keystore.get_public().to_string(); c.keystore.save(&new_path, ""); info!("Key file saved to {}", &path); - c.bus.post(Event::KeySaved {path, public }); + c.bus.post(Event::KeySaved { path, public }); } } } - CheckDomain { name} => { + CheckDomain { name } => { let name = name.to_lowercase(); let c = context.lock().unwrap(); let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore()); @@ -282,7 +291,7 @@ fn run_interface(context: Arc>, miner: Arc>) { ChangeDomain { .. } => {} RenewDomain { .. } => {} TransferDomain { .. } => {} - CheckZone { name} => { + CheckZone { name } => { let name = name.to_lowercase(); if !check_domain(&name, false) { web_view.eval("zoneAvailable(false)").expect("Error evaluating!"); @@ -398,8 +407,8 @@ fn create_key(context: Arc>) { Some(keystore) => { info!("Key mined successfully: {:?}", &keystore.get_public()); let mut c = context.lock().unwrap(); - mining.store(false,Ordering::Relaxed); - c.bus.post(Event::KeyCreated {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()}); + mining.store(false, Ordering::Relaxed); + c.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() }); c.set_keystore(keystore); } } @@ -440,7 +449,7 @@ fn create_server_context(context: Arc>, settings: &Settings) -> A server_context.dns_port = settings.dns.port; server_context.resolve_strategy = match settings.dns.forwarders.is_empty() { true => { ResolveStrategy::Recursive } - false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() }} + false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() } } }; server_context.filters.push(Box::new(BlockchainFilter::new(context))); match server_context.initialize() { @@ -455,16 +464,16 @@ fn create_server_context(context: Arc>, settings: &Settings) -> A #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { Loaded, - LoadKey{}, - CreateKey{}, - SaveKey{}, - CheckZone{name: String}, - CreateZone{name: String, data: String}, - CheckDomain{name: String}, - CreateDomain{name: String, records: String, tags: String}, - ChangeDomain{name: String, records: String, tags: String}, - RenewDomain{name: String, days: u16}, - TransferDomain{name: String, owner: String}, + LoadKey {}, + CreateKey {}, + SaveKey {}, + CheckZone { name: String }, + CreateZone { name: String, data: String }, + CheckDomain { name: String }, + CreateDomain { name: String, records: String, tags: String }, + ChangeDomain { name: String, records: String, tags: String }, + RenewDomain { name: String, days: u16 }, + TransferDomain { name: String, owner: String }, StopMining, } diff --git a/src/p2p/network.rs b/src/p2p/network.rs index 71276d3..31302d0 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -91,6 +91,9 @@ impl Network { token => { if !handle_connection_event(context.clone(), &mut peers, &poll.registry(), &event) { let _ = peers.close_peer(poll.registry(), &token); + let mut context = context.lock().unwrap(); + let blocks_count = context.blockchain.height(); + context.bus.post(crate::event::Event::StatsCount { nodes: peers.get_peers_active_count(), blocks: blocks_count }); } } } @@ -123,9 +126,10 @@ fn handle_connection_event(context: Arc>, peers: &mut Peers, regi let data = data.unwrap(); match Message::from_bytes(data) { Ok(message) => { - debug!("Got message from socket {}: {:?}", &event.token().0, &message); + let m = format!("{:?}", &message); let new_state = handle_message(context.clone(), message, peers, &event.token()); let peer = peers.get_mut_peer(&event.token()).unwrap(); + debug!("Got message from {}: {:?}", &peer.get_addr(), &m); let stream = peer.get_stream(); match new_state { State::Message { data } => { @@ -163,17 +167,17 @@ fn handle_connection_event(context: Arc>, peers: &mut Peers, regi Some(peer) => { match peer.get_state().clone() { State::Connecting => { - debug!("Sending hello to socket {}", event.token().0); + debug!("Sending hello to {}", &peer.get_addr()); let data: String = { let c = context.lock().unwrap(); let message = Message::hand(&c.settings.origin, c.settings.version, c.settings.public); serde_json::to_string(&message).unwrap() }; send_message(peer.get_stream(), &data.into_bytes()); - debug!("Sent hello through socket {}", event.token().0); + debug!("Sent hello to {}", &peer.get_addr()); } State::Message { data } => { - debug!("Sending data to socket {}: {}", event.token().0, &String::from_utf8(data.clone()).unwrap()); + debug!("Sending data to {}: {}", &peer.get_addr(), &String::from_utf8(data.clone()).unwrap()); send_message(peer.get_stream(), &data); } State::Connected => {} @@ -273,9 +277,16 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe return State::Error; } if ok { + let active_count = peers.get_peers_active_count(); let peer = peers.get_mut_peer(token).unwrap(); peer.set_height(height); + peer.set_active(true); + let mut context = context.lock().unwrap(); + let blocks_count = context.blockchain.height(); + context.bus.post(crate::event::Event::StatsCount { nodes: active_count, blocks: blocks_count }); if peer.is_higher(my_height) { + context.blockchain.update_max_height(height); + context.bus.post(crate::event::Event::SyncStarted { have: my_height, height}); State::message(Message::GetBlock { index: my_height }) } else { State::message(Message::GetPeers) @@ -288,6 +299,7 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe Message::Ping { height } => { let peer = peers.get_mut_peer(token).unwrap(); peer.set_height(height); + peer.set_active(true); if peer.is_higher(my_height) { State::message(Message::GetBlock { index: my_height }) } else { @@ -297,9 +309,14 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe Message::Pong { height } => { let peer = peers.get_mut_peer(token).unwrap(); peer.set_height(height); + peer.set_active(true); if peer.is_higher(my_height) { State::message(Message::GetBlock { index: my_height }) } else { + let mut context = context.lock().unwrap(); + let blocks_count = context.blockchain.height(); + context.bus.post(crate::event::Event::ActionIdle); + context.bus.post(crate::event::Event::StatsCount { nodes: peers.get_peers_active_count(), blocks: blocks_count }); State::idle() } } @@ -324,12 +341,22 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe Ok(block) => block, Err(_) => return State::Error }; - // TODO check if the block is good + // TODO check here if the block is good before trying to add let context = context.clone(); thread::spawn(move || { let mut context = context.lock().unwrap(); + let max_height = context.blockchain.max_height(); match context.blockchain.add_block(block) { - Ok(_) => { context.bus.post(crate::event::Event::BlockchainChanged); } + Ok(_) => { + let my_height = context.blockchain.height(); + context.bus.post(crate::event::Event::BlockchainChanged); + // If it was the last block to sync + if my_height == max_height { + context.bus.post(crate::event::Event::ActionIdle); + } else { + context.bus.post(crate::event::Event::SyncStarted { have: my_height, height: max_height}); + } + } Err(_) => { warn!("Discarded received block"); } } }); diff --git a/src/p2p/peer.rs b/src/p2p/peer.rs index 18efaf8..88a1b12 100644 --- a/src/p2p/peer.rs +++ b/src/p2p/peer.rs @@ -10,11 +10,12 @@ pub struct Peer { height: u64, inbound: bool, public: bool, + active: bool, } impl Peer { pub fn new(addr: SocketAddr, stream: TcpStream, state: State, inbound: bool) -> Self { - Peer { addr, stream, state, height: 0, inbound, public: false } + Peer { addr, stream, state, height: 0, inbound, public: false, active: false } } pub fn get_addr(&self) -> SocketAddr { @@ -53,8 +54,12 @@ impl Peer { self.public = public; } + pub fn set_active(&mut self, active: bool) { + self.active = active; + } + pub fn active(&self) -> bool { - self.state.active() + self.active } pub fn disabled(&self) -> bool { diff --git a/src/p2p/peers.rs b/src/p2p/peers.rs index 3e27cea..8696928 100644 --- a/src/p2p/peers.rs +++ b/src/p2p/peers.rs @@ -45,6 +45,7 @@ impl Peers { if !peer.disabled() && !peer.is_inbound() { peer.set_state(State::offline()); + peer.set_active(false); } else { self.peers.remove(token); } @@ -112,6 +113,16 @@ impl Peers { result } + pub fn get_peers_active_count(&self) -> usize { + let mut count = 0; + for (_, peer) in self.peers.iter() { + if peer.active() { + count += 1; + } + } + count + } + pub fn skip_peer_connection(&self, addr: &SocketAddr) -> bool { for (_, peer) in self.peers.iter() { if peer.equals(addr) && (!peer.is_public() || peer.active() || peer.disabled()) { diff --git a/src/p2p/state.rs b/src/p2p/state.rs index d91eea2..8972d01 100644 --- a/src/p2p/state.rs +++ b/src/p2p/state.rs @@ -26,16 +26,6 @@ impl State { State::Message {data: Vec::from(response.as_bytes()) } } - pub fn active(&self) -> bool { - match self { - State::Connecting => { true } - State::Connected => { true } - State::Idle { .. } => { true } - State::Message { .. } => { true } - _ => { false } - } - } - pub fn is_idle(&self) -> bool { match self { State::Idle { .. } => { true } diff --git a/src/webview/bulma.css b/src/webview/bulma.css index c262663..57f060a 100644 --- a/src/webview/bulma.css +++ b/src/webview/bulma.css @@ -10846,4 +10846,13 @@ html { width: 50%; top: 10pt; right: 10pt; +} + +.footer { + background-color: #f4f4f4; + padding: 0.2rem 0.5rem 0.2rem 0.5rem; + width: 100%; + position: absolute; + bottom: 0px; + z-index: 9; } \ No newline at end of file diff --git a/src/webview/busy_indicator.css b/src/webview/busy_indicator.css new file mode 100644 index 0000000..298c658 --- /dev/null +++ b/src/webview/busy_indicator.css @@ -0,0 +1,34 @@ +.busy_indicator { + width:24px; + height:24px; + display:inline-block; + padding:0px; + text-align:left; + float:left; + margin-left: -5px; +} +.busy_indicator span { + position:absolute; + display:inline-block; + width:24px; + height:24px; + border-radius:100%; + background:#ee3333; + -webkit-animation:busy_indicator 1.6s linear infinite; + animation:busy_indicator 1.6s linear infinite; +} +.busy_indicator span:last-child { + animation-delay:-0.8s; + -webkit-animation-delay:-0.8s; +} +@keyframes busy_indicator { + 0% {transform: scale(0, 0);opacity:0.9;} + 100% {transform: scale(1, 1);opacity:0;} +} +@-webkit-keyframes busy_indicator { + 0% {-webkit-transform: scale(0, 0);opacity:0.9;} + 100% {-webkit-transform: scale(1, 1);opacity:0;} +} +.busy_blue span { + background:#3273dc; +} \ No newline at end of file diff --git a/src/webview/index.html b/src/webview/index.html index 61260c0..5e52255 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -9,10 +9,6 @@ {scripts} -
- - -
- + + \ No newline at end of file diff --git a/src/webview/miner.css b/src/webview/miner.css deleted file mode 100644 index 7bb0e27..0000000 --- a/src/webview/miner.css +++ /dev/null @@ -1,34 +0,0 @@ -.mining_indicator { - position:absolute; - left:0px; - bottom:0px; - width:30px; - height:30px; - display:inline-block; - padding:0px; - text-align:left; - float:left; - z-index:10; -} -.mining_indicator span { - position:absolute; - display:inline-block; - width:30px; - height:30px; - border-radius:100%; - background:#ee3333; - -webkit-animation:mining_indicator 1.6s linear infinite; - animation:mining_indicator 1.6s linear infinite; -} -.mining_indicator span:last-child { - animation-delay:-0.8s; - -webkit-animation-delay:-0.8s; -} -@keyframes mining_indicator { - 0% {transform: scale(0, 0);opacity:0.5;} - 100% {transform: scale(1, 1);opacity:0;} -} -@-webkit-keyframes mining_indicator { - 0% {-webkit-transform: scale(0, 0);opacity:0.5;} - 100% {-webkit-transform: scale(1, 1);opacity:0;} -} \ No newline at end of file diff --git a/src/webview/scripts.js b/src/webview/scripts.js index cbdb11f..c4bb3a7 100644 --- a/src/webview/scripts.js +++ b/src/webview/scripts.js @@ -249,12 +249,19 @@ function showWarning(text) { setTimeout(button.onclick, 5000); } -function showMiningIndicator(visible) { - indicator = document.getElementById("mining_indicator"); +function showMiningIndicator(visible, blue) { + var indicator = document.getElementById("busy_indicator"); + var parent = document.getElementById("indicator_parent"); + var add = ""; + if (blue) { + add = " busy_blue"; + } if (visible) { - indicator.style.visibility = 'visible'; + indicator.className = 'busy_indicator' + add; + parent.style.display = 'flex'; } else { - indicator.style.visibility = 'hidden'; + indicator.className = 'busy_indicator is-hidden'; + parent.style.display = 'none'; } } @@ -264,6 +271,16 @@ function miningIndicatorClick(element) { }); } +function setLeftStatusBarText(text) { + var bar = document.getElementById("status_bar_left"); + bar.innerHTML = text; +} + +function setRightStatusBarText(text) { + var bar = document.getElementById("status_bar_right"); + bar.innerHTML = text; +} + function keystoreChanged(path, pub_key) { if (path == '') { path = "In memory";