Files
LorenEteval_Furious/Furious/Controllers/RoutingController.py
T
2026-08-19 15:03:55 +08:00

167 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (C) 2024present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Own application routing selection independently from its UI controls."""
from __future__ import annotations
from Furious.Frozenlib import (
AppConnectionController,
AppBuiltinRouting,
AppSettings,
registerAppSettings,
)
from Furious.Plugins import getPluginRegistry
from Furious.Repository import Storage
from PySide6 import QtCore
__all__ = ['RoutingController']
registerAppSettings('Routing', default=AppBuiltinRouting.BypassMainlandChina.value)
class RoutingController(QtCore.QObject):
"""Expose one routing state shared by every application surface."""
stateChanged = QtCore.Signal(object, str)
optionsChanged = QtCore.Signal(object)
routingChanged = QtCore.Signal(str)
interactionEnabledChanged = QtCore.Signal(bool)
def __init__(self, parent=None):
"""Initialize persisted routing state and plugin-provided options."""
super().__init__(parent)
if AppSettings.get('Routing') == 'Bypass':
# Update the value used by older Furious releases.
AppSettings.set('Routing', AppBuiltinRouting.BypassMainlandChina.value)
self._options = tuple()
self._routing = str(AppSettings.get('Routing'))
self._interactionEnabled = True
self.refresh()
@staticmethod
def currentProfileForRouting():
"""Return the connected profile, falling back to repository selection."""
try:
profile = AppConnectionController().activeProfile
if profile is not None:
return profile
except (AttributeError, RuntimeError):
# The connection controller may not exist during early startup.
pass
try:
index, servers = (
Storage.UserActivatedItemIndex(),
Storage.UserServers(),
)
if 0 <= index < len(servers):
return servers[index]
except Exception:
# Any non-exit exceptions
# The controller can exist before persistent storage is ready.
pass
return None
@property
def options(self):
"""Return routing options supported by the active configuration."""
return self._options
@property
def routing(self) -> str:
"""Return the normalized route shown by routing controls."""
return self._routing
@property
def interactionEnabled(self) -> bool:
"""Return whether users may change routing at this lifecycle state."""
return self._interactionEnabled
def state(self):
"""Return the current immutable option and selection snapshot."""
return self.options, self.routing
def refresh(self, *, force=False):
"""Refresh routing capabilities from the active proxy-core plugin."""
profile = self.currentProfileForRouting()
registry = getPluginRegistry()
options = registry.routingOptions(profile) if profile is not None else tuple()
routing = (
registry.normalizeRouting(profile, AppSettings.get('Routing'))
if profile is not None
else str(AppSettings.get('Routing'))
)
optionsChanged = options != self._options
routingChanged = routing != self._routing
self._options = options
self._routing = routing
if optionsChanged:
self.optionsChanged.emit(options)
if routingChanged:
self.routingChanged.emit(routing)
if force or optionsChanged or routingChanged:
self.stateChanged.emit(options, routing)
return self.state()
@QtCore.Slot(str)
def selectRouting(self, routing: str) -> bool:
"""Persist a supported route and reconnect the active proxy if needed."""
options, _current = self.refresh()
if routing not in tuple(option.id for option in options):
return False
if AppSettings.get('Routing') == routing:
return False
AppSettings.set('Routing', routing)
self.refresh(force=True)
connectionController = AppConnectionController()
if connectionController.isConnected():
connectionController.startReconnection()
return True
@QtCore.Slot(bool)
def setInteractionEnabled(self, enabled: bool):
"""Publish whether routing controls may accept user interaction."""
enabled = bool(enabled)
if enabled == self._interactionEnabled:
return
self._interactionEnabled = enabled
self.interactionEnabledChanged.emit(enabled)