@@ -1,33 +1,45 @@
use std ::sync ::Arc ;
use std ::sync ::Mutex ;
use std ::sync ::{ Arc , Mutex } ; // Добавили Arc
use crate ::models ::* ;
use crate ::VpnInterface ;
use netrunner_client ::Session ;
use netrunner_client ::SessionManager ;
use serde ::de ::DeserializeOwned ;
// ВАЖНО: Добавляем Emitter для возможности вызова .emit ()
use tauri ::Manager ; // Важно для app.manage ()
use tauri ::{ plugin ::PluginApi , AppHandle , Emitter , Runtime } ;
pub fn init < R : Runtime , C : DeserializeOwned > (
app : & AppHandle < R > ,
_api : PluginApi < R , C > ,
) -> crate ::Result < Vpn < R > > {
Ok ( Vpn {
let vpn = Vpn {
app : app . clone ( ) ,
active_session : Mutex ::new ( None ) ,
} )
// Оборачиваем Mutex в Arc, чтобы структуру Vpn можно было клонировать и шарить
active_session : Arc ::new ( Mutex ::new ( None ) ) ,
} ;
// 🛑 САМОЕ ВАЖНОЕ: Регистрируем стейт в Tauri, чтобы app.state() больше не падал!
app . manage ( vpn . clone ( ) ) ;
Ok ( vpn )
}
pub struct Vpn < R : Runtime > {
app : AppHandle < R > ,
active_session : Mutex < Option < Arc < Session > > > ,
active_session : Arc < Mutex < Option < Arc < Session > > > > ,
}
impl < R : Runtime > Clone for Vpn < R > {
fn clone ( & self ) -> Self {
Self {
app : self . app . clone ( ) ,
active_session : self . active_session . clone ( ) ,
}
}
}
impl < R : Runtime > VpnInterface < R > for Vpn < R > {
fn start_vpn ( & self , _config : TunnelConfig , ip : String , port : i32 ) -> crate ::Result < ( ) > {
// 1. Сразу уведомляем фронт о начале подключения
// Имя события совпадает с тем, что мы слушаем в useEffect
let _ = self . app . emit (
" status-change " ,
StatusResponse {
@@ -35,14 +47,16 @@ impl<R: Runtime> VpnInterface<R> for Vpn<R> {
} ,
) ;
// Запуск сессии (здесь может быть паника, если нет прав админа!)
let session = SessionManager . spawn_session ( format! ( " {} : {} " , ip , port ) , None , " . " . into ( ) ) ;
// Обрати внимание: SessionManager.spawn_session -> SessionManager::new().spawn_session
let session =
SessionManager ::new ( ) . spawn_session ( format! ( " {} : {} " , ip , port ) , None , " . " . into ( ) ) ;
// Сохраняем сессию
let mut lock = self . active_session . lock ( ) . unwrap ( ) ;
* lock = Some ( session ) ;
{
let mut lock = self . active_session . lock ( ) . unwrap ( ) ;
* lock = Some ( session ) ;
}
// 2. Успешное подключение
let _ = self . app . emit (
" status-change " ,
StatusResponse {
@@ -50,6 +64,38 @@ impl<R: Runtime> VpnInterface<R> for Vpn<R> {
} ,
) ;
// --- ФОНОВЫЙ ЦИКЛ СТАТИСТИКИ (Оптимизированный) ---
let app_handle = self . app . clone ( ) ;
// Клонируем умный указатель (Arc), чтобы отдать его внутрь потока
let session_ref = self . active_session . clone ( ) ;
std ::thread ::spawn ( move | | {
loop {
std ::thread ::sleep ( std ::time ::Duration ::from_secs ( 1 ) ) ;
// Теперь мы НЕ обращаемся к app_handle.state(), а проверяем локальную ссылку!
// Это полностью исключает дедлоки стейта Tauri.
if session_ref . lock ( ) . unwrap ( ) . is_none ( ) {
eprintln! ( " --- [DEBUG] VPN Session ended. Stopping desktop stats loop. --- " ) ;
break ;
}
let stats = SessionManager ::new ( ) . get_traffic_stats ( ) ;
let _ = app_handle . emit (
" traffic-stats " ,
TrafficStatsResponse {
rx_bytes : stats . rx_bytes ,
tx_bytes : stats . tx_bytes ,
rx_packets : stats . rx_packets ,
tx_packets : stats . tx_packets ,
} ,
) ;
}
} ) ;
// -----------------------------------------------------------------
Ok ( ( ) )
}
@@ -58,7 +104,6 @@ impl<R: Runtime> VpnInterface<R> for Vpn<R> {
if let Some ( session ) = lock . take ( ) {
session . stop ( ) ;
// 3. Уведомляем об отключении
let _ = self . app . emit (
" status-change " ,
StatusResponse {