From a4f0e5eece695f1e2200a1ab0ba6e1623b55ee36 Mon Sep 17 00:00:00 2001 From: LunaChocken Date: Mon, 9 Jun 2025 16:07:29 +0100 Subject: [PATCH] add caching --- main.py | 78 +++++++++++++++++++++++++++++++++++++------- pyproject.toml | 1 + src/prompt_helper.py | 12 +++++-- src/vdfparser.py | 6 ++++ uv.lock | 70 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 35f862c..d784861 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,10 @@ from rich.columns import Columns from rich.panel import Panel import src.vdfparser as vd from src.prompt_helper import PromptHelper +# Commandline options support +import argparse + + class TableItem: def __init__(self, name, data='', colour='white', type='desc'): @@ -25,11 +29,19 @@ class TableItem: def __str__(self): return f"\n[white]{self.name}[/white]: [{self.colour}]"+self.generate_link()+"" + class SteamGamePathTool: - def __init__(self): - self.steam_path = vd.fetch_steam_path() + # arg mode means user input will be skipped and take all args from command line + def __init__(self, steamPath=vd.fetch_steam_path(), arg_mode_enabled=None, game_name=None, game_id=None, all=False): + self.steam_path = steamPath self.steam_vdf_path = vd.fetch_steam_vdf() + # arg support + self.arg_mode_enabled = arg_mode_enabled + self.game_name = game_name + self.game_id = game_id + self.all = all + if self.steam_vdf_path is None: print("Steam VDF file not found") # Add user input to attempt to find @@ -43,10 +55,13 @@ class SteamGamePathTool: games = vd.fetchall_vdfs(self.steam_vdf) self.games = self.sort_games(games) + self.prompter = PromptHelper(games) for library in self.steam_library_locations: print(f"[+] Found Steam library at: {library}") - self.prompter = PromptHelper(games) + if arg_mode_enabled: + self.arg_mode() + return while True: self.prompt_user() try: @@ -55,6 +70,26 @@ class SteamGamePathTool: print("\nExiting...") break + + def arg_mode(self): + if self.all: + console = Console() + game_rend = [Panel(self.get_game_content(game), expand=True) for game in self.games] + console.print(Columns(game_rend)) + return + if self.game_id: + game = self.prompter.find_game_num(self.game_id) + elif self.game_name: + game = self.prompter.find_game_str(self.game_name) + else: + return + console = Console() + data = self.get_game_content(game) + if data: + console.print(Panel(data, expand=True)) + else: + console.print("No game found") + def prompt_user(self): game = self.prompter.prompt_game(text="Input (game name | appid | all | q/quit): ") if game is None: @@ -79,15 +114,34 @@ class SteamGamePathTool: :return: string formatted for table using rich library """ # Spaces must be replaced with %20 otherwise they won't link properly - - string = f"""[b]{game['name']}[/b]""" - string+=str(TableItem(name="Game acf", data=f"{game['acf_path'].replace(' ', '%20')}", colour="green",type="file")) - string+=str(TableItem(name="Game Path", data=f"{game['true_path'].replace(' ', '%20')}", colour="blue",type="dir")) - if game['workshop_path']: - string+=str(TableItem(name="Game Workshop Path", data=f"{game['workshop_path'].replace(' ', '%20')}", colour="blue", type="dir")) - if game["compatdata_path"]: - string+=str(TableItem(name="Game Compatdata Path",data=f"{game['compatdata_path'].replace(' ', '%20')}",colour="blue",type="dir")) + try: + string = f"""[b]{game['name']}[/b]""" + string+=str(TableItem(name="Game ID", data=f"{game['appid']}", colour="blue",type="desc")) + string+=str(TableItem(name="Game acf", data=f"{game['acf_path'].replace(' ', '%20')}", colour="green",type="file")) + string+=str(TableItem(name="Game Path", data=f"{game['true_path'].replace(' ', '%20')}", colour="blue",type="dir")) + if game['workshop_path']: + string+=str(TableItem(name="Game Workshop Path", data=f"{game['workshop_path'].replace(' ', '%20')}", colour="blue", type="dir")) + if game["compatdata_path"]: + string+=str(TableItem(name="Game Compatdata Path",data=f"{game['compatdata_path'].replace(' ', '%20')}",colour="blue",type="dir")) + except TypeError: + return None return string if __name__ == "__main__": - steam_path_tool = SteamGamePathTool() + print("""Welcome to S-Path +A tool to print the locations of steam games, their respective gameID, workshop path, etc.""") + + + parser = argparse.ArgumentParser() + parser.add_argument('--name', help='Game name to search for') + parser.add_argument('--id', help='Game ID to search for') + # parser.add_argument('--help', help='Display help message') + parser.add_argument('--steam-path', help='Steam path to use') + parser.add_argument('--all', help='Displays all games installed', action='store_true') + + args = parser.parse_args() + + if args.name or args.id or args.all: + steam_path_tool = SteamGamePathTool(game_id=args.id, game_name=args.name, arg_mode_enabled=True, all=args.all) + else: + steam_path_tool = SteamGamePathTool() diff --git a/pyproject.toml b/pyproject.toml index 3df181e..d244126 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.11" dependencies = [ + "cachier>=3.1.2", "prompt-toolkit>=3.0.51", "pyinstaller>=6.14.0", "rich>=14.0.0", diff --git a/src/prompt_helper.py b/src/prompt_helper.py index eea546d..c437e04 100644 --- a/src/prompt_helper.py +++ b/src/prompt_helper.py @@ -1,7 +1,12 @@ import prompt_toolkit as pt from prompt_toolkit.completion import WordCompleter, FuzzyCompleter import sys +from cachier import cachier +import datetime + + +@cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') def generate_completer(game_list): g_list = [x['name'] for x in game_list] + [x['appid'] for x in game_list] g_list.append("q") @@ -14,11 +19,14 @@ class PromptHelper: self.game_list = game_list self.completer = generate_completer(game_list) - def find_game_str(self, game_name): + @cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') + def find_game_str(self, game_name) -> dict|None: for game in self.game_list: if game['name'] == game_name: return game - def find_game_num(self, appid: str): + + @cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') + def find_game_num(self, appid: str) -> dict|None: for game in self.game_list: if game['appid'] == appid: return game diff --git a/src/vdfparser.py b/src/vdfparser.py index 494658c..5fca25f 100644 --- a/src/vdfparser.py +++ b/src/vdfparser.py @@ -1,6 +1,9 @@ import os import vdf +from cachier import cachier +import datetime +@cachier(stale_after=datetime.timedelta(days=1)) def parse_vdf(steam_vdf_path) -> dict: """ Reads the Steam vdf file at the given path and returns the corresponding dict. @@ -11,6 +14,7 @@ def parse_vdf(steam_vdf_path) -> dict: vdf_data = vdf.loads(open(steam_vdf_path, 'r').read()) return vdf_data +@cachier(stale_after=datetime.timedelta(days=1)) def fetch_ids(vdf_json: dict): """ Prints all Steam app IDs from the given vdf data. @@ -76,11 +80,13 @@ def find_extra_locations(vdf_json): return steam_library_locations # Reads manifest of individual game vdfs +@cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') def read_game_vdf(gameID: int, steam_vdf_json: dict, path, game) -> dict: with open(os.path.join(path, game), 'r') as f: game_vdf = vdf.loads(f.read()) return game_vdf['AppState'] +@cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') def fetchall_vdfs(steam_vdf_json: dict): """ Reads all Steam game manifests in the given Steam VDF data and returns a list of dictionaries containing information about each game. diff --git a/uv.lock b/uv.lock index 4730d25..585fccb 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212 }, ] +[[package]] +name = "cachier" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "portalocker" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/95/f17aaba6b786f968a8078c8e86f8004652ba9157d8aeb329837c85d10a43/cachier-3.1.2.tar.gz", hash = "sha256:8ef53b6ae83ba04d8864d2e1f45eb8b46eecb611d0f6a24c006e8fda074c6557", size = 27319 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/9f/11e07e8ffbc921d7c963568937b0290ac9204455a3f3cd866164e82f36ba/cachier-3.1.2-py3-none-any.whl", hash = "sha256:24c0fefd6aef1d38a4337590aba82f3c4bd844df18393ba93c7fd7dcc98da304", size = 24098 }, +] + [[package]] name = "macholib" version = "1.16.3" @@ -62,6 +75,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791 }, ] +[[package]] +name = "portalocker" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/91/8bfe23e1f7f630f2061ef38b5225d9fda9068d6a30fcbc187951e678e630/portalocker-3.1.1.tar.gz", hash = "sha256:ec20f6dda2ad9ce89fa399a5f31f4f1495f515958f0cb7ca6543cef7bb5a749e", size = 43708 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/60/1974cfdd5bb770568ddc6f89f3e0df4cfdd1acffd5a609dff5e95f48c6e2/portalocker-3.1.1-py3-none-any.whl", hash = "sha256:80e984e24de292ff258a5bea0e4f3f778fff84c0ae1275dbaebc4658de4aacb3", size = 19661 }, +] + [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -124,6 +149,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/e1/ed48c7074145898e5c5b0072e87be975c5bd6a1d0f08c27a1daa7064fca0/pyinstaller_hooks_contrib-2025.4-py3-none-any.whl", hash = "sha256:6c2d73269b4c484eb40051fc1acee0beb113c2cfb3b37437b8394faae6f0d072", size = 434451 }, ] +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -160,6 +201,7 @@ name = "steamgamepath" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "cachier" }, { name = "prompt-toolkit" }, { name = "pyinstaller" }, { name = "rich" }, @@ -168,6 +210,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "cachier", specifier = ">=3.1.2" }, { name = "prompt-toolkit", specifier = ">=3.0.51" }, { name = "pyinstaller", specifier = ">=6.14.0" }, { name = "rich", specifier = ">=14.0.0" }, @@ -183,6 +226,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/60/6456b687cf55cf60020dcd01f9bc51561c3cc84f05fd8e0feb71ce60f894/vdf-3.4-py2.py3-none-any.whl", hash = "sha256:68c1a125cc49e343d535af2dd25074e9cb0908c6607f073947c4a04bbe234534", size = 10357 }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] + [[package]] name = "wcwidth" version = "0.2.13"