2021-04-18 16:55:10 +02:00
extern crate open ;
2021-03-10 22:21:50 +01:00
extern crate serde ;
extern crate serde_json ;
2021-04-18 16:55:10 +02:00
extern crate tinyfiledialogs as tfd ;
2021-03-10 22:21:50 +01:00
2021-04-15 12:21:41 +02:00
use std ::sync ::{ Arc , Mutex , MutexGuard };
2025-10-29 16:01:41 +01:00
use std ::sync ::atomic ::{ AtomicUsize , Ordering };
2021-03-10 22:21:50 +01:00
use std ::thread ;
2025-10-27 20:36:37 +01:00
use std ::time ::Duration ;
2021-03-10 22:21:50 +01:00
2021-05-02 12:55:51 +02:00
use alfis ::blockchain ::transaction ::DomainData ;
2021-04-10 09:47:21 +02:00
use alfis ::blockchain ::types ::MineResult ;
2021-05-02 12:55:51 +02:00
use alfis ::commons ::* ;
2021-06-09 20:36:36 +02:00
use alfis ::crypto ::CryptoBox ;
2021-03-10 22:21:50 +01:00
use alfis ::dns ::protocol ::DnsRecord ;
2021-04-10 09:47:21 +02:00
use alfis ::event ::Event ;
2021-06-09 20:36:36 +02:00
use alfis ::eventbus ::{ post , register };
2021-04-10 09:47:21 +02:00
use alfis ::miner ::Miner ;
2021-06-09 20:36:36 +02:00
use alfis ::{ keystore , Block , Bytes , Context , Keystore , Transaction };
2025-10-27 20:36:37 +01:00
use chrono ::{ Local , Utc };
2025-10-29 16:39:44 +01:00
use image ::GenericImageView ;
2021-06-09 20:36:36 +02:00
#[allow(unused_imports)]
use log ::{ debug , error , info , trace , warn , LevelFilter };
use serde ::{ Deserialize , Serialize };
2021-03-10 22:21:50 +01:00
use Cmd ::* ;
2021-04-10 09:47:21 +02:00
2025-10-27 20:36:37 +01:00
use tao ::{
event ::{ Event as TaoEvent , WindowEvent },
event_loop ::{ ControlFlow , EventLoopBuilder , EventLoopProxy },
window ::WindowBuilder ,
};
2025-10-27 22:09:11 +01:00
use tao ::dpi ::PhysicalPosition ;
2025-10-29 16:01:41 +01:00
use tray_icon ::menu ::{ Menu , MenuEvent , MenuItem };
use tray_icon ::{ TrayIconBuilder , TrayIconEvent };
2025-10-27 20:36:37 +01:00
use wry ::WebViewBuilder ;
2021-03-10 22:21:50 +01:00
2025-10-29 16:01:41 +01:00
pub fn run_interface ( context : Arc < Mutex < Context >> , miner : Arc < Mutex < Miner >> , hide : bool ) {
2021-03-10 22:21:50 +01:00
let file_content = include_str! ( "webview/index.html" );
let mut styles = inline_style ( include_str! ( "webview/bulma.css" ));
2021-03-24 19:06:22 +01:00
styles . push_str ( & inline_style ( include_str! ( "webview/styles.css" )));
2021-03-10 22:21:50 +01:00
styles . push_str ( & inline_style ( include_str! ( "webview/busy_indicator.css" )));
let scripts = inline_script ( include_str! ( "webview/scripts.js" ));
2025-10-27 20:36:37 +01:00
let html = file_content . to_owned (). replace ( "{styles}" , & styles ). replace ( "{scripts}" , & scripts );
2021-03-10 22:21:50 +01:00
let title = format! ( "ALFIS {} " , env! ( "CARGO_PKG_VERSION" ));
2025-10-27 20:36:37 +01:00
// Create event loop and window
let event_loop = EventLoopBuilder ::< UserEvent > ::with_user_event (). build ();
2025-10-29 16:01:41 +01:00
// Create tray menu
let tray_menu = Menu ::new ();
let show_item = MenuItem ::new ( "Show Window" , true , None );
let quit_item = MenuItem ::new ( "Quit" , true , None );
tray_menu . append ( & show_item ). unwrap ();
tray_menu . append ( & quit_item ). unwrap ();
#[cfg(windows)]
let icon = tray_icon ::Icon ::from_resource ( 1 , None ). unwrap ();
// Create tray icon
2025-10-29 16:39:44 +01:00
#[cfg(not(target_os = "windows" ))]
let icon = load_icon_from_png ();
let tray_icon = TrayIconBuilder ::new ()
2025-10-29 16:01:41 +01:00
. with_menu ( Box ::new ( tray_menu ))
. with_tooltip ( & title )
. with_icon ( icon )
. with_menu_on_left_click ( false )
. build ()
. unwrap ();
2025-10-27 20:36:37 +01:00
2025-10-28 13:24:34 +01:00
let window_size = tao ::dpi ::LogicalSize ::new ( 1024 , 720 );
2025-10-27 22:09:11 +01:00
// Get primary monitor and calculate center position
2025-10-28 13:24:34 +01:00
let position = match event_loop . primary_monitor () {
Some ( monitor ) => {
let monitor_size = monitor . size ();
let monitor_position = monitor . position ();
2025-10-27 22:09:11 +01:00
2025-10-28 13:24:34 +01:00
let scaled = window_size . to_physical ::< i32 > ( monitor . scale_factor ());
let center_x = monitor_position . x + ( monitor_size . width as i32 - scaled . width ) / 2 ;
let center_y = monitor_position . y + ( monitor_size . height as i32 - scaled . height ) / 2 ;
Some ( PhysicalPosition ::new ( center_x , center_y ))
}
None => None ,
};
2025-10-27 22:09:11 +01:00
2025-10-28 13:24:34 +01:00
let mut builder = WindowBuilder ::new ()
2025-10-27 20:36:37 +01:00
. with_title ( & title )
2025-10-27 22:09:11 +01:00
. with_inner_size ( window_size )
2025-10-27 20:36:37 +01:00
. with_min_inner_size ( tao ::dpi ::LogicalSize ::new ( 773 , 350 ))
. with_resizable ( true )
2025-10-29 16:01:41 +01:00
. with_visible ( ! hide );
2025-10-28 13:24:34 +01:00
if let Some ( position ) = position {
builder = builder . with_position ( position );
}
let window = builder . build ( & event_loop )
2025-10-27 22:09:11 +01:00
. expect ( "Failed to create the window" );
2025-10-27 20:36:37 +01:00
#[cfg(windows)]
{
use winapi ::um ::shellscalingapi ::SetProcessDpiAwareness ;
unsafe {
SetProcessDpiAwareness ( 2 );
2021-03-10 22:21:50 +01:00
}
2025-10-27 22:09:11 +01:00
use tao ::platform ::windows ::IconExtWindows ;
use tao ::window ::Icon ;
let icon = Icon ::from_resource ( 1 , None ). unwrap ();
window . set_window_icon ( Some ( icon ));
2021-03-10 22:21:50 +01:00
}
2021-03-18 00:16:17 +01:00
2025-10-27 20:36:37 +01:00
// Clone for the IPC handler
let context_ipc = Arc ::clone ( & context );
let miner_ipc = Arc ::clone ( & miner );
2025-10-29 16:01:41 +01:00
let proxy = event_loop . create_proxy ();
2025-10-27 20:36:37 +01:00
let proxy_ipc = proxy . clone ();
// Create webview
let builder = WebViewBuilder ::new ()
. with_transparent ( false )
. with_visible ( true )
2025-10-28 23:08:49 +01:00
. with_devtools ( cfg! ( debug_assertions ))
2025-10-27 20:36:37 +01:00
. with_html ( html ) // Using test HTML to verify wry works
. with_ipc_handler ( move | request | {
let body = request . body ();
debug! ( "Command {}" , body );
match serde_json ::from_str ( body ) {
Ok ( cmd ) => {
match cmd {
Loaded => {
let _ = proxy_ipc . send_event ( UserEvent ::Loaded );
}
LoadKey => {
action_load_key ( & context_ipc , & proxy_ipc );
}
CreateKey => {
keystore ::create_key ( Arc ::clone ( & context_ipc ));
}
SaveKey => {
action_save_key ( & context_ipc );
}
SelectKey { index } => {
action_select_key ( & context_ipc , & proxy_ipc , index );
}
CheckRecord { data } => {
let result = check_record ( & data );
let _ = proxy_ipc . send_event ( UserEvent ::EvalJs ( format! ( "recordOkay( {} )" , result )));
}
CheckDomain { name } => {
let available = check_domain_available ( & context_ipc , & name );
let _ = proxy_ipc . send_event ( UserEvent ::EvalJs ( format! ( "domainAvailable( {} )" , available )));
}
MineDomain { name , data , signing , encryption , renewal } => {
action_create_domain ( Arc ::clone ( & context_ipc ), Arc ::clone ( & miner_ipc ), & proxy_ipc , name , data , signing , encryption , renewal );
}
TransferDomain { name , owner } => {
info! ( "Transferring '{name}' to '{owner}'" );
}
StopMining => {
post ( Event ::ActionStopMining );
}
Open { link } => {
if open ::that ( & link ). is_err () {
let _ = proxy_ipc . send_event ( UserEvent ::ShowWarning ( "Something wrong, I can't open the link 😢" . to_string ()));
}
}
}
}
Err ( e ) => {
error! ( "Error parsing command: {}" , e );
2021-05-05 09:11:23 +02:00
}
}
2025-10-27 20:36:37 +01:00
});
2021-05-14 14:14:45 +02:00
2025-10-27 20:36:37 +01:00
#[cfg(not(target_os = "linux" ))]
let webview = builder . build ( & window ). unwrap ();
#[cfg(target_os = "linux" )]
let webview = {
use tao ::platform ::unix ::WindowExtUnix ;
use wry ::WebViewBuilderExtUnix ;
let vbox = window . default_vbox (). unwrap ();
builder . build_gtk ( vbox ). expect ( "Failed to build webview gtk object" )
};
2025-10-28 23:08:49 +01:00
// Disabling context menu on the page in release build
#[cfg(not(debug_assertions))]
let _ = webview . evaluate_script ( "document.addEventListener('contextmenu', e => e.preventDefault());" );
2021-05-14 14:14:45 +02:00
2025-10-27 20:36:37 +01:00
let webview = Arc ::new ( Mutex ::new ( webview ));
let webview_clone = Arc ::clone ( & webview );
2021-03-18 00:16:17 +01:00
2025-10-27 20:36:37 +01:00
// Setup event bus listener
let proxy_events = proxy . clone ();
2021-04-10 09:47:21 +02:00
let threads = context . lock (). unwrap (). settings . mining . threads ;
let threads = match threads {
0 => num_cpus ::get (),
_ => threads
};
2021-09-21 15:25:42 +02:00
let status = Arc ::new ( Mutex ::new ( UiStatus ::new ( threads )));
2025-10-29 16:01:41 +01:00
let connected_nodes = Arc ::new ( AtomicUsize ::new ( 0 ));
let nodes_copy = Arc ::clone ( & connected_nodes );
2021-04-15 12:21:41 +02:00
2021-05-10 00:49:01 +02:00
register ( move | _uuid , e | {
2021-03-26 18:22:43 +01:00
let status = Arc ::clone ( & status );
2025-10-27 20:36:37 +01:00
let proxy = proxy_events . clone ();
2025-10-29 16:01:41 +01:00
let nodes_copy = Arc ::clone ( & nodes_copy );
2025-10-27 20:36:37 +01:00
thread ::Builder ::new (). name ( String ::from ( "webui" )). spawn ( move || {
2021-03-26 18:22:43 +01:00
let mut status = status . lock (). unwrap ();
let eval = match e {
2021-04-06 00:31:50 +02:00
Event ::KeyCreated { path , public , hash } => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::LoadDomains );
let _ = proxy . send_event ( UserEvent ::SendKeysToUi );
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('luck', ' {} ', 'Key successfully created! Don \\ 't forget to save it!');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-04-06 11:21:31 +02:00
let mut s = format! ( "keystoreChanged(' {} ', ' {} ', ' {} ');" , & path , & public , & hash );
2021-04-15 10:26:05 +02:00
s . push_str ( " showSuccess('New key mined successfully! Save it to a safe place!')" );
2021-04-06 11:21:31 +02:00
s
2021-04-06 00:31:50 +02:00
}
2021-03-26 18:22:43 +01:00
Event ::KeyLoaded { path , public , hash } |
Event ::KeySaved { path , public , hash } => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::LoadDomains );
let _ = proxy . send_event ( UserEvent ::SendKeysToUi );
2021-03-26 18:22:43 +01:00
format! ( "keystoreChanged(' {} ', ' {} ', ' {} ');" , & path , & public , & hash )
}
Event ::MinerStarted | Event ::KeyGeneratorStarted => {
status . mining = true ;
2021-04-23 17:36:47 +02:00
status . max_diff = 0 ;
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Mining started');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-03-26 18:22:43 +01:00
String ::from ( "setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);" )
}
2021-06-09 20:36:36 +02:00
Event ::MinerStopped { success , full } => {
2021-03-26 18:22:43 +01:00
status . mining = false ;
2021-04-23 17:36:47 +02:00
status . max_diff = 0 ;
2021-03-26 18:22:43 +01:00
let mut s = if status . syncing {
String ::from ( "setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);" )
} else {
String ::from ( "setLeftStatusBarText('Idle'); showMiningIndicator(false, false);" )
};
if full {
match success {
2021-04-06 00:31:50 +02:00
true => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::LoadDomains );
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('luck', ' {} ', 'Mining is successful!');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-04-06 00:31:50 +02:00
s . push_str ( " showSuccess('Block successfully mined!')" );
}
false => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Mining finished without result.');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-05-24 17:36:07 +02:00
s . push_str ( " showWarning('Mining unsuccessful, sorry.')" );
2021-04-06 00:31:50 +02:00
}
2021-03-26 18:22:43 +01:00
}
}
s
}
2021-04-23 13:20:26 +02:00
Event ::MinerStats { thread , speed , max_diff , target_diff } => {
2021-04-10 09:47:21 +02:00
if status . max_diff < max_diff {
status . max_diff = max_diff ;
}
status . set_thread_speed ( thread , speed );
2021-05-24 17:44:35 +02:00
if thread as usize == threads - 1 {
2021-04-23 13:20:26 +02:00
format! ( "setLeftStatusBarText('Mining speed {} H/s, max found difficulty {} / {} .'); showMiningIndicator(true, false);" , status . get_speed (), status . max_diff , target_diff )
2021-04-10 09:47:21 +02:00
} else {
String ::new ()
}
}
2021-04-06 11:21:31 +02:00
Event ::KeyGeneratorStopped => {
2021-03-26 18:22:43 +01:00
status . mining = false ;
2021-04-06 11:21:31 +02:00
if status . syncing {
2021-03-26 18:22:43 +01:00
String ::from ( "setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);" )
} else {
String ::from ( "setLeftStatusBarText('Idle'); showMiningIndicator(false, false);" )
2021-03-25 20:55:09 +01:00
}
}
2021-03-26 18:22:43 +01:00
Event ::Syncing { have , height } => {
status . syncing = true ;
status . synced_blocks = have ;
2021-04-27 19:52:05 +02:00
if height != status . sync_height {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Syncing started...');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-04-27 19:52:05 +02:00
status . sync_height = height ;
}
2021-03-26 18:22:43 +01:00
if status . mining {
String ::from ( "setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);" )
} else {
format! ( "setLeftStatusBarText('Synchronizing {} / {} '); showMiningIndicator(true, true);" , have , height )
}
2021-03-18 00:16:17 +01:00
}
2021-03-26 18:22:43 +01:00
Event ::SyncFinished => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::LoadDomains );
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Syncing finished.');" , Local ::now (). format ( "%d.%m.%y %X" ))));
2021-03-26 18:22:43 +01:00
status . syncing = false ;
if status . mining {
String ::from ( "setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);" )
} else {
2021-12-25 18:40:36 +01:00
String ::from ( "setLeftStatusBarText('Idle'); showMiningIndicator(false, false);" )
2021-03-26 18:22:43 +01:00
}
2021-03-18 00:16:17 +01:00
}
2021-05-21 23:32:46 +02:00
Event ::NetworkStatus { blocks , domains , keys , nodes } => {
2025-10-29 16:01:41 +01:00
nodes_copy . store ( nodes , Ordering ::SeqCst );
2021-03-26 18:22:43 +01:00
if status . mining || status . syncing || nodes < 3 {
2021-05-21 23:32:46 +02:00
format! ( "setStats( {} , {} , {} , {} );" , blocks , domains , keys , nodes )
2021-03-26 18:22:43 +01:00
} else {
2021-05-21 23:32:46 +02:00
format! ( "setLeftStatusBarText('Idle'); setStats( {} , {} , {} , {} );" , blocks , domains , keys , nodes )
2021-03-26 18:22:43 +01:00
}
2021-03-18 00:16:17 +01:00
}
2021-06-09 20:36:36 +02:00
Event ::BlockchainChanged { index } => {
2021-04-27 17:10:05 +02:00
debug! ( "Current blockchain height is {}" , index );
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Blockchain changed, current block count is {} now.');" , Local ::now (). format ( "%d.%m.%y %X" ), index )));
String ::new ()
2021-04-27 17:10:05 +02:00
}
2021-11-20 16:11:05 +01:00
Event ::Error { text } => format! ( "showError(' {} ')" , & text ),
2021-06-09 20:36:36 +02:00
_ => String ::new ()
2021-03-26 18:22:43 +01:00
};
2021-03-18 00:16:17 +01:00
2021-03-26 18:22:43 +01:00
if ! eval . is_empty () {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( eval ));
2021-03-26 18:22:43 +01:00
}
2025-10-27 20:36:37 +01:00
}). ok ();
2021-03-18 00:16:17 +01:00
true
});
2021-03-26 18:22:43 +01:00
2025-10-29 16:01:41 +01:00
let proxy = event_loop . create_proxy ();
TrayIconEvent ::set_event_handler ( Some ( move | event | {
let _ = proxy . send_event ( UserEvent ::TrayIconEvent ( event ));
}));
let proxy = event_loop . create_proxy ();
MenuEvent ::set_event_handler ( Some ( move | event | {
let _ = proxy . send_event ( UserEvent ::MenuEvent ( event ));
}));
let proxy = event_loop . create_proxy ();
2025-10-27 20:36:37 +01:00
// Run event loop
event_loop . run ( move | event , _ , control_flow | {
* control_flow = ControlFlow ::Wait ;
match event {
TaoEvent ::WindowEvent {
event : WindowEvent ::CloseRequested ,
..
} => {
2025-10-29 16:01:41 +01:00
window . set_visible ( false );
2025-10-27 20:36:37 +01:00
}
TaoEvent ::UserEvent ( user_event ) => {
let wv = webview_clone . lock (). unwrap ();
match user_event {
UserEvent ::EvalJs ( js ) => {
let js_escaped = js . replace ( " \\ " , " \\\\ " );
if let Err ( e ) = wv . evaluate_script ( & js_escaped ) {
error! ( "Error evaluating JavaScript: {}" , e );
}
}
UserEvent ::Loaded => {
action_loaded ( & context , & wv , & proxy );
}
UserEvent ::LoadDomains => {
load_domains ( & mut context . lock (). unwrap (), & wv );
}
UserEvent ::SendKeysToUi => {
send_keys_to_ui ( & context . lock (). unwrap (), & wv );
}
UserEvent ::ShowWarning ( text ) => {
show_warning ( & wv , & text );
}
2025-10-29 16:01:41 +01:00
UserEvent ::TrayIconEvent ( event ) => {
match event {
TrayIconEvent ::DoubleClick { button , .. } => {
if button == tray_icon ::MouseButton ::Left {
window . set_visible ( true );
window . set_focus ();
}
}
TrayIconEvent ::Enter { .. } => {
let nodes = connected_nodes . load ( Ordering ::SeqCst );
let title = format! ( "ALFIS {} \n Connected: {nodes} " , env! ( "CARGO_PKG_VERSION" ));
2025-10-29 16:39:44 +01:00
let _ = tray_icon . set_tooltip ( Some ( title ));
2025-10-29 16:01:41 +01:00
}
_ => {}
}
}
UserEvent ::MenuEvent ( event ) => {
if event . id == show_item . id () {
window . set_visible ( true );
} else if event . id == quit_item . id () {
info! ( "Interface closed, exiting" );
post ( Event ::ActionQuit );
thread ::sleep ( Duration ::from_millis ( 100 ));
* control_flow = ControlFlow ::Exit ;
}
}
2025-10-27 20:36:37 +01:00
}
}
_ => {}
}
});
}
#[derive(Debug)]
enum UserEvent {
EvalJs ( String ),
Loaded ,
LoadDomains ,
SendKeysToUi ,
ShowWarning ( String ),
2025-10-29 16:01:41 +01:00
TrayIconEvent ( TrayIconEvent ),
MenuEvent ( MenuEvent )
2025-10-27 20:36:37 +01:00
}
2025-10-29 16:39:44 +01:00
/// Load icon from embedded in binary PNG file. Only needed in Linux/macOS builds.
#[cfg(not(target_os = "windows" ))]
fn load_icon_from_png () -> tray_icon ::Icon {
// Include PNG in binary
const ICON_BYTES : & [ u8 ] = include_bytes! ( "../img/logo/alfis_icon32.png" );
// decode image by crate `image`
let image = image ::load_from_memory ( ICON_BYTES )
. expect ( "Error loading image from png" );
let rgba = image . to_rgba8 ();
let ( width , height ) = image . dimensions ();
// Convert to format for tray_icon
tray_icon ::Icon ::from_rgba ( rgba . into_vec (), width , height )
. expect ( "Error loading icon" )
}
2025-10-27 20:36:37 +01:00
fn check_record ( data : & str ) -> bool {
match serde_json ::from_str ::< DnsRecord > ( data ) {
Ok ( record ) => {
if let Some ( string ) = record . get_data () {
string . len () <= MAX_DATA_LEN
} else {
false
}
}
Err ( _ ) => false
}
}
fn check_domain_available ( context : & Arc < Mutex < Context >> , name : & str ) -> bool {
let c = context . lock (). unwrap ();
if let Some ( keystore ) = c . get_keystore () {
let name = name . to_lowercase ();
matches! ( c . chain . can_mine_domain ( c . chain . get_height (), & name , & keystore . get_public ()), MineResult ::Fine )
} else {
false
}
}
fn action_save_key ( context : & Arc < Mutex < Context >> ) {
if ! context . lock (). unwrap (). has_keys () {
return ;
}
let result = tfd ::save_file_dialog_with_filter ( "Save keys file" , "" , & [ "*.toml" ], "Key files (*.toml)" );
match result {
None => {}
Some ( mut new_path ) => {
if ! new_path . ends_with ( ".toml" ) {
new_path . push_str ( ".toml" );
}
let path = new_path . clone ();
if let Some ( keystore ) = context . lock (). unwrap (). get_keystore_mut () {
let public = keystore . get_public (). to_string ();
let hash = keystore . get_hash (). to_string ();
keystore . save ( & new_path , "" );
info! ( "Key file saved to {}" , & path );
post ( Event ::KeySaved { path , public , hash });
}
}
}
}
fn action_select_key ( context : & Arc < Mutex < Context >> , proxy : & EventLoopProxy < UserEvent > , index : usize ) {
if context . lock (). unwrap (). select_key_by_index ( index ) {
let ( path , public , hash ) = {
let keystore = context . lock (). unwrap (). get_keystore (). cloned (). unwrap ();
let path = keystore . get_path (). to_owned ();
let public = keystore . get_public (). to_string ();
let hash = keystore . get_hash (). to_string ();
( path , public , hash )
};
post ( Event ::KeyLoaded { path , public , hash });
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "keySelected( {} )" , index )));
}
}
fn action_load_key ( context : & Arc < Mutex < Context >> , proxy : & EventLoopProxy < UserEvent > ) {
let result = tfd ::open_file_dialog ( "Open keys file" , "" , Some (( & [ "*.key" , "*.toml" ], "Key files" )));
match result {
None => {}
Some ( file_name ) => {
match Keystore ::from_file ( & file_name , "" ) {
None => {
error! ( "Error loading keystore '{}'!" , & file_name );
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "Error loading key!<br>Key cannot be loaded or its difficulty is not enough." . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('fail', ' {} ', 'Error loading key from \\\\ ' {} \\\\ !');" , Local ::now (). format ( "%d.%m.%y %X" ), & file_name )));
}
Some ( keystore ) => {
info! ( "Loaded keystore with keys: {:?}, {:?}" , & keystore . get_public (), & keystore . get_encryption_public ());
let path = keystore . get_path (). to_owned ();
let public = keystore . get_public (). to_string ();
let hash = keystore . get_hash (). to_string ();
post ( Event ::KeyLoaded { path , public , hash });
if ! context . lock (). unwrap (). select_key_by_public ( & keystore . get_public ()) {
context . lock (). unwrap (). add_keystore ( keystore );
} else {
warn! ( "This key is already loaded!" );
}
}
}
}
}
}
fn action_loaded ( context : & Arc < Mutex < Context >> , webview : & wry ::WebView , proxy : & EventLoopProxy < UserEvent > ) {
info! ( "Interface loaded" );
let _ = webview . evaluate_script ( "showMiningIndicator(false, false);" );
let c = context . lock (). unwrap ();
2021-03-23 18:55:11 +01:00
if let Some ( keystore ) = c . get_keystore () {
2021-03-26 18:22:43 +01:00
let path = keystore . get_path (). to_owned ();
let public = keystore . get_public (). to_string ();
let hash = keystore . get_hash (). to_string ();
2021-05-10 00:49:01 +02:00
post ( Event ::KeyLoaded { path , public , hash });
2021-03-23 18:55:11 +01:00
}
2025-10-27 20:36:37 +01:00
2021-04-20 20:54:45 +02:00
let index = c . chain . get_height ();
2021-04-27 19:52:05 +02:00
if index > 0 {
2021-05-10 00:49:01 +02:00
post ( Event ::BlockchainChanged { index });
2021-05-02 12:55:51 +02:00
}
2025-10-27 20:36:37 +01:00
2021-05-02 16:02:14 +02:00
let zones = c . chain . get_zones ();
info! ( "Loaded zones: {:?}" , & zones );
if let Ok ( zones ) = serde_json ::to_string ( & zones ) {
2025-10-27 20:36:37 +01:00
let _ = webview . evaluate_script ( & format! ( "zonesChanged(' {} ');" , & zones ));
2021-04-27 17:10:05 +02:00
}
2025-10-27 20:36:37 +01:00
drop ( c );
let _ = proxy . send_event ( UserEvent ::SendKeysToUi );
let c = context . lock (). unwrap ();
2021-06-09 20:36:36 +02:00
let command = format! ( "setStats( {} , {} , {} , {} );" , c . chain . get_height (), c . chain . get_domains_count (), c . chain . get_users_count (), 0 );
2025-10-27 20:36:37 +01:00
if let Err ( e ) = webview . evaluate_script ( & command ) {
2021-05-21 23:32:46 +02:00
error! ( "Error evaluating stats: {}" , e );
}
2025-10-27 20:36:37 +01:00
let _ = webview . evaluate_script ( & format! ( "addEvent('info', ' {} ', 'Application loaded');" , Local ::now (). format ( "%d.%m.%y %X" )));
2021-03-18 00:16:17 +01:00
}
2025-10-27 20:36:37 +01:00
fn load_domains ( context : & mut MutexGuard < Context > , webview : & wry ::WebView ) {
let _ = webview . evaluate_script ( "clearMyDomains();" );
2021-05-14 14:14:45 +02:00
let domains = context . chain . get_my_domains ( context . get_keystore ());
2024-07-10 20:15:10 +02:00
let mut domains = domains . iter (). map ( | ( _ , d ) | d ). collect ::< Vec < _ >> ();
domains . sort_by ( | a , b | a . 0. cmp ( & b . 0 ));
for ( domain , timestamp , data ) in domains {
2021-04-15 12:21:41 +02:00
let d = serde_json ::to_string ( & data ). unwrap ();
2024-07-10 20:15:10 +02:00
let d = d . replace ( "'" , " \\ '" ). replace ( " \\ n" , " \\\\ n" ). replace ( " \" " , " \\\" " );
2021-05-18 18:16:28 +02:00
let command = format! ( "addMyDomain(' {} ', {} , {} , ' {} ');" , & domain , timestamp , timestamp + DOMAIN_LIFETIME , & d );
2025-10-27 20:36:37 +01:00
let _ = webview . evaluate_script ( & command );
2021-04-15 12:21:41 +02:00
}
2025-10-27 20:36:37 +01:00
let _ = webview . evaluate_script ( "refreshMyDomains();" );
2021-04-15 12:21:41 +02:00
}
2025-10-27 20:36:37 +01:00
fn send_keys_to_ui ( context : & MutexGuard < Context > , webview : & wry ::WebView ) {
2021-05-14 14:14:45 +02:00
let keys = {
let mut keys = Vec ::new ();
for key in context . get_keystores () {
let path = key . get_path (). replace ( " \\ " , "/" );
2021-12-25 18:40:36 +01:00
let parts : Vec <& str > = path . rsplitn ( 2 , '/' ). collect ();
2021-05-14 14:14:45 +02:00
keys . push ( KeysForJS { file_name : parts [ 0 ]. to_owned (), public : key . get_public (). to_string () });
}
keys
};
if ! keys . is_empty () {
let index = context . get_active_key_index ();
2025-10-27 20:36:37 +01:00
let command = format! ( "keysChanged(' {} '); keySelected( {} );" , serde_json ::to_string ( & keys ). unwrap (), index );
let _ = webview . evaluate_script ( & command );
2021-05-14 14:14:45 +02:00
}
}
2025-10-27 20:36:37 +01:00
fn action_create_domain ( context : Arc < Mutex < Context >> , miner : Arc < Mutex < Miner >> , proxy : & EventLoopProxy < UserEvent > , name : String , data : String , signing : String , encryption : String , renewal : bool ) {
2021-04-10 09:47:21 +02:00
debug! ( "Creating domain with data: {}" , & data );
2021-03-18 00:16:17 +01:00
let c = Arc ::clone ( & context );
2025-10-27 20:36:37 +01:00
let context_guard = context . lock (). unwrap ();
if ! context_guard . has_keys () {
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "You don't have keys loaded!<br>Load or mine the keys and try again." . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-03-23 18:55:11 +01:00
return ;
}
2025-10-27 20:36:37 +01:00
if context_guard . chain . is_waiting_signers () {
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "Waiting for last full block to be signed. Try again later." . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-04-18 18:34:32 +02:00
info! ( "Waiting for last full block to be signed. Try again later." );
return ;
}
2025-10-27 20:36:37 +01:00
let keystore = context_guard . get_keystore (). unwrap (). clone ();
2021-03-23 18:55:11 +01:00
let pub_key = keystore . get_public ();
2021-04-23 19:17:05 +02:00
let data = match serde_json ::from_str ::< DomainData > ( & data ) {
2021-06-09 20:36:36 +02:00
Ok ( data ) => data ,
2021-04-11 17:50:55 +02:00
Err ( e ) => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "Something wrong with domain data. I cannot mine it." . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-04-11 17:50:55 +02:00
warn! ( "Error parsing data: {}" , e );
2021-04-10 09:47:21 +02:00
return ;
}
};
2025-10-27 20:36:37 +01:00
2021-05-07 10:14:14 +02:00
info! ( "Parsed domain data: \n {:#?}" , & data );
2025-10-27 20:36:37 +01:00
2021-05-05 09:11:23 +02:00
if data . records . len () > MAX_RECORDS {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "Too many records. Mining more than 30 records not allowed." . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-05 09:11:23 +02:00
return ;
}
2025-10-27 20:36:37 +01:00
2021-04-10 20:02:51 +02:00
// Check if yggdrasil only quality of zone is not violated
2025-10-27 20:36:37 +01:00
let zones = context_guard . chain . get_zones ();
2021-05-02 12:55:51 +02:00
for z in zones {
2021-12-25 18:40:36 +01:00
if z . name == data . zone && z . yggdrasil {
for record in & data . records {
if ! is_yggdrasil_record ( record ) {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( format! ( "Zone {} is Yggdrasil only, you cannot use IPs from clearnet!" , & data . zone )));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-12-25 18:40:36 +01:00
return ;
2021-04-10 20:02:51 +02:00
}
}
}
}
2025-10-27 20:36:37 +01:00
2021-05-05 11:50:00 +02:00
let ( signing , encryption ) = if signing . is_empty () || encryption . is_empty () {
( keystore . get_public (), keystore . get_encryption_public ())
2021-05-04 16:47:03 +02:00
} else {
2021-05-05 11:50:00 +02:00
( Bytes ::new ( from_hex ( & signing ). unwrap ()), Bytes ::new ( from_hex ( & encryption ). unwrap ()))
2021-05-04 16:47:03 +02:00
};
2025-10-27 20:36:37 +01:00
match context_guard . chain . can_mine_domain ( context_guard . chain . get_height (), & name , & pub_key ) {
2021-03-18 00:16:17 +01:00
MineResult ::Fine => {
2025-10-27 20:36:37 +01:00
drop ( context_guard );
2022-04-13 13:02:58 +02:00
create_domain ( c , miner , CLASS_DOMAIN , & name , data , DOMAIN_DIFFICULTY , & keystore , signing , encryption , renewal );
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningStarted();" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'Mining of domain \\\\ ' {} \\\\ ' has started');" , Local ::now (). format ( "%d.%m.%y %X" ), & name )));
2021-03-18 00:16:17 +01:00
}
2021-05-01 11:28:01 +02:00
MineResult ::WrongName => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "You can't mine this domain!" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-01 11:28:01 +02:00
}
MineResult ::WrongData => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "You have an error in records!" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-01 11:28:01 +02:00
}
MineResult ::WrongKey => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "You can't mine with current key!" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-01 11:28:01 +02:00
}
MineResult ::WrongZone => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "You can't mine domain in this zone!" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-01 11:28:01 +02:00
}
MineResult ::NotOwned => {
2025-10-27 20:36:37 +01:00
let _ = proxy . send_event ( UserEvent ::ShowWarning ( "This domain is already taken, and it is not yours!" . to_string ()));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-05-01 11:28:01 +02:00
}
2021-03-18 00:16:17 +01:00
MineResult ::Cooldown { time } => {
2025-10-27 20:36:37 +01:00
let cooldown = format_cooldown ( time );
let _ = proxy . send_event ( UserEvent ::EvalJs ( format! ( "addEvent('info', ' {} ', 'You have cooldown {} !');" , Local ::now (). format ( "%d.%m.%y %X" ), & cooldown )));
let _ = proxy . send_event ( UserEvent ::ShowWarning ( format! ( "You have cooldown {} !" , cooldown )));
let _ = proxy . send_event ( UserEvent ::EvalJs ( "domainMiningUnavailable();" . to_string ()));
2021-03-18 00:16:17 +01:00
}
}
}
2021-05-09 18:44:09 +02:00
fn format_cooldown ( time : i64 ) -> String {
if time <= 60 {
return format! ( " {} seconds" , time );
}
let minutes = time / 60 ;
if minutes <= 60 {
return format! ( " {} minutes" , minutes );
}
format! ( " {} hours" , minutes / 60 )
}
2025-10-27 20:36:37 +01:00
fn show_warning ( webview : & wry ::WebView , text : & str ) {
2021-03-25 20:55:09 +01:00
let str = text . replace ( '\'' , " \\ '" );
2025-10-27 20:36:37 +01:00
if let Err ( e ) = webview . evaluate_script ( & format! ( "showWarning(' {} ');" , & str )) {
warn! ( "Error showing warning: {}" , e );
2021-03-25 20:55:09 +01:00
}
}
2021-12-25 18:40:36 +01:00
#[allow(clippy::too_many_arguments)]
2022-04-13 13:02:58 +02:00
fn create_domain ( context : Arc < Mutex < Context >> , miner : Arc < Mutex < Miner >> , class : & str , name : & str , mut data : DomainData , difficulty : u32 , keystore : & Keystore , signing : Bytes , encryption : Bytes , renewal : bool ) {
2021-04-23 19:17:05 +02:00
let name = name . to_owned ();
2021-05-04 16:47:03 +02:00
let encrypted = CryptoBox ::encrypt ( encryption . as_slice (), name . as_bytes ()). expect ( "Error encrypting domain name!" );
data . encrypted = Bytes ::from_bytes ( & encrypted );
2021-04-23 19:17:05 +02:00
let data = serde_json ::to_string ( & data ). unwrap ();
2021-05-04 16:47:03 +02:00
let ( signing , encryption ) = if signing . is_empty () || encryption . is_empty () {
( keystore . get_public (), keystore . get_encryption_public ())
} else {
( signing , encryption )
};
let transaction = Transaction ::from_str ( name , class . to_owned (), data , signing , encryption );
2021-06-30 13:25:43 +02:00
// If this domain is already in blockchain we approve slightly smaller difficulty
2022-05-12 14:50:31 +02:00
let height = context . lock (). unwrap (). chain . get_height ();
let discount = context . lock (). unwrap (). chain . get_identity_discount ( & transaction . identity , renewal , height , Utc ::now (). timestamp ());
2021-06-30 13:25:43 +02:00
let block = Block ::new ( Some ( transaction ), keystore . get_public (), Bytes ::default (), difficulty - discount );
2021-04-23 19:17:05 +02:00
miner . lock (). unwrap (). add_block ( block , keystore . clone ());
}
2021-03-10 22:21:50 +01:00
#[derive(Deserialize)]
#[serde(tag = "cmd" , rename_all = "camelCase" )]
pub enum Cmd {
Loaded ,
2021-03-18 00:16:17 +01:00
LoadKey ,
CreateKey ,
SaveKey ,
2021-05-14 14:14:45 +02:00
SelectKey { index : usize },
2021-03-18 18:53:14 +01:00
CheckRecord { data : String },
2021-03-10 22:21:50 +01:00
CheckDomain { name : String },
2022-04-13 13:02:58 +02:00
MineDomain { name : String , data : String , signing : String , encryption : String , renewal : bool },
2021-03-10 22:21:50 +01:00
TransferDomain { name : String , owner : String },
StopMining ,
2021-06-09 20:36:36 +02:00
Open { link : String }
2021-03-10 22:21:50 +01:00
}
2021-09-21 15:25:42 +02:00
struct UiStatus {
2021-03-10 22:21:50 +01:00
pub mining : bool ,
pub syncing : bool ,
pub synced_blocks : u64 ,
pub sync_height : u64 ,
2021-04-10 09:47:21 +02:00
pub max_diff : u32 ,
pub speed : Vec < u64 >
2021-03-10 22:21:50 +01:00
}
2021-09-21 15:25:42 +02:00
impl UiStatus {
2021-04-10 09:47:21 +02:00
fn new ( threads : usize ) -> Self {
2025-10-27 20:36:37 +01:00
let speed = vec! [ 0 ; threads ];
2021-09-21 15:25:42 +02:00
UiStatus { mining : false , syncing : false , synced_blocks : 0 , sync_height : 0 , max_diff : 0 , speed }
2021-04-10 09:47:21 +02:00
}
2021-05-24 17:36:07 +02:00
fn set_thread_speed ( & mut self , thread : u32 , speed : u64 ) {
self . speed [ thread as usize ] = speed ;
2021-04-10 09:47:21 +02:00
}
fn get_speed ( & self ) -> u64 {
self . speed . iter (). sum ()
2021-03-10 22:21:50 +01:00
}
}
2021-05-14 14:14:45 +02:00
#[derive(Serialize)]
struct KeysForJS {
file_name : String ,
public : String
}
2021-03-10 22:21:50 +01:00
fn inline_style ( s : & str ) -> String {
format! ( r #"<style type="text/css">{}</style>"# , s )
}
fn inline_script ( s : & str ) -> String {
format! ( r #"<script type="text/javascript">{}</script>"# , s )
2021-06-09 20:36:36 +02:00
}