zapret-kvn/xray_fluent/path_utils.py
XrayFluent Dev 626bb17b1e feat: TUN VPN mode via tun2socks, UI improvements, settings overhaul
Major changes:
- TUN mode: tun2socks + xray SOCKS architecture (replaced sing-box)
- Hot-swap: node switching keeps TUN alive, only restarts xray
- Traffic graph: live QPainter chart on dashboard with detail dialog
- Settings: stock qfluentwidgets cards (SpinBox, ColorPicker, PushSettingCard)
- Navigation: fixed icon/text overlap in expanded menu
- Ctrl+V import, copy link from context menu
- System proxy cleanup on exit (atexit safety net)
- Build: data/ backup/restore during PyInstaller rebuild
- Log throttling in TUN mode to prevent UI freeze

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 23:01:50 +03:00

88 lines
2.4 KiB
Python

from __future__ import annotations
from pathlib import Path
from .constants import BASE_DIR
def _clean_path_value(path_value: str | Path | None) -> str:
return str(path_value or "").strip()
def _base_relative(path: Path) -> Path | None:
try:
return path.resolve(strict=False).relative_to(BASE_DIR.resolve(strict=False))
except ValueError:
return None
def _looks_like_default_location(path: Path, default_path: Path) -> bool:
default_relative = _base_relative(default_path) or default_path
path_parts = tuple(part.casefold() for part in path.parts)
default_parts = tuple(part.casefold() for part in default_relative.parts)
if len(path_parts) < len(default_parts):
return False
return path_parts[-len(default_parts):] == default_parts
def normalize_path_for_storage(path_value: str | Path | None) -> str:
text = _clean_path_value(path_value)
if not text:
return ""
path = Path(text)
if not path.is_absolute():
return str(path)
relative = _base_relative(path)
if relative is not None:
return str(relative)
return str(path)
def normalize_configured_path(
path_value: str | Path | None,
*,
default_path: Path | None = None,
use_default_if_empty: bool = False,
migrate_default_location: bool = False,
) -> str:
text = _clean_path_value(path_value)
if not text:
if use_default_if_empty and default_path is not None:
return normalize_path_for_storage(default_path)
return ""
path = Path(text)
if (
default_path is not None
and migrate_default_location
and path.is_absolute()
and not path.exists()
and _looks_like_default_location(path, default_path)
):
return normalize_path_for_storage(default_path)
return normalize_path_for_storage(path)
def resolve_configured_path(
path_value: str | Path | None,
*,
default_path: Path | None = None,
use_default_if_empty: bool = False,
migrate_default_location: bool = False,
) -> Path | None:
normalized = normalize_configured_path(
path_value,
default_path=default_path,
use_default_if_empty=use_default_if_empty,
migrate_default_location=migrate_default_location,
)
if not normalized:
return None
path = Path(normalized)
if path.is_absolute():
return path.resolve(strict=False)
return (BASE_DIR / path).resolve(strict=False)