checkport fix
This commit is contained in:
@@ -62,16 +62,53 @@ def _public_ip(profile: dict | None = None, force: bool = False) -> str:
|
||||
return res.read(64).decode("utf-8", "replace").strip()
|
||||
|
||||
|
||||
def _incoming_port(profile: dict) -> int | None:
|
||||
MAX_PORT_CHECK_CANDIDATES = 256
|
||||
|
||||
|
||||
def _parse_port_candidates(value: str, limit: int = MAX_PORT_CHECK_CANDIDATES) -> tuple[list[int], bool]:
|
||||
"""Return valid incoming port candidates from rTorrent network.port_range.
|
||||
|
||||
Note: rTorrent may keep a range/list and pick a random port on start.
|
||||
The old checker used only the first number, which produced false "closed"
|
||||
results when another configured port was actually active.
|
||||
"""
|
||||
ports: list[int] = []
|
||||
seen: set[int] = set()
|
||||
truncated = False
|
||||
|
||||
def add(port: int) -> None:
|
||||
nonlocal truncated
|
||||
if not 1 <= port <= 65535 or port in seen:
|
||||
return
|
||||
if len(ports) >= limit:
|
||||
truncated = True
|
||||
return
|
||||
seen.add(port)
|
||||
ports.append(port)
|
||||
|
||||
for start, end in re.findall(r"(\d{1,5})\s*-\s*(\d{1,5})", value or ""):
|
||||
a, b = int(start), int(end)
|
||||
if a > b:
|
||||
a, b = b, a
|
||||
for port in range(a, b + 1):
|
||||
add(port)
|
||||
if truncated:
|
||||
break
|
||||
|
||||
without_ranges = re.sub(r"\d{1,5}\s*-\s*\d{1,5}", " ", value or "")
|
||||
for item in re.findall(r"\d{1,5}", without_ranges):
|
||||
add(int(item))
|
||||
|
||||
return ports, truncated
|
||||
|
||||
|
||||
def _incoming_ports(profile: dict) -> dict:
|
||||
try:
|
||||
value = str(rtorrent.client_for(profile).call("network.port_range") or "")
|
||||
raw_value = str(rtorrent.client_for(profile).call("network.port_range") or "")
|
||||
except Exception:
|
||||
value = ""
|
||||
match = re.search(r"(\d{2,5})", value)
|
||||
if not match:
|
||||
return None
|
||||
port = int(match.group(1))
|
||||
return port if 1 <= port <= 65535 else None
|
||||
raw_value = ""
|
||||
ports, truncated = _parse_port_candidates(raw_value)
|
||||
return {"ports": ports, "raw": raw_value, "truncated": truncated}
|
||||
|
||||
|
||||
def _yougetsignal_check(public_ip: str, port: int) -> dict:
|
||||
@@ -104,16 +141,40 @@ def _local_port_fallback(public_ip: str, port: int) -> dict:
|
||||
return {"status": "unknown", "source": "local-fallback", "error": f"Local fallback inconclusive: {exc}"}
|
||||
|
||||
|
||||
def _check_ports(public_ip: str, ports: list[int], checker) -> dict:
|
||||
checked: list[int] = []
|
||||
first_closed: dict | None = None
|
||||
last_result: dict = {"status": "unknown"}
|
||||
|
||||
for port in ports:
|
||||
checked.append(port)
|
||||
current = checker(public_ip, port)
|
||||
last_result = current
|
||||
if current.get("status") == "open":
|
||||
current.update({"port": port, "open_port": port, "checked_ports": checked})
|
||||
return current
|
||||
if current.get("status") == "closed" and first_closed is None:
|
||||
first_closed = current
|
||||
|
||||
result = first_closed or last_result
|
||||
result.update({"port": ports[0] if ports else None, "open_port": None, "checked_ports": checked})
|
||||
return result
|
||||
|
||||
|
||||
def port_check_status(force: bool = False) -> dict:
|
||||
profile = preferences.active_profile()
|
||||
prefs = preferences.get_preferences()
|
||||
enabled = bool((prefs or {}).get("port_check_enabled"))
|
||||
if not profile:
|
||||
return {"status": "unknown", "enabled": enabled, "error": "No profile"}
|
||||
port = _incoming_port(profile)
|
||||
if not port:
|
||||
|
||||
port_info = _incoming_ports(profile)
|
||||
ports = port_info["ports"]
|
||||
if not ports:
|
||||
return {"status": "unknown", "enabled": enabled, "error": "Cannot read rTorrent network.port_range"}
|
||||
cache_key = f"port_check:{profile['id']}:{port}"
|
||||
|
||||
ports_key = ",".join(str(port) for port in ports)
|
||||
cache_key = f"port_check:{profile['id']}:{ports_key}:{int(bool(port_info['truncated']))}"
|
||||
if not force:
|
||||
cached = _app_setting_get(cache_key)
|
||||
if cached:
|
||||
@@ -127,20 +188,31 @@ def port_check_status(force: bool = False) -> dict:
|
||||
return data
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
checked_at_epoch = time.time()
|
||||
result = {"status": "unknown", "enabled": enabled, "port": port, "checked_at_epoch": checked_at_epoch, "checked_at": _iso_from_epoch(checked_at_epoch), "cached": False}
|
||||
result = {
|
||||
"status": "unknown",
|
||||
"enabled": enabled,
|
||||
"port": ports[0],
|
||||
"ports": ports,
|
||||
"port_range": port_info["raw"],
|
||||
"ports_truncated": port_info["truncated"],
|
||||
"checked_at_epoch": checked_at_epoch,
|
||||
"checked_at": _iso_from_epoch(checked_at_epoch),
|
||||
"cached": False,
|
||||
}
|
||||
try:
|
||||
public_ip = _public_ip(profile, force=force)
|
||||
result["public_ip"] = public_ip
|
||||
result["remote"] = bool(profile.get("is_remote"))
|
||||
result.update(_yougetsignal_check(public_ip, port))
|
||||
result.update(_check_ports(public_ip, ports, _yougetsignal_check))
|
||||
except Exception as exc:
|
||||
result["error"] = f"YouGetSignal failed: {exc}"
|
||||
try:
|
||||
public_ip = result.get("public_ip") or _public_ip(profile, force=force)
|
||||
result["public_ip"] = public_ip
|
||||
result["remote"] = bool(profile.get("is_remote"))
|
||||
result.update(_local_port_fallback(public_ip, port))
|
||||
result.update(_check_ports(public_ip, ports, _local_port_fallback))
|
||||
except Exception as fallback_exc:
|
||||
result["fallback_error"] = str(fallback_exc)
|
||||
result["source"] = "none"
|
||||
|
||||
Reference in New Issue
Block a user