use std ::time ::{ Duration , SystemTime } ;
use std ::net ::TcpStream ;
use eframe ::egui ;
use egui ::FontFamily ::Proportional ;
use egui ::FontId ;
use egui ::TextStyle ::* ;
use crate ::player ::Player ;
use crate ::tor ::GuiTor ;
use crate ::stats ::Market ;
use crate ::irc ::Client as IRCClient ;
use libtor ::Error as libtorError ;
pub struct App {
pub player : Player ,
pub market : Market ,
pub irc : IRCClient ,
pub tor_required : bool ,
pub tor_started : bool ,
pub tor_connected : bool ,
pub to_data : String ,
pub show_irc : bool ,
pub irc_message : String ,
pub irc_connected : bool ,
pub show_market_data : bool ,
pub show_radio : bool ,
pub irc_stream : Option < TcpStream > ,
pub irc_last_ping : SystemTime ,
pub irc_active_channel : String
}
impl Default for App {
fn default ( ) -> Self {
Self {
player : Player ::default ( ) ,
market : Market ::new ( ) ,
irc : IRCClient ::new ( ) ,
tor_started : false ,
tor_required : true ,
tor_connected : false ,
show_market_data : false ,
show_irc : false ,
irc_connected : false ,
to_data : "" . to_owned ( ) ,
irc_message : "" . to_owned ( ) ,
show_radio : true ,
irc_stream : None ,
irc_last_ping : SystemTime ::now ( ) ,
irc_active_channel : "#wownero-music" . to_owned ( )
}
}
}
fn show_boolmoji ( b : bool ) -> String {
if b { return "✔" . to_owned ( ) } else { return "☠" . to_owned ( ) }
}
impl eframe ::App for App {
fn update ( & mut self , ctx : & egui ::Context , _frame : & mut eframe ::Frame ) {
let mut style = ( * ctx . style ( ) ) . clone ( ) ;
style . text_styles = [
( Heading , FontId ::new ( 30.0 , Proportional ) ) ,
( Name ( "Heading2" . into ( ) ) , FontId ::new ( 25.0 , Proportional ) ) ,
( Name ( "Context" . into ( ) ) , FontId ::new ( 23.0 , Proportional ) ) ,
( Body , FontId ::new ( 18.0 , Proportional ) ) ,
( Monospace , FontId ::new ( 14.0 , Proportional ) ) ,
( Button , FontId ::new ( 14.0 , Proportional ) ) ,
( Small , FontId ::new ( 10.0 , Proportional ) ) ,
] . into ( ) ;
ctx . set_style ( style ) ;
ctx . set_visuals ( egui ::Visuals ::dark ( ) ) ;
egui ::TopBottomPanel ::top ( "top" ) . show ( ctx , | ui | {
ui . heading ( "Wownero Operations Center" ) ;
ui . label ( "Made by ya boi, lza_menace" ) ;
ui . hyperlink ( "https://lzahq.tech" ) ;
} ) ;
egui ::CentralPanel ::default ( ) . show ( ctx , | ui | {
ui . horizontal ( | ui | {
ui . checkbox ( & mut self . tor_required , "Require Tor" ) ;
if ( self . tor_connected & & self . tor_required ) | | ( ! self . tor_required ) {
ui . checkbox ( & mut self . show_market_data , "Show Market Data" ) ;
ui . checkbox ( & mut self . show_irc , "Show IRC" ) ;
}
ui . checkbox ( & mut self . show_radio , "Show Radio" ) ;
} ) ;
// Tor
if self . tor_required {
if self . tor_started {
ui . label (
egui ::RichText ::new (
format! (
"Tor Started: {} Tor Connected: {} Proxy Up: {}" ,
show_boolmoji ( self . tor_started ) ,
show_boolmoji ( self . tor_connected ) ,
show_boolmoji ( self . tor_connected )
)
)
) ;
} else {
if ui . button ( "Connect to the Tor network" ) . clicked ( ) {
let _t : std ::thread ::JoinHandle < Result < u8 , libtorError > > = GuiTor ::start_tor ( ) ;
self . tor_started = true ;
self . tor_connected = true ;
}
}
}
ui . separator ( ) ;
// WOW!Radio
if self . show_radio {
ui . heading ( egui ::RichText ::new ( "WOW!Radio" ) . color ( egui ::Color32 ::WHITE ) ) ;
ui . label ( egui ::RichText ::new ( "Your home for the most diabolical playlist of the century, made by the skeevers, scallywags, chupacabras, snails, and whores of the Wownero community. Join da chat to peep da scoop.\n" ) . color ( egui ::Color32 ::WHITE ) ) ;
ui . horizontal ( | ui | {
if self . player . playing {
if ui . button ( "⏸" ) . clicked ( ) {
let _ = self . player . sink . pause ( ) ;
self . player . playing = false ;
}
// if ui.button("⏹").clicked() {
// let _ = self.player.sink.stop();
// self.player.playing = false;
// let _ = std::fs::remove_file(crate::RADIO_STREAM);
// }
ui . add ( egui ::Slider ::new ( & mut self . player . volume , 0. 0 ..= 100.0 ) ) ;
self . player . sink . set_volume ( & self . player . volume / 100.0 ) ;
if self . player . sink . len ( ) ! = 1 {
let f = std ::fs ::File ::open ( crate ::RADIO_STREAM ) ;
if let Ok ( fo ) = f {
let file = std ::io ::BufReader ::new ( fo ) ;
let source = rodio ::Decoder ::new ( file ) ;
if source . is_err ( ) {
return ( )
}
let _ = self . player . sink . append ( source . unwrap ( ) ) ;
let _ = self . player . sink . play ( ) ;
} else {
return ( )
}
}
} else {
if ! self . tor_connected & & self . tor_required {
ui . label ( "Connect to the Tor network first." ) ;
} else {
if ui . button ( "▶" ) . clicked ( ) {
if ! self . tor_connected & & self . tor_required {
return ( ) ;
}
// If stream thread is done, start again
if self . player . stream_thread . is_finished ( ) {
self . player . stream_thread = self . player . start_radio_stream ( self . tor_required ) ;
}
let _ = self . player . sink . play ( ) ;
self . player . playing = true ;
}
}
}
} ) ;
// Show spinner when downloading, along with file size
if ! self . player . stream_thread . is_finished ( ) {
ui . horizontal ( | ui | {
ui . spinner ( ) ;
let size : u64 = self . player . get_radio_size ( ) ;
ui . label ( format! (
"{:?} -> {} ({} bytes)" ,
self . player . stream_source ,
crate ::RADIO_STREAM ,
size
) ) ;
} ) ;
}
// Show exif metadata when radio file available to read
if self . player . playing & & self . player . get_radio_size ( ) > 0 {
let rt = egui ::RichText ::new (
format! ( "\n{:?}" , self . player . stream_exif ) )
. color ( egui ::Color32 ::WHITE )
. size ( 18.0 ) ;
ui . label ( rt ) ;
let dur = self . player . exif_date . elapsed ( ) . unwrap ( ) ;
if dur > Duration ::from_secs ( 15 ) {
self . player . exif_date = SystemTime ::now ( ) ;
self . player . stream_exif = self . player . get_song_meta ( ) . unwrap ( ) ;
}
if ui . button ( " +1 " ) . clicked ( ) {
let _ = self . irc . send_tune ( ) ;
}
}
ui . separator ( ) ;
}
// IRC
if self . show_irc & & ! self . irc_connected {
let i = self . irc . run ( ) ;
if i . is_ok ( ) {
self . irc_stream = Some ( i . unwrap ( ) ) ;
self . irc_connected = true ;
}
} else if self . show_irc {
egui ::ScrollArea ::vertical ( ) . stick_to_bottom ( true ) . show ( ui , | ui | {
ui . label ( self . irc . read_irc_log ( self . irc_active_channel . clone ( ) ) ) ;
ui . horizontal ( | ui | {
egui ::ComboBox ::from_label ( "" )
. selected_text ( format! ( "{}" , self . irc_active_channel ) )
. show_ui ( ui , | ui | {
ui . selectable_value ( & mut self . irc_active_channel , "" . to_owned ( ) , "system" ) ;
ui . selectable_value ( & mut self . irc_active_channel , "#wownero-music" . to_owned ( ) , "#wownero-music" ) ;
ui . selectable_value ( & mut self . irc_active_channel , "#wownero" . to_owned ( ) , "#wownero" ) ;
}
) ;
ui . text_edit_singleline ( & mut self . irc_message ) ;
if ui . button ( "> Send <" ) . clicked ( ) {
let res = crate ::irc ::send_cmd ( & self . irc_stream . as_ref ( ) . unwrap ( ) , "PRIVMSG" , format! ( "{} :{}\r\n" , self . irc_active_channel , self . irc_message ) ) ;
if res . is_ok ( ) {
println! ( "wrote {} bytes to IRC: {}" , res . unwrap ( ) , self . irc_message ) ;
let _ = crate ::irc ::write_log ( format! ( "PRIVMSG {} :{}\r\n" , self . irc_active_channel , self . irc_message ) . as_str ( ) , false ) ;
} else {
eprintln! ( "error: {:?}" , res . err ( ) ) ;
}
self . irc_message = "" . to_owned ( ) ;
}
} ) ;
if ui . button ( "Clear IRC Log" ) . clicked ( ) {
let _ = std ::fs ::File ::create ( crate ::IRC_LOG ) ;
}
if self . irc_last_ping . elapsed ( ) . unwrap ( ) > Duration ::from_secs ( 60 ) {
let _ = crate ::irc ::send_cmd ( & self . irc_stream . as_ref ( ) . unwrap ( ) , "PING" , format! ( "" ) ) ;
self . irc_last_ping = SystemTime ::now ( ) ;
}
} ) ;
}
// Market
if self . show_market_data {
egui ::ComboBox ::from_label ( "Pick currency base." )
. selected_text ( format! ( "{}" , self . market . denomination ) )
. show_ui ( ui , | ui | {
ui . selectable_value ( & mut self . market . denomination , "sats" . to_owned ( ) , "sats" ) ;
ui . selectable_value ( & mut self . market . denomination , "usd" . to_owned ( ) , "usd" ) ;
ui . selectable_value ( & mut self . market . denomination , "eth" . to_owned ( ) , "eth" ) ;
}
) ;
if self . market . last_check_time . elapsed ( ) . unwrap ( ) > Duration ::from_secs ( 120 ) {
println! ( "[+] Refreshing WOW market data." ) ;
self . market . store_market_data ( self . tor_required ) ;
self . market . last_check_time = SystemTime ::now ( ) ;
}
if self . market . last_check_time . elapsed ( ) . unwrap ( ) < Duration ::from_secs ( 30 ) & & self . market . read_json_from_file ( ) . len ( ) = = 0 {
ui . horizontal ( | ui | {
ui . spinner ( ) ;
ui . label ( "Fetching market data..." ) ;
} ) ;
}
self . market . last_cg_data = self . market . read_json_from_file ( ) ;
let m = & self . market . last_cg_data ;
let md = & m [ "market_data" ] ;
ui . horizontal_wrapped ( | ui | {
ui . vertical ( | ui | {
ui . label ( "Current Price" ) ;
ui . heading ( egui ::RichText ::new ( md [ "current_price" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "All-Time High" ) ;
ui . heading ( egui ::RichText ::new ( md [ "ath" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "All-Time Low" ) ;
ui . heading ( egui ::RichText ::new ( md [ "atl" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "Total Volume" ) ;
ui . heading ( egui ::RichText ::new ( md [ "total_volume" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "Market Cap" ) ;
ui . heading ( egui ::RichText ::new ( md [ "market_cap" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
} ) ;
ui . vertical ( | ui | {
ui . label ( "PriceChg% (~24hrs)" ) ;
ui . heading ( egui ::RichText ::new ( md [ "price_change_percentage_24h_in_currency" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "PriceChg% (~7d)" ) ;
ui . heading ( egui ::RichText ::new ( md [ "price_change_percentage_7d_in_currency" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "PriceChg% (~14d)" ) ;
ui . heading ( egui ::RichText ::new ( md [ "price_change_percentage_14d_in_currency" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
ui . label ( "PriceChg% (~30d)" ) ;
ui . heading ( egui ::RichText ::new ( md [ "price_change_percentage_30d_in_currency" ] [ & self . market . denomination ] . to_string ( ) ) . strong ( ) ) ;
} ) ;
} ) ;
}
} ) ;
}
}