zapret-kvn/xray_fluent/application/runtime_services.py
2026-07-11 21:01:15 +03:00

202 lines
7.9 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING
from ..constants import SINGBOX_CLASH_API_PORT
from ..live_metrics_worker import LiveMetricsWorker
if TYPE_CHECKING:
from ..app_controller import AppController
def start_metrics_worker(controller: AppController) -> None:
session = controller._active_session
node = controller.selected_node
ping_host = session.ping_host if session is not None else (node.server if node else "")
ping_port = session.ping_port if session is not None else (node.port if node else 0)
controller._log(f"[metrics] starting worker, active_core={controller._active_core}")
stop_metrics_worker(controller)
if controller._active_core == "singbox":
mode = "singbox"
elif controller._active_session is not None and controller._active_session.tun_mode:
mode = "xray-tun"
else:
mode = "xray"
socks_port, http_port = controller.get_effective_proxy_ports()
inbound_tags = controller._active_session.xray_inbound_tags if controller._active_session else ()
controller._metrics_worker = LiveMetricsWorker(
controller.state.settings.xray_path,
controller._xray_api_port,
ping_host=ping_host,
ping_port=ping_port,
mode=mode,
clash_api_port=SINGBOX_CLASH_API_PORT,
socks_port=socks_port,
http_port=http_port,
xray_inbound_tags=list(inbound_tags),
)
controller._metrics_worker.metrics.connect(controller._on_live_metrics)
controller._metrics_worker.start()
def stop_metrics_worker(controller: AppController) -> None:
if not controller._metrics_worker:
return
if controller._metrics_worker.isRunning():
controller._metrics_worker.stop()
controller._metrics_worker.wait(1200)
controller._metrics_worker = None
def cleanup_connection_runtime_state(
controller: AppController,
*,
end_traffic_session: bool,
reset_auto_switch_cycle: bool,
reset_auto_switch_cooldown: bool,
) -> None:
controller._xray_tun_routes.cleanup()
controller._xray_api_port = 0
controller._protect_ss_port = 0
controller._protect_ss_password = ""
controller._tun2socks_proxy_username = ""
controller._tun2socks_proxy_password = ""
controller._traffic_save_counter = 0
controller._reset_auto_switch_state(
reset_cooldown=reset_auto_switch_cooldown,
reset_cycle=reset_auto_switch_cycle,
)
if end_traffic_session:
controller._traffic_history.end_session()
from ..process_traffic_collector import reset_connection_tracking
from ..win_proc_monitor import clear_pid_cache
reset_connection_tracking()
clear_pid_cache()
def stop_active_connection_processes(controller: AppController, *, disable_proxy: bool) -> bool:
stopped = True
if controller._active_core == "singbox":
if controller.singbox.is_running:
stopped = controller.singbox.stop() and stopped
if controller.xray.is_running:
stopped = controller.xray.stop() and stopped
if controller.tun2socks.is_running:
stopped = controller.tun2socks.stop() and stopped
elif controller._active_core == "tun2socks":
if controller.tun2socks.is_running:
stopped = controller.tun2socks.stop() and stopped
if controller.xray.is_running:
stopped = controller.xray.stop() and stopped
if controller.singbox.is_running:
stopped = controller.singbox.stop() and stopped
else:
if controller.xray.is_running:
stopped = controller.xray.stop() and stopped
if controller.singbox.is_running:
stopped = controller.singbox.stop() and stopped
if controller.tun2socks.is_running:
stopped = controller.tun2socks.stop() and stopped
if disable_proxy and controller.state.settings.enable_system_proxy:
controller.proxy.disable(restore_previous=True)
return stopped
def handle_unexpected_disconnect(controller: AppController) -> None:
if controller._cleaning_connection_state:
return
controller._cleaning_connection_state = True
try:
cleanup_connection_runtime_state(
controller,
end_traffic_session=True,
reset_auto_switch_cycle=not controller._auto_switch_transitioning,
reset_auto_switch_cooldown=True,
)
stop_active_connection_processes(controller, disable_proxy=not controller._reconnecting)
controller._active_core = (
"singbox"
if not controller.state.settings.tun_mode
and str(controller.state.settings.proxy_engine) == "singbox"
else "xray"
)
controller._clear_active_session()
if not controller._reconnecting:
controller._desired_connected = False
finally:
controller._auto_switch_transitioning = False
controller._cleaning_connection_state = False
def on_core_state_changed(controller: AppController, _running: bool) -> None:
was_connected, is_connected = controller._refresh_connected_state()
if not controller._switching and was_connected != is_connected:
controller.connection_changed.emit(is_connected)
if is_connected and not controller._switching and not was_connected:
start_metrics_worker(controller)
elif not is_connected:
stop_metrics_worker(controller)
if was_connected and not controller._switching:
controller.live_metrics_updated.emit({"down_bps": 0.0, "up_bps": 0.0, "latency_ms": None})
if not controller._disconnecting:
handle_unexpected_disconnect(controller)
if (
not is_connected
and controller.state.settings.enable_system_proxy
and not controller._reconnecting
):
controller.proxy.disable(restore_previous=True)
def on_live_metrics(controller: AppController, payload: dict[str, object]) -> None:
controller.live_metrics_updated.emit(payload)
down_bps = float(payload.get("down_bps") or 0.0)
controller._check_auto_switch(down_bps)
process_stats = payload.get("process_stats")
if process_stats:
stats_dict = {}
for ps in process_stats:
stats_dict[ps.exe] = (ps.upload, ps.download, ps.route)
controller._traffic_history.update_session(stats_dict)
controller._traffic_save_counter += 1
if controller._traffic_save_counter >= 15:
controller._traffic_history.save_periodic()
controller._traffic_save_counter = 0
def shutdown(controller: AppController) -> None:
if controller._country_resolver and controller._country_resolver.isRunning():
controller._country_resolver.quit()
controller._country_resolver.wait(2000)
if controller._ping_worker and controller._ping_worker.isRunning():
controller._ping_worker.cancel()
controller._ping_worker.wait(500)
if controller._connectivity_worker and controller._connectivity_worker.isRunning():
controller._connectivity_worker.wait(1000)
stop_metrics_worker(controller)
if controller._speed_worker and controller._speed_worker.isRunning():
controller._speed_worker.cancel()
controller._speed_worker.wait(20000)
if controller._xray_update_worker and controller._xray_update_worker.isRunning():
controller._xray_update_worker.wait(1000)
controller.disconnect_current()
if controller.tun2socks.is_running:
controller.tun2socks.stop()
if controller.singbox.is_running:
controller.singbox.stop()
if controller.xray.is_running:
controller.xray.stop()
controller._xray_tun_routes.cleanup()
if controller.zapret.running:
controller.zapret.stop()
if controller.proxy.is_enabled():
controller.proxy.disable(restore_previous=True)
controller._cleanup_tun_adapter()
controller.network_monitor.stop()
controller._lock_timer.stop()
controller.save()