Added download settings and optimized the downloader system with concurrency.

This commit is contained in:
2026-09-03 18:26:10 +02:00
parent ba079ef6fa
commit c61254e4a9
5 changed files with 954 additions and 84 deletions

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
from dataclasses import asdict, dataclass, fields, replace
import json
import math
import os
from pathlib import Path
import secrets
@@ -41,11 +42,19 @@ class Settings:
proton_sync: str = "auto"
proton_path: str | None = None
game_source: str | None = None
download_speed_limit_mbps: float | None = None
low_bandwidth_mode: bool = False
@property
def source_override(self) -> Path | None:
return Path(self.game_source).expanduser().resolve() if self.game_source else None
@property
def download_speed_limit_bytes_per_second(self) -> int | None:
if self.download_speed_limit_mbps is None:
return None
return round(self.download_speed_limit_mbps * 1_000_000)
def load_settings(paths: AppPaths) -> Settings:
value = _read_settings_object(paths.settings_file)
@@ -153,6 +162,11 @@ def _settings_from_object(value: dict[str, object]) -> Settings:
proton_sync=_string(value, "proton_sync", "auto"),
proton_path=_optional_string(value, "proton_path"),
game_source=_optional_string(value, "game_source"),
download_speed_limit_mbps=_optional_positive_number(
value,
"download_speed_limit_mbps",
),
low_bandwidth_mode=_boolean(value, "low_bandwidth_mode", False),
)
_validate(settings)
return settings
@@ -250,6 +264,17 @@ def _validate(settings: Settings) -> None:
raise ValueError(f"unsupported runtime selection: {settings.runtime}")
if settings.proton_sync not in {"auto", "fsync", "esync"}:
raise ValueError(f"unsupported Proton synchronization mode: {settings.proton_sync}")
if (
settings.download_speed_limit_mbps is not None
and (
not math.isfinite(settings.download_speed_limit_mbps)
or settings.download_speed_limit_mbps <= 0
or settings.download_speed_limit_mbps > 10_000
)
):
raise ValueError(
"download_speed_limit_mbps must be greater than 0 and at most 10000, or null"
)
def _boolean(value: dict[str, object], key: str, default: bool) -> bool:
item = value.get(key, default)
@@ -274,4 +299,16 @@ def _optional_string(value: dict[str, object], key: str) -> str | None:
return item
def _optional_positive_number(value: dict[str, object], key: str) -> float | None:
item = value.get(key)
if item is None:
return None
if isinstance(item, bool) or not isinstance(item, (int, float)):
raise ValueError(f"{key} must be null or a positive number")
number = float(item)
if not math.isfinite(number) or number <= 0:
raise ValueError(f"{key} must be null or a positive number")
return number
_SETTINGS_FIELD_NAMES = frozenset(field.name for field in fields(Settings))