From 861c9d8ed0af99cbd300676f023b23ff08a1f216 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sun, 20 Oct 2024 12:14:44 -0300 Subject: [PATCH 01/10] Add `appid` to entries and copy images to `grid` folder --- bottles/backend/managers/steam.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 68048e6fb15..ffdc6ce80d3 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -530,6 +530,7 @@ def add_shortcut(self, program_name: str, program_path: str): confs = glob(os.path.join(self.userdata_path, "*/config/")) shortcut = { + "appid": 3123456789 - 0x100000000, "AppName": program_name, "Exe": cmd, "StartDir": ManagerUtils.get_bottle_path(self.config), @@ -563,6 +564,15 @@ def add_shortcut(self, program_name: str, program_path: str): with open(os.path.join(c, "shortcuts.vdf"), "wb") as f: f.write(vdf.binary_dumps(_shortcuts)) + + import shutil + appid = shortcut["appid"] + 0x100000000 + base_path = os.path.join(c, f"grid/{appid}") + shutil.copy("grid1.jpg", f"{base_path}.jpg") + shutil.copy("grid2.jpg", f"{base_path}p.jpg") + shutil.copy("hero.jpg", f"{base_path}_hero.jpg") + shutil.copy("logo.png", f"{base_path}_logo.png") + shutil.copy("icon.png", f"{base_path}_icon.png") logging.info(f"Added shortcut for {program_name}") return Result(True) From 80dae8a63865e1a6bdcf290403e78ab6a01c3ad9 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sun, 20 Oct 2024 13:20:00 -0300 Subject: [PATCH 02/10] Generate appid using Steam ROM Manager hash method (`bottle name + program name`` is used instead) --- bottles/backend/managers/steam.py | 9 +++++---- requirements.txt | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index ffdc6ce80d3..32863f6e3cd 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -20,6 +20,7 @@ import shlex import shutil import uuid +from crc32c import crc32c from datetime import datetime from functools import lru_cache from glob import glob @@ -522,7 +523,8 @@ def launch_app(prefix: str): def add_shortcut(self, program_name: str, program_path: str): logging.info(f"Adding shortcut for {program_name}") cmd = "xdg-open" - args = "bottles:run/'{0}'/'{1}'" + args = f"bottles:run/'{self.config.Name}'/'{program_name}'" + appid = crc32c(str.encode(self.config.Name + program_name)) | 0x80000000 if self.userdata_path is None: logging.warning("Userdata path is not set") @@ -530,13 +532,13 @@ def add_shortcut(self, program_name: str, program_path: str): confs = glob(os.path.join(self.userdata_path, "*/config/")) shortcut = { - "appid": 3123456789 - 0x100000000, + "appid": appid - 0x100000000, "AppName": program_name, "Exe": cmd, "StartDir": ManagerUtils.get_bottle_path(self.config), "icon": ManagerUtils.extract_icon(self.config, program_name, program_path), "ShortcutPath": "", - "LaunchOptions": args.format(self.config.Name, program_name), + "LaunchOptions": args, "IsHidden": 0, "AllowDesktopConfig": 1, "AllowOverlay": 1, @@ -566,7 +568,6 @@ def add_shortcut(self, program_name: str, program_path: str): f.write(vdf.binary_dumps(_shortcuts)) import shutil - appid = shortcut["appid"] + 0x100000000 base_path = os.path.join(c, f"grid/{appid}") shutil.copy("grid1.jpg", f"{base_path}.jpg") shutil.copy("grid2.jpg", f"{base_path}p.jpg") diff --git a/requirements.txt b/requirements.txt index c6a2045ae53..6b8930dc6d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,14 +6,15 @@ chardet==5.2.0 requests[use_chardet_on_py3]==2.32.3 Markdown==3.7 icoextract==0.1.5 -patool==3.0.0 +patool==3.0.1 pathvalidate==3.2.1 FVS==0.3.4 -orjson==3.10.7 +orjson==3.10.9 pycairo==1.27.0 PyGObject==3.50.0 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 idna==3.10 urllib3==2.2.3 certifi==2024.8.30 pefile==2024.8.26 +crc32c==2.7.1 From 7071850410a283b83fe48cfa800e0b2d62f94708 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sun, 20 Oct 2024 21:36:10 -0300 Subject: [PATCH 03/10] Fetch Steam assets through `steamgrid-proxy` --- bottles/backend/managers/steam.py | 18 ++++++++------ bottles/backend/managers/steamgriddb.py | 32 ++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 32863f6e3cd..420400f1d6a 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -28,6 +28,7 @@ from typing import Union, Dict, Optional from bottles.backend.globals import Paths +from bottles.backend.managers.steamgriddb import SteamGridDBManager from bottles.backend.models.config import BottleConfig from bottles.backend.models.result import Result from bottles.backend.models.samples import Samples @@ -567,13 +568,16 @@ def add_shortcut(self, program_name: str, program_path: str): with open(os.path.join(c, "shortcuts.vdf"), "wb") as f: f.write(vdf.binary_dumps(_shortcuts)) - import shutil - base_path = os.path.join(c, f"grid/{appid}") - shutil.copy("grid1.jpg", f"{base_path}.jpg") - shutil.copy("grid2.jpg", f"{base_path}p.jpg") - shutil.copy("hero.jpg", f"{base_path}_hero.jpg") - shutil.copy("logo.png", f"{base_path}_logo.png") - shutil.copy("icon.png", f"{base_path}_icon.png") + asset_suffixes = { + "grids": "p", + "hgrids": "", + "heroes": "_hero", + "logos": "_logo", + "icons": "_icon", + } + for asset_type, suffix, in asset_suffixes.items(): + base_filename = f"{appid}{suffix}" + SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) logging.info(f"Added shortcut for {program_name}") return Result(True) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index 7670f7b85ef..af9c059e0ba 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -30,12 +30,27 @@ class SteamGridDBManager: @staticmethod def get_game_grid(name: str, config: BottleConfig): try: - res = requests.get(f"https://steamgrid.usebottles.com/api/search/{name}") + url = f"https://steamgrid.usebottles.com/api/search/{name}" + res = requests.get(url) except: return if res.status_code == 200: return SteamGridDBManager.__save_grid(res.json(), config) + + @staticmethod + def get_steam_game_asset(name: str, asset_type: str, config_path: str, base_filename: str): + try: + #url = f"https://steamgrid.usebottles.com/api/search/{name}/{asset_type}" + url = f"http://127.0.0.1:8000/api/search/{name}/{asset_type}" + print(url) + res = requests.get(url) + except: + return + + if res.status_code == 200: + assets_path = os.path.join(config_path, "grid") + return SteamGridDBManager.__save_asset_to_steam(res.json(), assets_path, base_filename) @staticmethod def __save_grid(url: str, config: BottleConfig): @@ -53,5 +68,20 @@ def __save_grid(url: str, config: BottleConfig): f.write(r.content) except Exception: return + + @staticmethod + def __save_asset_to_steam(url: str, assets_path: str, base_filename: str): + if not os.path.exists(assets_path): + os.makedirs(assets_path) + + try: + r = requests.get(url) + ext = url.rpartition(".")[-1] + filename = f"{base_filename}.{ext}" + path = os.path.join(assets_path, filename) + with open(path, "wb") as f: + f.write(r.content) + except Exception: + return return f"grid:{filename}" From 72d3eaeb1b31f5ffcfc2df48651f32226dbf6383 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sun, 20 Oct 2024 22:35:40 -0300 Subject: [PATCH 04/10] chore: add back missing return --- bottles/backend/managers/steamgriddb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index af9c059e0ba..3ccda56573a 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -68,6 +68,8 @@ def __save_grid(url: str, config: BottleConfig): f.write(r.content) except Exception: return + + return f"grid:{filename}" @staticmethod def __save_asset_to_steam(url: str, assets_path: str, base_filename: str): From 24eec6b1f7149b76281c7b1a321add4ddf5ab43d Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sun, 20 Oct 2024 23:43:00 -0300 Subject: [PATCH 05/10] Logging stuff --- bottles/backend/managers/steam.py | 6 +++++- bottles/backend/managers/steamgriddb.py | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 420400f1d6a..b1ee01b567c 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -568,6 +568,7 @@ def add_shortcut(self, program_name: str, program_path: str): with open(os.path.join(c, "shortcuts.vdf"), "wb") as f: f.write(vdf.binary_dumps(_shortcuts)) + logging.info(f"Searching SteamGridDB for {program_name} assets…") asset_suffixes = { "grids": "p", "hgrids": "", @@ -577,7 +578,10 @@ def add_shortcut(self, program_name: str, program_path: str): } for asset_type, suffix, in asset_suffixes.items(): base_filename = f"{appid}{suffix}" - SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) + filename = SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) + if filename: + s = asset_type[:-1] if asset_type != "heroes" else "hero" + logging.info(f"Added {s.capitalize()} asset ({filename})") logging.info(f"Added shortcut for {program_name}") return Result(True) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index 3ccda56573a..57239086800 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -43,7 +43,6 @@ def get_steam_game_asset(name: str, asset_type: str, config_path: str, base_file try: #url = f"https://steamgrid.usebottles.com/api/search/{name}/{asset_type}" url = f"http://127.0.0.1:8000/api/search/{name}/{asset_type}" - print(url) res = requests.get(url) except: return @@ -86,4 +85,4 @@ def __save_asset_to_steam(url: str, assets_path: str, base_filename: str): except Exception: return - return f"grid:{filename}" + return filename From c5d186b63d38d4a34c6acca93b8a5d5988c48b24 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Mon, 21 Oct 2024 13:57:15 -0300 Subject: [PATCH 06/10] Save custom icon location (when available) on `shortcuts.vdf` --- bottles/backend/managers/steam.py | 33 +++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index b1ee01b567c..b877a5421c8 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -552,6 +552,24 @@ def add_shortcut(self, program_name: str, program_path: str): } for c in confs: + logging.info(f"Searching SteamGridDB for {program_name} assets…") + asset_suffixes = { + "grids": "p", + "hgrids": "", + "heroes": "_hero", + "logos": "_logo", + "icons": "_icon", + } + for asset_type, suffix, in asset_suffixes.items(): + base_filename = f"{appid}{suffix}" + filename = SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) + if filename: + if asset_type == "icons": + shortcut["icon"] = os.path.join(c, "grid", filename) + + s = asset_type[:-1] if asset_type != "heroes" else "hero" + logging.info(f"Added {s.capitalize()} asset ({filename})") + _shortcuts = {} _existing = {} @@ -567,21 +585,6 @@ def add_shortcut(self, program_name: str, program_path: str): with open(os.path.join(c, "shortcuts.vdf"), "wb") as f: f.write(vdf.binary_dumps(_shortcuts)) - - logging.info(f"Searching SteamGridDB for {program_name} assets…") - asset_suffixes = { - "grids": "p", - "hgrids": "", - "heroes": "_hero", - "logos": "_logo", - "icons": "_icon", - } - for asset_type, suffix, in asset_suffixes.items(): - base_filename = f"{appid}{suffix}" - filename = SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) - if filename: - s = asset_type[:-1] if asset_type != "heroes" else "hero" - logging.info(f"Added {s.capitalize()} asset ({filename})") logging.info(f"Added shortcut for {program_name}") return Result(True) From 83d79652afe554c69b290bf7102d1d0313b00e7d Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sat, 4 Jan 2025 13:20:44 -0300 Subject: [PATCH 07/10] chore: simply use native `binascii.crc32` instead of `crc32c` lib --- bottles/backend/managers/steam.py | 13 +++++++++---- bottles/backend/managers/steamgriddb.py | 16 ++++++++++------ build-aux/pypi-deps.yaml | 12 ++++++------ requirements.txt | 1 - 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index b877a5421c8..439e8f9ff5f 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -20,7 +20,7 @@ import shlex import shutil import uuid -from crc32c import crc32c +from binascii import crc32 from datetime import datetime from functools import lru_cache from glob import glob @@ -525,7 +525,7 @@ def add_shortcut(self, program_name: str, program_path: str): logging.info(f"Adding shortcut for {program_name}") cmd = "xdg-open" args = f"bottles:run/'{self.config.Name}'/'{program_name}'" - appid = crc32c(str.encode(self.config.Name + program_name)) | 0x80000000 + appid = crc32(str.encode(self.config.Name + program_name)) | 0x80000000 if self.userdata_path is None: logging.warning("Userdata path is not set") @@ -560,9 +560,14 @@ def add_shortcut(self, program_name: str, program_path: str): "logos": "_logo", "icons": "_icon", } - for asset_type, suffix, in asset_suffixes.items(): + for ( + asset_type, + suffix, + ) in asset_suffixes.items(): base_filename = f"{appid}{suffix}" - filename = SteamGridDBManager.get_steam_game_asset(program_name, asset_type, c, base_filename) + filename = SteamGridDBManager.get_steam_game_asset( + program_name, asset_type, c, base_filename + ) if filename: if asset_type == "icons": shortcut["icon"] = os.path.join(c, "grid", filename) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index 57239086800..c94bbe8b02a 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -37,11 +37,13 @@ def get_game_grid(name: str, config: BottleConfig): if res.status_code == 200: return SteamGridDBManager.__save_grid(res.json(), config) - + @staticmethod - def get_steam_game_asset(name: str, asset_type: str, config_path: str, base_filename: str): + def get_steam_game_asset( + name: str, asset_type: str, config_path: str, base_filename: str + ): try: - #url = f"https://steamgrid.usebottles.com/api/search/{name}/{asset_type}" + # url = f"https://steamgrid.usebottles.com/api/search/{name}/{asset_type}" url = f"http://127.0.0.1:8000/api/search/{name}/{asset_type}" res = requests.get(url) except: @@ -49,7 +51,9 @@ def get_steam_game_asset(name: str, asset_type: str, config_path: str, base_file if res.status_code == 200: assets_path = os.path.join(config_path, "grid") - return SteamGridDBManager.__save_asset_to_steam(res.json(), assets_path, base_filename) + return SteamGridDBManager.__save_asset_to_steam( + res.json(), assets_path, base_filename + ) @staticmethod def __save_grid(url: str, config: BottleConfig): @@ -67,9 +71,9 @@ def __save_grid(url: str, config: BottleConfig): f.write(r.content) except Exception: return - + return f"grid:{filename}" - + @staticmethod def __save_asset_to_steam(url: str, assets_path: str, base_filename: str): if not os.path.exists(assets_path): diff --git a/build-aux/pypi-deps.yaml b/build-aux/pypi-deps.yaml index 624a86aa9d9..beceb8700bb 100644 --- a/build-aux/pypi-deps.yaml +++ b/build-aux/pypi-deps.yaml @@ -28,8 +28,8 @@ sources: url: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl sha256: e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 - type: file - url: https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - sha256: 90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b + url: https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: 8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15 only-arches: - x86_64 - type: file @@ -39,16 +39,16 @@ sources: url: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl sha256: 946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - type: file - url: https://files.pythonhosted.org/packages/a0/6b/34e6904ac99df811a06e42d8461d47b6e0c9b86e2fe7ee84934df6e35f0d/orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - sha256: a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09 + url: https://files.pythonhosted.org/packages/bb/f0/1d89c199aca0b00a35a5bd55f892093f25acc8c5a0334096d77a91c4d6a2/orjson-3.10.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: e1e91b90c0c26bd79593967c1adef421bcff88c9e723d49c93bb7ad8af80bc6b only-arches: - x86_64 - type: file url: https://files.pythonhosted.org/packages/d3/5e/76a9d08b4b4e4583f269cb9f64de267f9aeae0dacef23307f53a14211716/pathvalidate-3.2.1-py3-none-any.whl sha256: 9a6255eb8f63c9e2135b9be97a5ce08f10230128c4ae7b3e935378b82b22c4c9 - type: file - url: https://files.pythonhosted.org/packages/0e/44/192ede8c7f935643e4c8a56545fcac6ae1b8c50a77f54b2b1c4ab9fcae49/patool-3.0.0-py2.py3-none-any.whl - sha256: 928070d5f82a776534a290a52f4758e2c0dd9cd5a633e3f63f7270c8982833b8 + url: https://files.pythonhosted.org/packages/df/cc/67b2a7ad09ae95ae789c0c731c057a19c69592e3b6c66d53fc3e39a51961/patool-3.0.1-py2.py3-none-any.whl + sha256: 0d712d479c90bd3798e8c713069a45b893122578cbaf05241e6183ba4267ada5 - type: file url: https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl sha256: 76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f diff --git a/requirements.txt b/requirements.txt index 6b8930dc6d7..c940e5bbea1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,3 @@ idna==3.10 urllib3==2.2.3 certifi==2024.8.30 pefile==2024.8.26 -crc32c==2.7.1 From 3d483f299ae28f36a00175cec2a2f9c14232e41d Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sat, 4 Jan 2025 15:24:09 -0300 Subject: [PATCH 08/10] steamgriddb: better `requests` exception handling --- bottles/backend/managers/steamgriddb.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index c94bbe8b02a..135d6e046ad 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -18,6 +18,7 @@ import os import uuid import requests +from requests.exceptions import RequestException, HTTPError from bottles.backend.logger import Logger from bottles.backend.models.config import BottleConfig @@ -43,10 +44,14 @@ def get_steam_game_asset( name: str, asset_type: str, config_path: str, base_filename: str ): try: - # url = f"https://steamgrid.usebottles.com/api/search/{name}/{asset_type}" - url = f"http://127.0.0.1:8000/api/search/{name}/{asset_type}" + # url = f"https://steamgrid.usebottles.com/api/search/{name}" + url = f"http://127.0.0.1:8000/api/search/{name}" + if asset_type: + url += f"/{asset_type}" res = requests.get(url) - except: + res.raise_for_status() + except (RequestException, HTTPError) as e: + logging.error(str(e)) return if res.status_code == 200: From 045e8b5e48ce5ec6e5e57ee9477f68162dcc840b Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sat, 4 Jan 2025 21:02:51 -0300 Subject: [PATCH 09/10] steamgriddb: refactoring, drop obsolete methods, enhance exception handling --- bottles/backend/managers/library.py | 10 ++- bottles/backend/managers/steam.py | 64 +++++++++-------- bottles/backend/managers/steamgriddb.py | 91 +++++++++---------------- 3 files changed, 73 insertions(+), 92 deletions(-) diff --git a/bottles/backend/managers/library.py b/bottles/backend/managers/library.py index 95f28fff53e..ff04558a064 100644 --- a/bottles/backend/managers/library.py +++ b/bottles/backend/managers/library.py @@ -20,7 +20,7 @@ from bottles.backend.models.config import BottleConfig from bottles.backend.utils import yaml - +from bottles.backend.utils.manager import ManagerUtils from bottles.backend.logger import Logger from bottles.backend.globals import Paths from bottles.backend.managers.steamgriddb import SteamGridDBManager @@ -74,7 +74,10 @@ def add_to_library(self, data: dict, config: BottleConfig): logging.info(f"Adding new entry to library: {_uuid}") if not data.get("thumbnail"): - data["thumbnail"] = SteamGridDBManager.get_game_grid(data["name"], config) + grids_path = os.path.join(ManagerUtils.get_bottle_path(config), "grids") + data["thumbnail"] = SteamGridDBManager.get_steam_game_asset( + data["name"], grids_path + ) self.__library[_uuid] = data self.save_library() @@ -87,7 +90,8 @@ def download_thumbnail(self, _uuid: str, config: BottleConfig): return False data = self.__library.get(_uuid) - value = SteamGridDBManager.get_game_grid(data["name"], config) + os.path.join(ManagerUtils.get_bottle_path(config), "grids") + value = SteamGridDBManager.get_steam_game_asset(data["name"], config) if not value: return False diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 439e8f9ff5f..960f804b9f2 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -27,6 +27,8 @@ from pathlib import Path from typing import Union, Dict, Optional +from requests.exceptions import HTTPError, RequestException + from bottles.backend.globals import Paths from bottles.backend.managers.steamgriddb import SteamGridDBManager from bottles.backend.models.config import BottleConfig @@ -551,7 +553,7 @@ def add_shortcut(self, program_name: str, program_path: str): "tags": {"0": "Bottles"}, } - for c in confs: + for conf in confs: logging.info(f"Searching SteamGridDB for {program_name} assets…") asset_suffixes = { "grids": "p", @@ -560,36 +562,38 @@ def add_shortcut(self, program_name: str, program_path: str): "logos": "_logo", "icons": "_icon", } - for ( - asset_type, - suffix, - ) in asset_suffixes.items(): + for asset_type, suffix in asset_suffixes.items(): base_filename = f"{appid}{suffix}" - filename = SteamGridDBManager.get_steam_game_asset( - program_name, asset_type, c, base_filename - ) - if filename: - if asset_type == "icons": - shortcut["icon"] = os.path.join(c, "grid", filename) - - s = asset_type[:-1] if asset_type != "heroes" else "hero" - logging.info(f"Added {s.capitalize()} asset ({filename})") - - _shortcuts = {} - _existing = {} - - if os.path.exists(os.path.join(c, "shortcuts.vdf")): - with open(os.path.join(c, "shortcuts.vdf"), "rb") as f: - try: - _existing = vdf.binary_loads(f.read()).get("shortcuts", {}) - except: - continue - - _all = list(_existing.values()) + [shortcut] - _shortcuts = {"shortcuts": {str(i): s for i, s in enumerate(_all)}} - - with open(os.path.join(c, "shortcuts.vdf"), "wb") as f: - f.write(vdf.binary_dumps(_shortcuts)) + asset_path = os.path.join(conf, "grid", base_filename) + try: + filename = SteamGridDBManager.get_steam_game_asset( + program_name, asset_path, asset_type, reraise_exceptions=True + ) + except HTTPError: + # Usually missing asset (404), keep trying for the rest + continue + except: + # Unreachable host or issue saving files, nothing we can do + break + + if asset_type == "icons": + shortcut["icon"] = os.path.join(conf, "grid", filename) + + s = asset_type[:-1] if asset_type != "heroes" else "hero" + logging.info(f"Added {s.capitalize()} asset ({filename})") + + try: + with open(os.path.join(conf, "shortcuts.vdf"), "rb") as f: + _existing = vdf.binary_loads(f.read()).get("shortcuts", {}) + + _all = list(_existing.values()) + [shortcut] + _shortcuts = {"shortcuts": {str(i): s for i, s in enumerate(_all)}} + + with open(os.path.join(conf, "shortcuts.vdf"), "wb") as f: + f.write(vdf.binary_dumps(_shortcuts)) + + except (OSError, IOError) as e: + logging.error(e) logging.info(f"Added shortcut for {program_name}") return Result(True) diff --git a/bottles/backend/managers/steamgriddb.py b/bottles/backend/managers/steamgriddb.py index 135d6e046ad..f6261e89b13 100644 --- a/bottles/backend/managers/steamgriddb.py +++ b/bottles/backend/managers/steamgriddb.py @@ -16,9 +16,10 @@ # import os -import uuid +from typing import Optional + import requests -from requests.exceptions import RequestException, HTTPError +from requests.exceptions import HTTPError, RequestException from bottles.backend.logger import Logger from bottles.backend.models.config import BottleConfig @@ -28,70 +29,42 @@ class SteamGridDBManager: - @staticmethod - def get_game_grid(name: str, config: BottleConfig): - try: - url = f"https://steamgrid.usebottles.com/api/search/{name}" - res = requests.get(url) - except: - return - - if res.status_code == 200: - return SteamGridDBManager.__save_grid(res.json(), config) - - @staticmethod def get_steam_game_asset( - name: str, asset_type: str, config_path: str, base_filename: str - ): + program_name: str, + asset_path: str, + asset_type: Optional[str] = None, + reraise_exceptions: bool = False, + ) -> Optional[str]: try: - # url = f"https://steamgrid.usebottles.com/api/search/{name}" - url = f"http://127.0.0.1:8000/api/search/{name}" + # url = f"https://steamgrid.usebottles.com/api/search/{program_name}" + url = f"http://127.0.0.1:8000/api/search/{program_name}" if asset_type: - url += f"/{asset_type}" - res = requests.get(url) + url = f"{url}/{asset_type}" + res = requests.get(url, timeout=5) res.raise_for_status() - except (RequestException, HTTPError) as e: - logging.error(str(e)) - return - - if res.status_code == 200: - assets_path = os.path.join(config_path, "grid") - return SteamGridDBManager.__save_asset_to_steam( - res.json(), assets_path, base_filename - ) + filename = SteamGridDBManager.__save_asset_to_steam(res.json(), asset_path) - @staticmethod - def __save_grid(url: str, config: BottleConfig): - grids_path = os.path.join(ManagerUtils.get_bottle_path(config), "grids") - if not os.path.exists(grids_path): - os.makedirs(grids_path) - - ext = url.split(".")[-1] - filename = str(uuid.uuid4()) + "." + ext - path = os.path.join(grids_path, filename) - - try: - r = requests.get(url) - with open(path, "wb") as f: - f.write(r.content) - except Exception: - return + except Exception as e: + if isinstance(e, HTTPError): + logging.warning(str(e)) + else: + logging.error(str(e)) + if reraise_exceptions: + raise - return f"grid:{filename}" + return filename @staticmethod - def __save_asset_to_steam(url: str, assets_path: str, base_filename: str): - if not os.path.exists(assets_path): - os.makedirs(assets_path) + def __save_asset_to_steam(url: str, asset_path: str) -> str: + asset_dir = os.path.dirname(asset_path) + if not os.path.exists(asset_dir): + os.makedirs(asset_dir) - try: - r = requests.get(url) - ext = url.rpartition(".")[-1] - filename = f"{base_filename}.{ext}" - path = os.path.join(assets_path, filename) - with open(path, "wb") as f: - f.write(r.content) - except Exception: - return + res = requests.get(url) + res.raise_for_status() + ext = os.path.splitext(url)[-1] + asset_path += ext + with open(asset_path, "wb") as img: + img.write(res.content) - return filename + return os.path.basename(asset_path) From 8b7acbfb3bc57634302113f6e222176a11d072a8 Mon Sep 17 00:00:00 2001 From: EmoonX Date: Sat, 4 Jan 2025 22:09:21 -0300 Subject: [PATCH 10/10] steam: better success/failure handling for `add_shortcut` --- bottles/backend/managers/steam.py | 73 +++++++++++++++++-------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 960f804b9f2..63e2cb5dd21 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -524,36 +524,7 @@ def launch_app(prefix: str): SignalManager.send(Signals.GShowUri, Result(data=uri)) def add_shortcut(self, program_name: str, program_path: str): - logging.info(f"Adding shortcut for {program_name}") - cmd = "xdg-open" - args = f"bottles:run/'{self.config.Name}'/'{program_name}'" - appid = crc32(str.encode(self.config.Name + program_name)) | 0x80000000 - - if self.userdata_path is None: - logging.warning("Userdata path is not set") - return Result(False) - - confs = glob(os.path.join(self.userdata_path, "*/config/")) - shortcut = { - "appid": appid - 0x100000000, - "AppName": program_name, - "Exe": cmd, - "StartDir": ManagerUtils.get_bottle_path(self.config), - "icon": ManagerUtils.extract_icon(self.config, program_name, program_path), - "ShortcutPath": "", - "LaunchOptions": args, - "IsHidden": 0, - "AllowDesktopConfig": 1, - "AllowOverlay": 1, - "OpenVR": 0, - "Devkit": 0, - "DevkitGameID": "", - "DevkitOverrideAppID": "", - "LastPlayTime": 0, - "tags": {"0": "Bottles"}, - } - - for conf in confs: + def __add_to_user_conf(conf: str) -> bool: logging.info(f"Searching SteamGridDB for {program_name} assets…") asset_suffixes = { "grids": "p", @@ -594,6 +565,44 @@ def add_shortcut(self, program_name: str, program_path: str): except (OSError, IOError) as e: logging.error(e) + return False + + return True + + logging.info(f"Adding shortcut for {program_name}") + cmd = "xdg-open" + args = f"bottles:run/'{self.config.Name}'/'{program_name}'" + appid = crc32(str.encode(self.config.Name + program_name)) | 0x80000000 + + if self.userdata_path is None: + logging.warning("Userdata path is not set") + return Result(False) + + confs = glob(os.path.join(self.userdata_path, "*/config/")) + shortcut = { + "appid": appid - 0x100000000, + "AppName": program_name, + "Exe": cmd, + "StartDir": ManagerUtils.get_bottle_path(self.config), + "icon": ManagerUtils.extract_icon(self.config, program_name, program_path), + "ShortcutPath": "", + "LaunchOptions": args, + "IsHidden": 0, + "AllowDesktopConfig": 1, + "AllowOverlay": 1, + "OpenVR": 0, + "Devkit": 0, + "DevkitGameID": "", + "DevkitOverrideAppID": "", + "LastPlayTime": 0, + "tags": {"0": "Bottles"}, + } + + ok = False + for conf in confs: + ok |= __add_to_user_conf(conf) + + if ok: + logging.info(f"Added shortcut for {program_name}") - logging.info(f"Added shortcut for {program_name}") - return Result(True) + return Result(ok)