pub mod tui;
mod webui;
use self::{tui::Tui, webui::WebUI};
use super::{
common::{config, paths::SearchPath},
data::ipc::IFCollectionOutputData,
net::subs::peer_representation::{self, PeerRepresentation},
};
use async_std::task;
use crossbeam::{channel::Receiver as CReceiver, sync::WaitGroup};
use libp2p::core::PeerId;
use std::{
collections::hash_map::DefaultHasher,
hash::Hasher,
io,
sync::{
mpsc::{channel, Receiver, Sender},
Arc, Mutex,
},
thread,
};
#[derive(Clone, Serialize, Deserialize)]
pub enum CollectionPathAlive {
BusyPath(usize),
HostSearch,
}
#[derive(Clone, Serialize)]
pub enum Status {
ON,
OFF,
}
#[derive(Clone)]
pub enum NetInfoMsg {
Debug(String),
ShowStats { show: NetStatsMsg },
}
#[derive(Clone)]
pub struct UiClientPeer {
pub id: PeerId,
pub addresses: Vec<String>,
}
#[derive(Clone)]
pub enum ForwardNetMsg {
Add(UiClientPeer),
Delete(PeerId),
Stats(NetInfoMsg),
}
pub enum InternalUiMsg {
Update(ForwardNetMsg),
StartAnimate(CollectionPathAlive, Status),
StepAndAnimate(CollectionPathAlive),
PeerSearchFinished(PeerId, IFCollectionOutputData),
Terminate,
}
#[derive(Clone)]
pub enum UiUpdateMsg {
NetUpdate(ForwardNetMsg),
CollectionUpdate(CollectionPathAlive, Status),
PeerSearchFinished(PeerId, IFCollectionOutputData),
StopUI,
}
#[derive(Copy, Clone)]
pub struct NetStatsMsg {
pub line: usize,
pub max: usize,
}
enum Finisher {
TUI,
WEBUI,
}
pub struct Ctrl {
peer_id: PeerId,
paths: Arc<Mutex<SearchPath>>,
with_net: bool,
}
impl Ctrl {
fn new(new_id: PeerId, paths: Arc<Mutex<SearchPath>>, with_net: bool) -> Self {
Self {
peer_id: new_id,
paths: paths.clone(),
with_net,
}
}
pub fn run(
new_id: PeerId,
paths: Arc<Mutex<SearchPath>>,
receiver: CReceiver<UiUpdateMsg>,
with_net: bool,
wait_main: WaitGroup,
has_webui: bool,
has_tui: bool,
open_browser: bool,
web_port: u16,
) -> Result<(), std::io::Error> {
let wait_all_uis = WaitGroup::new();
let (thread_finisher, finish_threads) = channel::<Finisher>();
let instance = Ctrl::new(new_id, paths, with_net);
let arc_self_tui = Arc::new(Mutex::new(instance));
let arc_self_webui = arc_self_tui.clone();
let mut internal_senders: Vec<Sender<InternalUiMsg>> = vec![];
let (sender_tui_to_register, receiver_to_tui_thread) = channel::<InternalUiMsg>();
let sender_tui_only_to_finish = sender_tui_to_register.clone();
let thread_tui = if has_tui {
let resender = sender_tui_to_register.clone();
let tui_waitgroup = wait_all_uis.clone();
let thread_finisher_tui = thread_finisher.clone();
internal_senders.push(sender_tui_to_register);
Self::spawn_tui(
arc_self_tui,
resender,
receiver_to_tui_thread,
tui_waitgroup,
thread_finisher_tui,
)?
} else {
std::thread::spawn(|| Ok(()))
};
let (sender_wui, receiver_to_web_ui_thread) = channel::<InternalUiMsg>();
let thread_webui = if has_webui {
let sender_to_register = sender_wui.clone();
let wui_waitgroup = wait_all_uis.clone();
let thread_finisher_tui = thread_finisher.clone();
internal_senders.push(sender_to_register);
Self::spawn_webui(
arc_self_webui,
receiver_to_web_ui_thread,
wui_waitgroup,
thread_finisher_tui,
open_browser,
web_port,
)?
} else {
std::thread::spawn(|| Ok(()))
};
let forwarding_message_loop = Self::spawn_message_loop(receiver, internal_senders);
info!("syncing with 2 other sub threads webui and tui");
wait_all_uis.wait();
info!("synced with 2 other sub threads webui and tui");
info!("waiting for main thread sync");
wait_main.wait();
info!("synced with main thread");
match finish_threads.recv() {
Ok(finished) => match finished {
Finisher::TUI => {
info!("TUI finished first, so send to terminate WEBUI!");
sender_wui.send(InternalUiMsg::Terminate).unwrap();
let to_pass_through = thread_tui.join().unwrap();
drop(forwarding_message_loop);
to_pass_through
}
Finisher::WEBUI => {
info!("WEBUI finished first, so send to terminate TUI!");
sender_tui_only_to_finish
.send(InternalUiMsg::Terminate)
.unwrap();
let to_pass_through = thread_webui.join().unwrap();
drop(forwarding_message_loop);
to_pass_through
}
},
Err(e) => {
error!("something really bad happenend: {}!!", e);
drop(thread_webui);
drop(thread_tui);
drop(forwarding_message_loop);
Ok::<(), std::io::Error>(())
}
}
}
fn spawn_webui(
this: Arc<Mutex<Self>>,
receiver: Receiver<InternalUiMsg>,
wait_ui_sync: WaitGroup,
thread_finisher: Sender<Finisher>,
open_browser: bool,
web_port: u16,
) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
let with_net;
let paths;
let mut hasher = DefaultHasher::new();
{
let unlocker = this.lock().unwrap();
paths = unlocker.paths.clone();
with_net = unlocker.with_net;
let peer_bytes = unlocker.peer_id.to_bytes();
hasher.write(peer_bytes.as_ref());
}
let peer_representation = hasher.finish();
thread::Builder::new().name("webui".into()).spawn(move || {
info!("start webui");
Self::run_webui(
receiver,
with_net,
peer_representation,
paths,
wait_ui_sync,
open_browser,
web_port,
)
.or_else(|forward| {
error!("error from webui-server: {}", forward);
Err(forward)
})?;
info!("stopped webui");
thread_finisher.send(Finisher::WEBUI).unwrap_or_else(|_| {
info!("probably receiver got tui finisher first!");
});
Ok::<(), std::io::Error>(())
})
}
fn spawn_tui(
this: Arc<Mutex<Self>>,
resender: Sender<InternalUiMsg>,
receiver: Receiver<InternalUiMsg>,
sync_startup: WaitGroup,
thread_finisher: Sender<Finisher>,
) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
let title;
let paths;
let with_net;
{
let unlocker = this.lock().unwrap();
title = peer_representation::peer_to_hash_string(&unlocker.peer_id);
paths = unlocker.paths.clone();
with_net = unlocker.with_net.clone();
}
std::thread::Builder::new()
.name("tui".into())
.spawn(move || {
trace!("tui waits for sync");
sync_startup.wait();
trace!("tui starts");
let fix_path = paths.lock().unwrap().read();
Self::run_tui(title, fix_path, with_net, receiver, resender).map_err(
|error_text| std::io::Error::new(std::io::ErrorKind::Other, error_text),
)?;
info!("stopped tui");
thread_finisher.send(Finisher::TUI).unwrap_or_else(|_| {
info!("probably receiver got webui finisher first!");
});
Ok::<(), std::io::Error>(())
})
}
fn spawn_message_loop(
receiver: CReceiver<UiUpdateMsg>,
multiplex_send: Vec<Sender<InternalUiMsg>>,
) -> Result<thread::JoinHandle<()>, std::io::Error> {
thread::Builder::new()
.name("ui msg".into())
.spawn(move || loop {
if !Self::run_message_forwarding(&receiver, &multiplex_send) {
break;
}
})
}
fn run_tui(
title: String,
paths: Vec<String>,
with_net: bool,
tui_receiver: Receiver<InternalUiMsg>,
resender: Sender<InternalUiMsg>,
) -> Result<(), String> {
info!("tui about to run");
info!("spawning tui async thread");
let mut tui = Tui::new(title, &paths, with_net)?;
task::block_on(async move {
loop {
if !tui.refresh().await {
break;
}
tui.run_cursive(&resender, &tui_receiver).await;
}
});
Ok(())
}
fn run_webui(
webui_receiver: Receiver<InternalUiMsg>,
net_support: bool,
peer_representation: PeerRepresentation,
paths: Arc<Mutex<SearchPath>>,
wait_ui_sync: WaitGroup,
open_browser: bool,
web_port: u16,
) -> io::Result<()> {
if open_browser {
if !try_open_browser(web_port) {
error!("Could not open browser!");
println!(
"Could not open browser, try opening manually: http://{}:{} to start!",
config::net::WEB_ADDR,
web_port
);
}
}
task::block_on(async move {
info!("spawning webui async thread");
let webui = WebUI::new(peer_representation, net_support, paths);
webui.run(webui_receiver, wait_ui_sync, web_port).await
})
}
fn run_message_forwarding(
receiver: &CReceiver<UiUpdateMsg>,
multiplex_send: &Vec<Sender<InternalUiMsg>>,
) -> bool {
if let Ok(forward_sys_message) = receiver.recv() {
match forward_sys_message {
UiUpdateMsg::NetUpdate(forward_net_message) => {
match forward_net_message {
ForwardNetMsg::Stats(_net_message) => {
}
ForwardNetMsg::Add(peer_to_add) => {
for forward_sender in multiplex_send {
forward_sender
.send(InternalUiMsg::Update( ForwardNetMsg::Add( peer_to_add.clone())))
.unwrap_or_else(|_| {
warn!("forwarding message cancelled probably due to quitting!");
});
}
}
ForwardNetMsg::Delete(peer_id_to_remove) => {
for forward_sender in multiplex_send {
forward_sender
.send(InternalUiMsg::Update( ForwardNetMsg::Delete( peer_id_to_remove.clone())))
.unwrap_or_else(|_| {
warn!("forwarding message cancelled probably due to quitting!");
});
}
}
}
true
}
UiUpdateMsg::CollectionUpdate(signal, on_off) => {
trace!(
"forwarding collection message to turn '{}'",
match on_off {
Status::ON => "on",
Status::OFF => "off",
}
);
for forward_sender in multiplex_send {
forward_sender
.send(InternalUiMsg::StartAnimate(signal.clone(), on_off.clone()))
.unwrap_or_else(|_| {
warn!("forwarding message cancelled probably due to quitting!");
});
}
true
}
UiUpdateMsg::PeerSearchFinished(peer_representation, data) => {
for forward_sender in multiplex_send {
forward_sender
.send(InternalUiMsg::PeerSearchFinished(
peer_representation.clone(),
data.clone(),
))
.unwrap_or_else(|_| {
warn!("forwarding message cancelled probably due to quitting!");
});
}
true
}
UiUpdateMsg::StopUI => {
trace!("stop all message forwarding to ui");
false
}
}
} else {
true
}
}
}
#[cfg(any(
target_os = "android",
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
targest_os = "netbsd",
target_os = "openbsd",
target_os = "haiku",
target_arch = "wasm32"
))]
fn try_open_browser(web_port: u16) -> bool {
webbrowser::open(
&["http://", config::net::WEB_ADDR, ":", &web_port.to_string()].concat(),
)
.is_ok()
}
#[cfg(not(any(
target_os = "android",
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "haiku",
target_arch = "wasm32"
)))]
pub fn try_open_browser(web_port: u16) -> bool {
false
}