Comprovació d'updates a l'inici opcional + timeouts de xarxa configurables
- Opció marcable «Comprova actualitzacions a l'inici» al menú; persisteix a settings.json i llança la comprovació diferida en obrir la finestra. - Tolerància a repos offline/inalcanzables: low-speed abort + techo dur de temps a les operacions git de xarxa (fetch/clone), evitant cuelgues. - Timeouts exposats a settings.json (git_fetch_timeout, git_clone_timeout, http_timeout, git_stall_limit, git_stall_time) via NetConfig, propagats UI -> workers -> gitops. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+112
-24
@@ -14,6 +14,7 @@ import subprocess
|
|||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .config import Game
|
from .config import Game
|
||||||
@@ -22,7 +23,26 @@ from .paths import game_dir, metadata_dir, repo_dir
|
|||||||
|
|
||||||
LogFn = Callable[[str], None]
|
LogFn = Callable[[str], None]
|
||||||
|
|
||||||
_HTTP_TIMEOUT = 15
|
|
||||||
|
# --- Tolerancia a repos offline / inalcanzables ----------------------------
|
||||||
|
# Dos mecanismos complementarios para que git no se cuelgue:
|
||||||
|
# 1) low-speed abort: si una transferencia baja de `stall_limit` bytes/s durante
|
||||||
|
# `stall_time` segundos, git la aborta (transferencias que se estancan a media
|
||||||
|
# descarga).
|
||||||
|
# 2) techo duro: timeout en subprocess.run que mata git pase lo que pase (cubre
|
||||||
|
# cuelgues de conexión TCP/DNS cuando el host está offline, donde aún no fluyen
|
||||||
|
# bytes y el low-speed no llega a dispararse).
|
||||||
|
# Los valores son configurables desde settings.json (ver settings.Settings).
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NetConfig:
|
||||||
|
fetch_timeout: float = 60 # techo para fetch / comprobar update (operación ligera)
|
||||||
|
clone_timeout: float = 900 # techo para clone (puede traer un repo grande)
|
||||||
|
http_timeout: float = 15 # techo para la API de Gitea (urllib)
|
||||||
|
stall_limit: int = 1000 # bytes/s por debajo de los cuales se considera estancado
|
||||||
|
stall_time: int = 20 # segundos por debajo del límite -> abortar
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_NET = NetConfig()
|
||||||
|
|
||||||
|
|
||||||
def _noop(_: str) -> None:
|
def _noop(_: str) -> None:
|
||||||
@@ -36,13 +56,33 @@ def _auth_args(token: str) -> list[str]:
|
|||||||
return ["-c", f"http.extraHeader=Authorization: token {token}"]
|
return ["-c", f"http.extraHeader=Authorization: token {token}"]
|
||||||
|
|
||||||
|
|
||||||
def _run_git(args: list[str], cwd: Path | None, log: LogFn, token: str = "") -> str:
|
def _net_args(net: NetConfig | None) -> list[str]:
|
||||||
"""Ejecuta git capturando salida; lanza RuntimeError si falla.
|
"""Args -c para abortar transferencias estancadas (solo afectan al transporte http)."""
|
||||||
|
if net is None:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
"-c",
|
||||||
|
f"http.lowSpeedLimit={net.stall_limit}",
|
||||||
|
"-c",
|
||||||
|
f"http.lowSpeedTime={net.stall_time}",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _run_git(
|
||||||
|
args: list[str],
|
||||||
|
cwd: Path | None,
|
||||||
|
log: LogFn,
|
||||||
|
token: str = "",
|
||||||
|
timeout: float | None = None,
|
||||||
|
net: NetConfig | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Ejecuta git capturando salida; lanza RuntimeError si falla o agota `timeout`.
|
||||||
|
|
||||||
Si se pasa `token`, inyecta la cabecera de autorización de Gitea (para clone/fetch
|
Si se pasa `token`, inyecta la cabecera de autorización de Gitea (para clone/fetch
|
||||||
de repos privados) y la redacta en el log para no filtrarla.
|
de repos privados) y la redacta en el log para no filtrarla. Si se pasa `net`
|
||||||
|
añade el low-speed abort; `timeout` impone un techo duro (mata git si se cuelga).
|
||||||
"""
|
"""
|
||||||
cmd = ["git", *_auth_args(token), *args]
|
cmd = ["git", *_auth_args(token), *_net_args(net), *args]
|
||||||
|
|
||||||
def emit(line: str) -> None:
|
def emit(line: str) -> None:
|
||||||
log(line.replace(token, "***") if token else line)
|
log(line.replace(token, "***") if token else line)
|
||||||
@@ -51,13 +91,23 @@ def _run_git(args: list[str], cwd: Path | None, log: LogFn, token: str = "") ->
|
|||||||
emit("$ " + " ".join(["git", *args]))
|
emit("$ " + " ".join(["git", *args]))
|
||||||
# GIT_TERMINAL_PROMPT=0: si falta auth en un repo privado, falla rápido
|
# GIT_TERMINAL_PROMPT=0: si falta auth en un repo privado, falla rápido
|
||||||
# en vez de colgarse esperando credenciales (no hay terminal en la GUI).
|
# en vez de colgarse esperando credenciales (no hay terminal en la GUI).
|
||||||
proc = subprocess.run(
|
try:
|
||||||
cmd,
|
proc = subprocess.run(
|
||||||
cwd=str(cwd) if cwd else None,
|
cmd,
|
||||||
capture_output=True,
|
cwd=str(cwd) if cwd else None,
|
||||||
text=True,
|
capture_output=True,
|
||||||
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
|
text=True,
|
||||||
)
|
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
emit(
|
||||||
|
f"git {' '.join(args)} ha excedit el temps ({timeout:.0f}s): "
|
||||||
|
"el repositori no respon o no és accessible."
|
||||||
|
)
|
||||||
|
raise RuntimeError(
|
||||||
|
f"git {' '.join(args)} ha excedit el temps ({timeout:.0f}s)"
|
||||||
|
) from None
|
||||||
if proc.stdout:
|
if proc.stdout:
|
||||||
emit(proc.stdout.rstrip())
|
emit(proc.stdout.rstrip())
|
||||||
if proc.stderr:
|
if proc.stderr:
|
||||||
@@ -72,7 +122,13 @@ def is_installed(root: Path, game: Game) -> bool:
|
|||||||
return (repo_dir(root, game.id) / ".git").exists()
|
return (repo_dir(root, game.id) / ".git").exists()
|
||||||
|
|
||||||
|
|
||||||
def check_update(root: Path, game: Game, log: LogFn = _noop, token: str = "") -> bool:
|
def check_update(
|
||||||
|
root: Path,
|
||||||
|
game: Game,
|
||||||
|
log: LogFn = _noop,
|
||||||
|
token: str = "",
|
||||||
|
net: NetConfig = DEFAULT_NET,
|
||||||
|
) -> bool:
|
||||||
"""Hace fetch y devuelve True si el clone local está por detrás del remoto.
|
"""Hace fetch y devuelve True si el clone local está por detrás del remoto.
|
||||||
|
|
||||||
No modifica el árbol de trabajo: solo cuenta los commits de origin/<rama> que no
|
No modifica el árbol de trabajo: solo cuenta los commits de origin/<rama> que no
|
||||||
@@ -81,7 +137,14 @@ def check_update(root: Path, game: Game, log: LogFn = _noop, token: str = "") ->
|
|||||||
repo = repo_dir(root, game.id)
|
repo = repo_dir(root, game.id)
|
||||||
if not (repo / ".git").exists():
|
if not (repo / ".git").exists():
|
||||||
return False
|
return False
|
||||||
_run_git(["fetch", "origin", "--prune"], repo, log, token=token)
|
_run_git(
|
||||||
|
["fetch", "origin", "--prune"],
|
||||||
|
repo,
|
||||||
|
log,
|
||||||
|
token=token,
|
||||||
|
timeout=net.fetch_timeout,
|
||||||
|
net=net,
|
||||||
|
)
|
||||||
target = (
|
target = (
|
||||||
load_meta(root, game.id).default_branch
|
load_meta(root, game.id).default_branch
|
||||||
or _detect_origin_head(repo, log)
|
or _detect_origin_head(repo, log)
|
||||||
@@ -106,7 +169,13 @@ def delete_local(root: Path, game: Game, log: LogFn = _noop) -> None:
|
|||||||
shutil.rmtree(target, ignore_errors=True)
|
shutil.rmtree(target, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def download(root: Path, game: Game, log: LogFn = _noop, token: str = "") -> GameMeta:
|
def download(
|
||||||
|
root: Path,
|
||||||
|
game: Game,
|
||||||
|
log: LogFn = _noop,
|
||||||
|
token: str = "",
|
||||||
|
net: NetConfig = DEFAULT_NET,
|
||||||
|
) -> GameMeta:
|
||||||
"""Clona (si no existe) o trae el remoto forzado (descartando cambios locales).
|
"""Clona (si no existe) o trae el remoto forzado (descartando cambios locales).
|
||||||
|
|
||||||
Luego refresca la metadata (descripción Gitea + versión + icono) y la devuelve.
|
Luego refresca la metadata (descripción Gitea + versión + icono) y la devuelve.
|
||||||
@@ -114,11 +183,18 @@ def download(root: Path, game: Game, log: LogFn = _noop, token: str = "") -> Gam
|
|||||||
repo = repo_dir(root, game.id)
|
repo = repo_dir(root, game.id)
|
||||||
repo.parent.mkdir(parents=True, exist_ok=True)
|
repo.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
branch = _fetch_default_branch(game, log, token)
|
branch = _fetch_default_branch(game, log, token, net)
|
||||||
|
|
||||||
if (repo / ".git").exists():
|
if (repo / ".git").exists():
|
||||||
log(f"Actualitzant {game.name} (forçat, descartant canvis locals)…")
|
log(f"Actualitzant {game.name} (forçat, descartant canvis locals)…")
|
||||||
_run_git(["fetch", "origin", "--prune"], repo, log, token=token)
|
_run_git(
|
||||||
|
["fetch", "origin", "--prune"],
|
||||||
|
repo,
|
||||||
|
log,
|
||||||
|
token=token,
|
||||||
|
timeout=net.fetch_timeout,
|
||||||
|
net=net,
|
||||||
|
)
|
||||||
target = branch or _detect_origin_head(repo, log) or "HEAD"
|
target = branch or _detect_origin_head(repo, log) or "HEAD"
|
||||||
_run_git(["reset", "--hard", f"origin/{target}"], repo, log)
|
_run_git(["reset", "--hard", f"origin/{target}"], repo, log)
|
||||||
_run_git(["clean", "-fd"], repo, log)
|
_run_git(["clean", "-fd"], repo, log)
|
||||||
@@ -126,9 +202,16 @@ def download(root: Path, game: Game, log: LogFn = _noop, token: str = "") -> Gam
|
|||||||
log(f"Clonant {game.name}…")
|
log(f"Clonant {game.name}…")
|
||||||
if repo.exists(): # carpeta a medias sin .git: limpiarla
|
if repo.exists(): # carpeta a medias sin .git: limpiarla
|
||||||
shutil.rmtree(repo, ignore_errors=True)
|
shutil.rmtree(repo, ignore_errors=True)
|
||||||
_run_git(["clone", game.clone_url, str(repo)], None, log, token=token)
|
_run_git(
|
||||||
|
["clone", game.clone_url, str(repo)],
|
||||||
|
None,
|
||||||
|
log,
|
||||||
|
token=token,
|
||||||
|
timeout=net.clone_timeout,
|
||||||
|
net=net,
|
||||||
|
)
|
||||||
|
|
||||||
return refresh_metadata(root, game, branch, log, token)
|
return refresh_metadata(root, game, branch, log, token, net)
|
||||||
|
|
||||||
|
|
||||||
def refresh_metadata(
|
def refresh_metadata(
|
||||||
@@ -137,13 +220,14 @@ def refresh_metadata(
|
|||||||
branch: str | None = None,
|
branch: str | None = None,
|
||||||
log: LogFn = _noop,
|
log: LogFn = _noop,
|
||||||
token: str = "",
|
token: str = "",
|
||||||
|
net: NetConfig = DEFAULT_NET,
|
||||||
) -> GameMeta:
|
) -> GameMeta:
|
||||||
"""Reconstruye info.json + icon.png a partir del repo clonado y la API Gitea."""
|
"""Reconstruye info.json + icon.png a partir del repo clonado y la API Gitea."""
|
||||||
repo = repo_dir(root, game.id)
|
repo = repo_dir(root, game.id)
|
||||||
meta = load_meta(root, game.id)
|
meta = load_meta(root, game.id)
|
||||||
|
|
||||||
# Descripción + rama por defecto desde la API de Gitea (best-effort).
|
# Descripción + rama por defecto desde la API de Gitea (best-effort).
|
||||||
api = _fetch_gitea_info(game, log, token)
|
api = _fetch_gitea_info(game, log, token, net)
|
||||||
if api is not None:
|
if api is not None:
|
||||||
meta.description = api.get("description", meta.description) or meta.description
|
meta.description = api.get("description", meta.description) or meta.description
|
||||||
meta.default_branch = api.get("default_branch", meta.default_branch)
|
meta.default_branch = api.get("default_branch", meta.default_branch)
|
||||||
@@ -194,7 +278,9 @@ def _read_version(game: Game, repo: Path, log: LogFn) -> str:
|
|||||||
return out.splitlines()[0] if out else ""
|
return out.splitlines()[0] if out else ""
|
||||||
|
|
||||||
|
|
||||||
def _fetch_gitea_info(game: Game, log: LogFn, token: str = "") -> dict | None:
|
def _fetch_gitea_info(
|
||||||
|
game: Game, log: LogFn, token: str = "", net: NetConfig = DEFAULT_NET
|
||||||
|
) -> dict | None:
|
||||||
if not game.info_url:
|
if not game.info_url:
|
||||||
return None
|
return None
|
||||||
headers = {"Accept": "application/json"}
|
headers = {"Accept": "application/json"}
|
||||||
@@ -202,15 +288,17 @@ def _fetch_gitea_info(game: Game, log: LogFn, token: str = "") -> dict | None:
|
|||||||
headers["Authorization"] = f"token {token}"
|
headers["Authorization"] = f"token {token}"
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(game.info_url, headers=headers)
|
req = urllib.request.Request(game.info_url, headers=headers)
|
||||||
with urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT) as resp:
|
with urllib.request.urlopen(req, timeout=net.http_timeout) as resp:
|
||||||
return json.loads(resp.read().decode("utf-8"))
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
except (urllib.error.URLError, OSError, json.JSONDecodeError, ValueError) as exc:
|
except (urllib.error.URLError, OSError, json.JSONDecodeError, ValueError) as exc:
|
||||||
log(f"No s'ha pogut llegir la info de Gitea ({game.info_url}): {exc}")
|
log(f"No s'ha pogut llegir la info de Gitea ({game.info_url}): {exc}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _fetch_default_branch(game: Game, log: LogFn, token: str = "") -> str | None:
|
def _fetch_default_branch(
|
||||||
info = _fetch_gitea_info(game, log, token)
|
game: Game, log: LogFn, token: str = "", net: NetConfig = DEFAULT_NET
|
||||||
|
) -> str | None:
|
||||||
|
info = _fetch_gitea_info(game, log, token, net)
|
||||||
if info:
|
if info:
|
||||||
return info.get("default_branch")
|
return info.get("default_branch")
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ class Settings:
|
|||||||
hide_not_downloaded: bool = False
|
hide_not_downloaded: bool = False
|
||||||
updates_pending: list[str] = field(default_factory=list) # ids con update pendiente
|
updates_pending: list[str] = field(default_factory=list) # ids con update pendiente
|
||||||
gitea_token: str = "" # token personal de Gitea para repos privados (no se versiona)
|
gitea_token: str = "" # token personal de Gitea para repos privados (no se versiona)
|
||||||
|
check_updates_on_start: bool = False # comprobar updates automáticamente al iniciar
|
||||||
|
# Tolerancia a repos offline/inalcanzables (segundos, salvo stall_limit en bytes/s).
|
||||||
|
git_fetch_timeout: int = 60 # techo para fetch / comprobar update
|
||||||
|
git_clone_timeout: int = 900 # techo para clone (repo grande)
|
||||||
|
http_timeout: int = 15 # techo para la API de Gitea
|
||||||
|
git_stall_limit: int = 1000 # bytes/s: por debajo se considera transferencia estancada
|
||||||
|
git_stall_time: int = 20 # segundos estancado antes de abortar
|
||||||
|
|
||||||
|
|
||||||
def settings_path() -> Path:
|
def settings_path() -> Path:
|
||||||
@@ -34,6 +41,12 @@ def load_settings() -> Settings:
|
|||||||
hide_not_downloaded=bool(data.get("hide_not_downloaded", False)),
|
hide_not_downloaded=bool(data.get("hide_not_downloaded", False)),
|
||||||
updates_pending=list(data.get("updates_pending", [])),
|
updates_pending=list(data.get("updates_pending", [])),
|
||||||
gitea_token=str(data.get("gitea_token", "")),
|
gitea_token=str(data.get("gitea_token", "")),
|
||||||
|
check_updates_on_start=bool(data.get("check_updates_on_start", False)),
|
||||||
|
git_fetch_timeout=int(data.get("git_fetch_timeout", 60)),
|
||||||
|
git_clone_timeout=int(data.get("git_clone_timeout", 900)),
|
||||||
|
http_timeout=int(data.get("http_timeout", 15)),
|
||||||
|
git_stall_limit=int(data.get("git_stall_limit", 1000)),
|
||||||
|
git_stall_time=int(data.get("git_stall_time", 20)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtCore import QThreadPool, Qt
|
from PySide6.QtCore import QThreadPool, Qt, QTimer
|
||||||
from PySide6.QtGui import QAction
|
from PySide6.QtGui import QAction
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QInputDialog,
|
QInputDialog,
|
||||||
@@ -84,6 +84,22 @@ class MainWindow(QMainWindow):
|
|||||||
self.rows[game_id].set_update_available(True)
|
self.rows[game_id].set_update_available(True)
|
||||||
self._apply_filter()
|
self._apply_filter()
|
||||||
|
|
||||||
|
# Comprobación automática de updates al iniciar (si está activada). Se difiere
|
||||||
|
# con un timer 0 para que la ventana se muestre antes de lanzar el worker.
|
||||||
|
if self.settings.check_updates_on_start:
|
||||||
|
QTimer.singleShot(0, self._check_updates)
|
||||||
|
|
||||||
|
def _net_config(self) -> gitops.NetConfig:
|
||||||
|
"""Construye la config de red/timeouts a partir de las preferencias guardadas."""
|
||||||
|
s = self.settings
|
||||||
|
return gitops.NetConfig(
|
||||||
|
fetch_timeout=s.git_fetch_timeout,
|
||||||
|
clone_timeout=s.git_clone_timeout,
|
||||||
|
http_timeout=s.http_timeout,
|
||||||
|
stall_limit=s.git_stall_limit,
|
||||||
|
stall_time=s.git_stall_time,
|
||||||
|
)
|
||||||
|
|
||||||
# --------------------------------------------------------------- menú
|
# --------------------------------------------------------------- menú
|
||||||
|
|
||||||
def _build_menu(self) -> None:
|
def _build_menu(self) -> None:
|
||||||
@@ -98,6 +114,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.action_check.triggered.connect(self._check_updates)
|
self.action_check.triggered.connect(self._check_updates)
|
||||||
menu.addAction(self.action_check)
|
menu.addAction(self.action_check)
|
||||||
|
|
||||||
|
self.action_check_on_start = QAction(
|
||||||
|
"Comprova actualitzacions a l'inici", self, checkable=True
|
||||||
|
)
|
||||||
|
self.action_check_on_start.setChecked(self.settings.check_updates_on_start)
|
||||||
|
self.action_check_on_start.toggled.connect(self._on_toggle_check_on_start)
|
||||||
|
menu.addAction(self.action_check_on_start)
|
||||||
|
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
self.action_delete = QAction("Esborra un joc", self, checkable=True)
|
self.action_delete = QAction("Esborra un joc", self, checkable=True)
|
||||||
self.action_delete.toggled.connect(self._set_delete_mode)
|
self.action_delete.toggled.connect(self._set_delete_mode)
|
||||||
@@ -129,6 +152,10 @@ class MainWindow(QMainWindow):
|
|||||||
save_settings(self.settings)
|
save_settings(self.settings)
|
||||||
self._apply_filter()
|
self._apply_filter()
|
||||||
|
|
||||||
|
def _on_toggle_check_on_start(self, checked: bool) -> None:
|
||||||
|
self.settings.check_updates_on_start = checked
|
||||||
|
save_settings(self.settings)
|
||||||
|
|
||||||
def _apply_filter(self) -> None:
|
def _apply_filter(self) -> None:
|
||||||
hide = self.action_hide.isChecked()
|
hide = self.action_hide.isChecked()
|
||||||
for row in self.rows.values():
|
for row in self.rows.values():
|
||||||
@@ -139,7 +166,9 @@ class MainWindow(QMainWindow):
|
|||||||
def _check_updates(self) -> None:
|
def _check_updates(self) -> None:
|
||||||
self.action_check.setEnabled(False)
|
self.action_check.setEnabled(False)
|
||||||
self._log("=== Comprovant actualitzacions ===")
|
self._log("=== Comprovant actualitzacions ===")
|
||||||
worker = CheckUpdatesWorker(self.root, self.config.games, self.settings.gitea_token)
|
worker = CheckUpdatesWorker(
|
||||||
|
self.root, self.config.games, self.settings.gitea_token, self._net_config()
|
||||||
|
)
|
||||||
worker.signals.log.connect(self._log)
|
worker.signals.log.connect(self._log)
|
||||||
worker.signals.result.connect(self._mark_update)
|
worker.signals.result.connect(self._mark_update)
|
||||||
worker.signals.finished.connect(self._check_done)
|
worker.signals.finished.connect(self._check_done)
|
||||||
@@ -208,7 +237,9 @@ class MainWindow(QMainWindow):
|
|||||||
row.set_busy(True, "Descarregant…")
|
row.set_busy(True, "Descarregant…")
|
||||||
self._log(f"=== Descàrrega: {game.name} ===")
|
self._log(f"=== Descàrrega: {game.name} ===")
|
||||||
|
|
||||||
worker = DownloadWorker(self.root, game, self.settings.gitea_token)
|
worker = DownloadWorker(
|
||||||
|
self.root, game, self.settings.gitea_token, self._net_config()
|
||||||
|
)
|
||||||
worker.signals.log.connect(self._log)
|
worker.signals.log.connect(self._log)
|
||||||
worker.signals.finished.connect(lambda _meta, g=game: self._download_done(g))
|
worker.signals.finished.connect(lambda _meta, g=game: self._download_done(g))
|
||||||
worker.signals.error.connect(lambda msg, g=game: self._op_error(g, msg))
|
worker.signals.error.connect(lambda msg, g=game: self._op_error(g, msg))
|
||||||
|
|||||||
+26
-4
@@ -25,17 +25,28 @@ class _Signals(QObject):
|
|||||||
class DownloadWorker(QRunnable):
|
class DownloadWorker(QRunnable):
|
||||||
"""Clona o actualiza (forzado) y refresca la metadata de un juego."""
|
"""Clona o actualiza (forzado) y refresca la metadata de un juego."""
|
||||||
|
|
||||||
def __init__(self, root: Path, game: Game, token: str = "") -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
root: Path,
|
||||||
|
game: Game,
|
||||||
|
token: str = "",
|
||||||
|
net: gitops.NetConfig = gitops.DEFAULT_NET,
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.root = root
|
self.root = root
|
||||||
self.game = game
|
self.game = game
|
||||||
self.token = token
|
self.token = token
|
||||||
|
self.net = net
|
||||||
self.signals = _Signals()
|
self.signals = _Signals()
|
||||||
|
|
||||||
def run(self) -> None: # noqa: D401 - API de QRunnable
|
def run(self) -> None: # noqa: D401 - API de QRunnable
|
||||||
try:
|
try:
|
||||||
meta: GameMeta = gitops.download(
|
meta: GameMeta = gitops.download(
|
||||||
self.root, self.game, log=self.signals.log.emit, token=self.token
|
self.root,
|
||||||
|
self.game,
|
||||||
|
log=self.signals.log.emit,
|
||||||
|
token=self.token,
|
||||||
|
net=self.net,
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001 - reportar a la UI
|
except Exception as exc: # noqa: BLE001 - reportar a la UI
|
||||||
self.signals.error.emit(str(exc))
|
self.signals.error.emit(str(exc))
|
||||||
@@ -67,11 +78,18 @@ class CheckUpdatesWorker(QRunnable):
|
|||||||
Emite `result(game_id, has_update)` por cada juego instalado y `finished` al final.
|
Emite `result(game_id, has_update)` por cada juego instalado y `finished` al final.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, root: Path, games: list[Game], token: str = "") -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
root: Path,
|
||||||
|
games: list[Game],
|
||||||
|
token: str = "",
|
||||||
|
net: gitops.NetConfig = gitops.DEFAULT_NET,
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.root = root
|
self.root = root
|
||||||
self.games = games
|
self.games = games
|
||||||
self.token = token
|
self.token = token
|
||||||
|
self.net = net
|
||||||
self.signals = _Signals()
|
self.signals = _Signals()
|
||||||
|
|
||||||
def run(self) -> None: # noqa: D401 - API de QRunnable
|
def run(self) -> None: # noqa: D401 - API de QRunnable
|
||||||
@@ -81,7 +99,11 @@ class CheckUpdatesWorker(QRunnable):
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
has_update = gitops.check_update(
|
has_update = gitops.check_update(
|
||||||
self.root, game, log=self.signals.log.emit, token=self.token
|
self.root,
|
||||||
|
game,
|
||||||
|
log=self.signals.log.emit,
|
||||||
|
token=self.token,
|
||||||
|
net=self.net,
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001 - no abortar el resto
|
except Exception as exc: # noqa: BLE001 - no abortar el resto
|
||||||
self.signals.log.emit(f"check {game.id}: {exc}")
|
self.signals.log.emit(f"check {game.id}: {exc}")
|
||||||
|
|||||||
Reference in New Issue
Block a user