修复:- 上次选中的节点在本机已不可用(如 IPv6 专用节点但本机无 IPv6 出口)时启动不再停留其上:新增 _verify_selected_node/_first_reachable_index,启动后台校验并自动回退到第一个可达节点、写入设置、刷新标题栏(原来只在点开启代理时才报错)
This commit is contained in:
@@ -263,6 +263,34 @@ def _reload_nodes():
|
||||
_IPV6_ONLY.clear()
|
||||
return _load_nodes()
|
||||
|
||||
def _first_reachable_index(nodes):
|
||||
"""第一个本机可达的节点下标;都不可达返回 0。供选中不可达节点时回退。"""
|
||||
for i, n in enumerate(nodes):
|
||||
if _node_reachable(n):
|
||||
return i
|
||||
return 0
|
||||
|
||||
def _verify_selected_node():
|
||||
"""启动时校验上次选中的节点在本机是否还可用。
|
||||
|
||||
典型场景:上次选了 IPv6 专用节点,这次本机没有 IPv6 出口(或该节点已不可达),
|
||||
继续选中它只会在开启代理时才报错。这里自动回退到第一个可达节点并记录到设置。
|
||||
返回 True 表示发生了切换(调用方据此刷新标题栏)。"""
|
||||
global _SELECTED_NODE
|
||||
nodes = _load_nodes()
|
||||
if not nodes or not (0 <= _SELECTED_NODE < len(nodes)):
|
||||
return False
|
||||
if _node_reachable(nodes[_SELECTED_NODE]):
|
||||
return False
|
||||
idx = _first_reachable_index(nodes)
|
||||
old = str(nodes[_SELECTED_NODE].get("domain") or "")
|
||||
new = str(nodes[idx].get("domain") or "")
|
||||
_SELECTED_NODE = idx
|
||||
_save_current_node(new)
|
||||
if new and new != old:
|
||||
log_red(f"节点 {old} 在本机不可用,已自动切换到 {new}")
|
||||
return True
|
||||
|
||||
def _node_val(key, default=""):
|
||||
node = _current_node()
|
||||
if not node:
|
||||
@@ -2130,6 +2158,7 @@ class MainWindow(QWidget):
|
||||
_update_download_done = pyqtSignal(bool, str)
|
||||
|
||||
_status_ready = pyqtSignal(object)
|
||||
_node_switched = pyqtSignal() # 启动校验时代替用户切换了节点(刷新标题栏)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -2143,6 +2172,7 @@ class MainWindow(QWidget):
|
||||
self._update_done.connect(self._on_update_done)
|
||||
self._update_download_done.connect(self._on_update_download_done)
|
||||
self._status_ready.connect(self._apply_status)
|
||||
self._node_switched.connect(self._on_node_switched)
|
||||
|
||||
_user_init_local() # 首启初始化用户数据目录(节点模板),须在 init_ui 读节点之前
|
||||
|
||||
@@ -2160,9 +2190,27 @@ class MainWindow(QWidget):
|
||||
threading.Thread(target=_user_init_remote, daemon=True).start()
|
||||
# 后台用已保存凭据同步最新节点配置(服务端改动下次启动生效)
|
||||
threading.Thread(target=_nodes_sync_remote, daemon=True).start()
|
||||
# 后台校验上次选中的节点在本机是否还可用(IPv6 专用节点本机无 IPv6 时回退)
|
||||
threading.Thread(target=self._verify_node_thread, daemon=True).start()
|
||||
# 节点未配置时延迟弹出登录框(等窗口显示后)
|
||||
QTimer.singleShot(600, self._maybe_show_login)
|
||||
|
||||
def _verify_node_thread(self):
|
||||
"""等节点配置在线同步完成后,校验当前选中节点是否可用,必要时自动回退。
|
||||
|
||||
探测要解析 DNS 并尝试 TCP 连接,放在后台线程,避免卡住启动。"""
|
||||
import time
|
||||
time.sleep(1.5)
|
||||
try:
|
||||
if _verify_selected_node():
|
||||
self._node_switched.emit()
|
||||
except Exception as e:
|
||||
log(f"节点可用性校验失败: {e}")
|
||||
|
||||
def _on_node_switched(self):
|
||||
"""节点被自动切换后刷新标题栏(在 UI 线程执行)。"""
|
||||
self.subtitle.setText(f"{_endpoint_domain()} ({_endpoint_country()})")
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化主界面布局与样式。"""
|
||||
self.setWindowTitle("R.R.P.P.")
|
||||
|
||||
Reference in New Issue
Block a user