From a4d956d29ce03981f2a9238949fe72d3e20dea37 Mon Sep 17 00:00:00 2001 From: Boris Yumankulov Date: Sun, 5 Jul 2026 12:50:43 +0500 Subject: [PATCH] refactor: replace jq usage with orjson helpers Signed-off-by: Boris Yumankulov --- build-aux/PKGBUILD | 2 +- build-aux/PKGBUILD-git | 2 +- build-aux/fedora-git.spec | 1 - build-aux/fedora.spec | 1 - .../share/portproton/scripts/functions_helper | 15 +++-- debian/control | 1 - portprotonqt/meson.build | 1 + portprotonqt/scripts_utils/json_tools.py | 67 +++++++++++++++++++ tests/test_json_tools.py | 44 ++++++++++++ 9 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 portprotonqt/scripts_utils/json_tools.py create mode 100644 tests/test_json_tools.py diff --git a/build-aux/PKGBUILD b/build-aux/PKGBUILD index 96f0f701..f09a8dfa 100644 --- a/build-aux/PKGBUILD +++ b/build-aux/PKGBUILD @@ -6,7 +6,7 @@ arch=('any') url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt" license=('GPL-3.0') depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson' - 'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3') + 'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3') makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext') conflicts=("portprotonqt-steam-compat") provides=('portprotonqt-steam-compat') diff --git a/build-aux/PKGBUILD-git b/build-aux/PKGBUILD-git index 60c26738..59e1c872 100644 --- a/build-aux/PKGBUILD-git +++ b/build-aux/PKGBUILD-git @@ -6,7 +6,7 @@ arch=('any') url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt" license=('GPL-3.0') depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson' - 'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3') + 'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3') makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext') conflicts=("portprotonqt-steam-compat") provides=('portprotonqt-steam-compat') diff --git a/build-aux/fedora-git.spec b/build-aux/fedora-git.spec index 7fa1a11d..593fb9df 100644 --- a/build-aux/fedora-git.spec +++ b/build-aux/fedora-git.spec @@ -50,7 +50,6 @@ Requires: cabextract Requires: gzip Requires: unzip Requires: curl -Requires: jq Requires: file Requires: findutils Requires: gawk diff --git a/build-aux/fedora.spec b/build-aux/fedora.spec index 9f8ceb8b..f5eb4c2f 100644 --- a/build-aux/fedora.spec +++ b/build-aux/fedora.spec @@ -47,7 +47,6 @@ Requires: cabextract Requires: gzip Requires: unzip Requires: curl -Requires: jq Requires: file Requires: findutils Requires: gawk diff --git a/build-aux/share/portproton/scripts/functions_helper b/build-aux/share/portproton/scripts/functions_helper index 0f098062..09639679 100755 --- a/build-aux/share/portproton/scripts/functions_helper +++ b/build-aux/share/portproton/scripts/functions_helper @@ -4222,11 +4222,8 @@ gui_proton_downloader () { pw_download_get_wine () { local URL_VERSION_PROTON_GIT local VERSION="$1" - local ALL_CATEGORIES=$(echo "$METADATA" | jq -r 'keys[]' 2>/dev/null) print_info "Download and install ${VERSION}..." - URL_VERSION_PROTON_GIT=$(echo "$METADATA" | jq -r --arg version "$VERSION" ' - to_entries[] | .value[]? | select((.name | ascii_downcase) == ($version | ascii_downcase)) | .url - ' 2>/dev/null | head -n 1) + URL_VERSION_PROTON_GIT=$(python_module json_tools wine-url "$VERSION" <<< "$METADATA" 2>/dev/null) if [[ -z "$URL_VERSION_PROTON_GIT" ]]; then fatal "no URL found for version ${VERSION}" fi @@ -4751,14 +4748,18 @@ pw_auto_create_shortcut () { if [[ "${PW_EXE_FILE}" =~ EpicGamesLauncher.exe$ ]]; then for item_file in "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c/ProgramData/Epic/EpicGamesLauncher/Data/Manifests/"*".item"; do if [[ -f "${item_file}" ]]; then - exe_path=$(jq -r '.InstallLocation + "\\" + .LaunchExecutable' "${item_file}") + IFS=$'\t' read -r exe_path PORTWINE_CREATE_SHORTCUT_NAME item_id < <( + python_module json_tools epic-manifest "${item_file}" 2>/dev/null + ) + if [[ -z "${exe_path}" ]]; then + print_warning "Failed to parse Epic manifest: ${item_file}" + continue + fi get_lnk_path "${exe_path}" - PORTWINE_CREATE_SHORTCUT_NAME=$(jq -r '.DisplayName' "${item_file}") if ! exe_path="$(realpath "${link_path}" 2>/dev/null)" || [[ ! -f "${exe_path}" ]]; then print_warning "Link broken for: ${PORTWINE_CREATE_SHORTCUT_NAME}" elif [[ ! -f "${exe_path}.ppdb" ]]; then unset FILE_SHA256SUM DESKTOP_NAME_FILE DESKTOP_CORRECT_FILE - item_id=$(jq -r '.AppName' "${item_file}") PW_EXE_FILE="${exe_path}" PORTWINE_DB_FILE="${PW_EXE_FILE}.ppdb" LAUNCH_URI="com.epicgames.launcher://apps/${item_id}?action=launch&silent=true" diff --git a/debian/control b/debian/control index 8b576a97..18b54912 100644 --- a/debian/control +++ b/debian/control @@ -22,7 +22,6 @@ Depends: curl, file, gawk, - jq, libimage-exiftool-perl, libsdl3-0, mesa-utils, diff --git a/portprotonqt/meson.build b/portprotonqt/meson.build index c8db989a..f448b241 100644 --- a/portprotonqt/meson.build +++ b/portprotonqt/meson.build @@ -139,6 +139,7 @@ install_data( 'scripts_utils/easyterm.py', 'scripts_utils/graphics_detector.py', 'scripts_utils/idle_inhibit.py', + 'scripts_utils/json_tools.py', 'scripts_utils/mount_points.py', 'scripts_utils/prefix_backup.py', 'scripts_utils/power_profiles.py', diff --git a/portprotonqt/scripts_utils/json_tools.py b/portprotonqt/scripts_utils/json_tools.py new file mode 100644 index 00000000..9d05d088 --- /dev/null +++ b/portprotonqt/scripts_utils/json_tools.py @@ -0,0 +1,67 @@ +"""JSON helpers for PortProton shell scripts.""" +import argparse +import sys +from pathlib import Path +from typing import Any + +import orjson + + +def _load_json_file(path: str) -> Any: + with Path(path).open("rb") as f: + return orjson.loads(f.read()) + + +def _wine_url(metadata: dict[str, Any], version: str) -> str: + version_lower = version.lower() + for items in metadata.values(): + if not isinstance(items, list): + continue + for item in items: + if not isinstance(item, dict): + continue + if str(item.get("name", "")).lower() == version_lower: + return str(item.get("url", "")) + return "" + + +def _epic_manifest_fields(manifest: dict[str, Any]) -> tuple[str, str, str]: + install_location = str(manifest.get("InstallLocation", "")) + launch_executable = str(manifest.get("LaunchExecutable", "")) + exe_path = f"{install_location}\\{launch_executable}" + return exe_path, str(manifest.get("DisplayName", "")), str(manifest.get("AppName", "")) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="PortProton JSON tools") + subparsers = parser.add_subparsers(dest="command", required=True) + + wine_parser = subparsers.add_parser("wine-url") + wine_parser.add_argument("version") + + epic_parser = subparsers.add_parser("epic-manifest") + epic_parser.add_argument("manifest_path") + + return parser.parse_args() + + +def main() -> int: + args = parse_args() + try: + if args.command == "wine-url": + metadata = orjson.loads(sys.stdin.buffer.read()) + sys.stdout.write(_wine_url(metadata, args.version)) + return 0 + if args.command == "epic-manifest": + fields = _epic_manifest_fields(_load_json_file(args.manifest_path)) + sys.stdout.write("\t".join(fields)) + return 0 + except (OSError, orjson.JSONDecodeError) as error: + sys.stderr.write(f"Failed to parse JSON: {error}\n") + return 1 + + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_json_tools.py b/tests/test_json_tools.py new file mode 100644 index 00000000..a2a64897 --- /dev/null +++ b/tests/test_json_tools.py @@ -0,0 +1,44 @@ +"""Tests for JSON helpers used by PortProton shell scripts.""" + +from pathlib import Path + +import orjson + +from portprotonqt.scripts_utils import json_tools + + +def test_wine_url_matches_version_case_insensitive() -> None: + metadata = { + "proton": [ + {"name": "GE-Proton10-15", "url": "https://example.org/proton.tar.gz"}, + ], + } + + assert json_tools._wine_url(metadata, "ge-proton10-15") == "https://example.org/proton.tar.gz" + + +def test_wine_url_ignores_unexpected_metadata() -> None: + metadata = { + "broken": {"name": "GE-Proton10-15"}, + "wine": [{"name": "Wine", "url": "https://example.org/wine.tar.gz"}], + } + + assert json_tools._wine_url(metadata, "missing") == "" + + +def test_epic_manifest_fields_reads_launcher_values(tmp_path: Path) -> None: + manifest_path = tmp_path / "game.item" + manifest_path.write_bytes(orjson.dumps({ + "InstallLocation": "C:\\Games\\Demo", + "LaunchExecutable": "Demo.exe", + "DisplayName": "Demo Game", + "AppName": "demo-app", + })) + + manifest = json_tools._load_json_file(str(manifest_path)) + + assert json_tools._epic_manifest_fields(manifest) == ( + "C:\\Games\\Demo\\Demo.exe", + "Demo Game", + "demo-app", + ) -- 2.54.0