From f3dcdbf0d2df0b37f12c90d716e482b08443b421 Mon Sep 17 00:00:00 2001 From: GeoffreyCoulaud Date: Wed, 26 Jul 2023 03:53:17 +0200 Subject: [PATCH 1/4] Using a named tuple to store source locations --- src/importer/sources/bottles_source.py | 35 +++++++++------- src/importer/sources/flatpak_source.py | 31 ++++++++------ src/importer/sources/heroic_source.py | 45 ++++++++++++-------- src/importer/sources/itch_source.py | 27 +++++++----- src/importer/sources/legendary_source.py | 33 +++++++++------ src/importer/sources/lutris_source.py | 53 ++++++++++++++---------- src/importer/sources/source.py | 8 ++-- src/importer/sources/steam_source.py | 36 +++++++++------- src/preferences.py | 22 ++++++---- 9 files changed, 172 insertions(+), 118 deletions(-) diff --git a/src/importer/sources/bottles_source.py b/src/importer/sources/bottles_source.py index 8829023..42eebaa 100644 --- a/src/importer/sources/bottles_source.py +++ b/src/importer/sources/bottles_source.py @@ -20,6 +20,7 @@ from pathlib import Path from time import time +from typing import NamedTuple import yaml @@ -35,7 +36,7 @@ class BottlesSourceIterable(SourceIterable): def __iter__(self): """Generator method producing games""" - data = self.source.data_location["library.yml"].read_text("utf-8") + data = self.source.locations.data["library.yml"].read_text("utf-8") library: dict = yaml.safe_load(data) added_time = int(time()) @@ -58,11 +59,11 @@ class BottlesSourceIterable(SourceIterable): # as Cartridges can't access directories picked via Bottles' file picker portal bottles_location = Path( yaml.safe_load( - self.source.data_location["data.yml"].read_text("utf-8") + self.source.locations.data["data.yml"].read_text("utf-8") )["custom_bottles_path"] ) except (FileNotFoundError, KeyError): - bottles_location = self.source.data_location.root / "bottles" + bottles_location = self.source.locations.data.root / "bottles" bottle_path = entry["bottle"]["path"] @@ -76,6 +77,10 @@ class BottlesSourceIterable(SourceIterable): yield (game, additional_data) +class BottlesLocations(NamedTuple): + data: Location + + class BottlesSource(URLExecutableSource): """Generic Bottles source""" @@ -84,15 +89,17 @@ class BottlesSource(URLExecutableSource): url_format = 'bottles:run/"{bottle_name}"/"{game_name}"' available_on = {"linux"} - data_location = Location( - schema_key="bottles-location", - candidates=( - shared.flatpak_dir / "com.usebottles.bottles" / "data" / "bottles", - shared.data_dir / "bottles/", - shared.home / ".local" / "share" / "bottles", - ), - paths={ - "library.yml": (False, "library.yml"), - "data.yml": (False, "data.yml"), - }, + locations = BottlesLocations( + Location( + schema_key="bottles-location", + candidates=( + shared.flatpak_dir / "com.usebottles.bottles" / "data" / "bottles", + shared.data_dir / "bottles/", + shared.home / ".local" / "share" / "bottles", + ), + paths={ + "library.yml": (False, "library.yml"), + "data.yml": (False, "data.yml"), + }, + ) ) diff --git a/src/importer/sources/flatpak_source.py b/src/importer/sources/flatpak_source.py index 2c9a4aa..f29028c 100644 --- a/src/importer/sources/flatpak_source.py +++ b/src/importer/sources/flatpak_source.py @@ -19,6 +19,7 @@ from pathlib import Path from time import time +from typing import NamedTuple from gi.repository import GLib, Gtk @@ -37,7 +38,7 @@ class FlatpakSourceIterable(SourceIterable): added_time = int(time()) icon_theme = Gtk.IconTheme.new() - icon_theme.add_search_path(str(self.source.data_location["icons"])) + icon_theme.add_search_path(str(self.source.locations.data["icons"])) blacklist = ( {"hu.kramo.Cartridges", "hu.kramo.Cartridges.Devel"} @@ -53,7 +54,7 @@ class FlatpakSourceIterable(SourceIterable): } ) - for entry in (self.source.data_location["applications"]).iterdir(): + for entry in (self.source.locations.data["applications"]).iterdir(): if entry.suffix != ".desktop": continue @@ -111,6 +112,10 @@ class FlatpakSourceIterable(SourceIterable): yield (game, additional_data) +class FlatpakLocations(NamedTuple): + data: Location + + class FlatpakSource(Source): """Generic Flatpak source""" @@ -119,14 +124,16 @@ class FlatpakSource(Source): executable_format = "flatpak run {flatpak_id}" available_on = {"linux"} - data_location = Location( - schema_key="flatpak-location", - candidates=( - "/var/lib/flatpak/", - shared.data_dir / "flatpak", - ), - paths={ - "applications": (True, "exports/share/applications"), - "icons": (True, "exports/share/icons"), - }, + locations = FlatpakLocations( + Location( + schema_key="flatpak-location", + candidates=( + "/var/lib/flatpak/", + shared.data_dir / "flatpak", + ), + paths={ + "applications": (True, "exports/share/applications"), + "icons": (True, "exports/share/icons"), + }, + ) ) diff --git a/src/importer/sources/heroic_source.py b/src/importer/sources/heroic_source.py index d169129..1c28f14 100644 --- a/src/importer/sources/heroic_source.py +++ b/src/importer/sources/heroic_source.py @@ -25,7 +25,7 @@ from hashlib import sha256 from json import JSONDecodeError from pathlib import Path from time import time -from typing import Iterable, Optional, TypedDict +from typing import Iterable, NamedTuple, Optional, TypedDict from functools import cached_property from src import shared @@ -87,7 +87,7 @@ class SubSourceIterable(Iterable): @cached_property def library_path(self) -> Path: - path = self.source.config_location.root / self.relative_library_path + path = self.source.locations.config.root / self.relative_library_path logging.debug("Using Heroic %s library.json path %s", self.name, path) return path @@ -116,7 +116,7 @@ class SubSourceIterable(Iterable): # Filenames are derived from the URL that heroic used to get the file uri: str = entry["art_square"] + self.image_uri_params digest = sha256(uri.encode()).hexdigest() - image_path = self.source.config_location.root / "images-cache" / digest + image_path = self.source.locations.config.root / "images-cache" / digest additional_data = {"local_image_path": image_path} return (game, additional_data) @@ -159,7 +159,7 @@ class StoreSubSourceIterable(SubSourceIterable): @cached_property def installed_path(self) -> Path: - path = self.source.config_location.root / self.relative_installed_path + path = self.source.locations.config.root / self.relative_installed_path logging.debug("Using Heroic %s installed.json path %s", self.name, path) return path @@ -226,7 +226,7 @@ class LegendaryIterable(StoreSubSourceIterable): and remove this property override. """ - heroic_config_path = self.source.config_location.root + heroic_config_path = self.source.locations.config.root # Heroic >= 2.9 if (path := heroic_config_path / "legendaryConfig").is_dir(): logging.debug("Using Heroic >= 2.9 legendary file") @@ -308,7 +308,7 @@ class HeroicSourceIterable(SourceIterable): """ try: - store = path_json_load(self.source.config_location["store_config.json"]) + store = path_json_load(self.source.locations.config["store_config.json"]) self.hidden_app_names = { app_name for game in store["games"]["hidden"] @@ -349,6 +349,10 @@ class HeroicSourceIterable(SourceIterable): continue +class HeroicLocations(NamedTuple): + config: Location + + class HeroicSource(URLExecutableSource): """Generic Heroic Games Launcher source""" @@ -357,18 +361,23 @@ class HeroicSource(URLExecutableSource): url_format = "heroic://launch/{app_name}" available_on = {"linux", "win32"} - config_location = Location( - schema_key="heroic-location", - candidates=( - shared.config_dir / "heroic", - shared.home / ".config" / "heroic", - shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config" / "heroic", - shared.appdata_dir / "heroic", - ), - paths={ - "config.json": (False, "config.json"), - "store_config.json": (False, Path("store") / "config.json"), - }, + locations = HeroicLocations( + Location( + schema_key="heroic-location", + candidates=( + shared.config_dir / "heroic", + shared.home / ".config" / "heroic", + shared.flatpak_dir + / "com.heroicgameslauncher.hgl" + / "config" + / "heroic", + shared.appdata_dir / "heroic", + ), + paths={ + "config.json": (False, "config.json"), + "store_config.json": (False, Path("store") / "config.json"), + }, + ) ) @property diff --git a/src/importer/sources/itch_source.py b/src/importer/sources/itch_source.py index a6d8990..dc4d4f9 100644 --- a/src/importer/sources/itch_source.py +++ b/src/importer/sources/itch_source.py @@ -21,6 +21,7 @@ from shutil import rmtree from sqlite3 import connect from time import time +from typing import NamedTuple from src import shared from src.game import Game @@ -51,7 +52,7 @@ class ItchSourceIterable(SourceIterable): caves.game_id = games.id ; """ - db_path = copy_db(self.source.config_location["butler.db"]) + db_path = copy_db(self.source.locations.config["butler.db"]) connection = connect(db_path) cursor = connection.execute(db_request) @@ -74,19 +75,25 @@ class ItchSourceIterable(SourceIterable): rmtree(str(db_path.parent)) +class ItchLocations(NamedTuple): + config: Location + + class ItchSource(URLExecutableSource): name = _("itch") iterable_class = ItchSourceIterable url_format = "itch://caves/{cave_id}/launch" available_on = {"linux", "win32"} - config_location = Location( - schema_key="itch-location", - candidates=( - shared.flatpak_dir / "io.itch.itch" / "config" / "itch", - shared.config_dir / "itch", - shared.home / ".config" / "itch", - shared.appdata_dir / "itch", - ), - paths={"butler.db": (False, "db/butler.db")}, + locations = ItchLocations( + Location( + schema_key="itch-location", + candidates=( + shared.flatpak_dir / "io.itch.itch" / "config" / "itch", + shared.config_dir / "itch", + shared.home / ".config" / "itch", + shared.appdata_dir / "itch", + ), + paths={"butler.db": (False, "db/butler.db")}, + ) ) diff --git a/src/importer/sources/legendary_source.py b/src/importer/sources/legendary_source.py index c7e06de..529bd8c 100644 --- a/src/importer/sources/legendary_source.py +++ b/src/importer/sources/legendary_source.py @@ -21,6 +21,7 @@ import json import logging from json import JSONDecodeError from time import time +from typing import NamedTuple from src import shared from src.game import Game @@ -50,7 +51,7 @@ class LegendarySourceIterable(SourceIterable): data = {} # Get additional metadata from file (optional) - metadata_file = self.source.config_location["metadata"] / f"{app_name}.json" + metadata_file = self.source.locations.config["metadata"] / f"{app_name}.json" try: metadata = json.load(metadata_file.open()) values["developer"] = metadata["metadata"]["developer"] @@ -66,7 +67,7 @@ class LegendarySourceIterable(SourceIterable): def __iter__(self): # Open library - file = self.source.config_location["installed.json"] + file = self.source.locations.config["installed.json"] try: library: dict = json.load(file.open()) except (JSONDecodeError, OSError): @@ -88,20 +89,26 @@ class LegendarySourceIterable(SourceIterable): yield result +class LegendaryLocations(NamedTuple): + config: Location + + class LegendarySource(Source): name = _("Legendary") executable_format = "legendary launch {app_name}" available_on = {"linux"} - iterable_class = LegendarySourceIterable - config_location: Location = Location( - schema_key="legendary-location", - candidates=( - shared.config_dir / "legendary", - shared.home / ".config" / "legendary", - ), - paths={ - "installed.json": (False, "installed.json"), - "metadata": (True, "metadata"), - }, + + locations = LegendaryLocations( + Location( + schema_key="legendary-location", + candidates=( + shared.config_dir / "legendary", + shared.home / ".config" / "legendary", + ), + paths={ + "installed.json": (False, "installed.json"), + "metadata": (True, "metadata"), + }, + ) ) diff --git a/src/importer/sources/lutris_source.py b/src/importer/sources/lutris_source.py index 7c100a8..5a26fc1 100644 --- a/src/importer/sources/lutris_source.py +++ b/src/importer/sources/lutris_source.py @@ -20,6 +20,7 @@ from shutil import rmtree from sqlite3 import connect from time import time +from typing import NamedTuple from src import shared from src.game import Game @@ -51,7 +52,7 @@ class LutrisSourceIterable(SourceIterable): "import_steam": shared.schema.get_boolean("lutris-import-steam"), "import_flatpak": shared.schema.get_boolean("lutris-import-flatpak"), } - db_path = copy_db(self.source.data_location["pga.db"]) + db_path = copy_db(self.source.locations.config["pga.db"]) connection = connect(db_path) cursor = connection.execute(request, params) @@ -73,7 +74,7 @@ class LutrisSourceIterable(SourceIterable): game = Game(values) # Get official image path - image_path = self.source.cache_location["coverart"] / f"{row[2]}.jpg" + image_path = self.source.locations.cache["coverart"] / f"{row[2]}.jpg" additional_data = {"local_image_path": image_path} # Produce game @@ -83,6 +84,11 @@ class LutrisSourceIterable(SourceIterable): rmtree(str(db_path.parent)) +class LutrisLocations(NamedTuple): + config: Location + cache: Location + + class LutrisSource(URLExecutableSource): """Generic Lutris source""" @@ -91,30 +97,31 @@ class LutrisSource(URLExecutableSource): url_format = "lutris:rungameid/{game_id}" available_on = {"linux"} - # FIXME possible bug: location picks ~/.var... and cache_lcoation picks ~/.local... + # FIXME possible bug: config picks ~/.var... and cache picks ~/.local... - data_location = Location( - schema_key="lutris-location", - candidates=( - shared.flatpak_dir / "net.lutris.Lutris" / "data" / "lutris", - shared.data_dir / "lutris", - shared.home / ".local" / "share" / "lutris", + locations = LutrisLocations( + Location( + schema_key="lutris-location", + candidates=( + shared.flatpak_dir / "net.lutris.Lutris" / "data" / "lutris", + shared.data_dir / "lutris", + shared.home / ".local" / "share" / "lutris", + ), + paths={ + "pga.db": (False, "pga.db"), + }, ), - paths={ - "pga.db": (False, "pga.db"), - }, - ) - - cache_location = Location( - schema_key="lutris-cache-location", - candidates=( - shared.flatpak_dir / "net.lutris.Lutris" / "cache" / "lutris", - shared.cache_dir / "lutris", - shared.home / ".cache" / "lutris", + Location( + schema_key="lutris-cache-location", + candidates=( + shared.flatpak_dir / "net.lutris.Lutris" / "cache" / "lutris", + shared.cache_dir / "lutris", + shared.home / ".cache" / "lutris", + ), + paths={ + "coverart": (True, "coverart"), + }, ), - paths={ - "coverart": (True, "coverart"), - }, ) @property diff --git a/src/importer/sources/source.py b/src/importer/sources/source.py index d7ba467..164a792 100644 --- a/src/importer/sources/source.py +++ b/src/importer/sources/source.py @@ -19,8 +19,8 @@ import sys from abc import abstractmethod -from collections.abc import Iterable, Iterator -from typing import Any, Generator, Optional +from collections.abc import Iterable +from typing import Any, Generator, Optional, Collection from src.game import Game from src.importer.sources.location import Location @@ -54,10 +54,8 @@ class Source(Iterable): name: str variant: str = None available_on: set[str] = set() - data_location: Optional[Location] = None - cache_location: Optional[Location] = None - config_location: Optional[Location] = None iterable_class: type[SourceIterable] + locations: Collection[Location] @property def full_name(self) -> str: diff --git a/src/importer/sources/steam_source.py b/src/importer/sources/steam_source.py index 7e65e5b..e4e9cfb 100644 --- a/src/importer/sources/steam_source.py +++ b/src/importer/sources/steam_source.py @@ -22,7 +22,7 @@ import logging import re from pathlib import Path from time import time -from typing import Iterable +from typing import Iterable, NamedTuple from src import shared from src.game import Game @@ -36,7 +36,7 @@ class SteamSourceIterable(SourceIterable): def get_manifest_dirs(self) -> Iterable[Path]: """Get dirs that contain steam app manifests""" - libraryfolders_path = self.source.data_location["libraryfolders.vdf"] + libraryfolders_path = self.source.locations.data["libraryfolders.vdf"] with open(libraryfolders_path, "r", encoding="utf-8") as file: contents = file.read() return [ @@ -100,7 +100,7 @@ class SteamSourceIterable(SourceIterable): # Add official cover image image_path = ( - self.source.data_location["librarycache"] + self.source.locations.data["librarycache"] / f"{appid}_library_600x900.jpg" ) additional_data = {"local_image_path": image_path, "steam_appid": appid} @@ -109,22 +109,28 @@ class SteamSourceIterable(SourceIterable): yield (game, additional_data) +class SteamLocations(NamedTuple): + data: Location + + class SteamSource(URLExecutableSource): name = _("Steam") available_on = {"linux", "win32"} iterable_class = SteamSourceIterable url_format = "steam://rungameid/{game_id}" - data_location = Location( - schema_key="steam-location", - candidates=( - shared.home / ".steam" / "steam", - shared.data_dir / "Steam", - shared.flatpak_dir / "com.valvesoftware.Steam" / "data" / "Steam", - shared.programfiles32_dir / "Steam", - ), - paths={ - "libraryfolders.vdf": (False, "steamapps/libraryfolders.vdf"), - "librarycache": (True, "appcache/librarycache"), - }, + locations = SteamLocations( + Location( + schema_key="steam-location", + candidates=( + shared.home / ".steam" / "steam", + shared.data_dir / "Steam", + shared.flatpak_dir / "com.valvesoftware.Steam" / "data" / "Steam", + shared.programfiles32_dir / "Steam", + ), + paths={ + "libraryfolders.vdf": (False, "steamapps/libraryfolders.vdf"), + "librarycache": (True, "appcache/librarycache"), + }, + ) ) diff --git a/src/preferences.py b/src/preferences.py index fdafd69..7300523 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -262,19 +262,19 @@ class PreferencesWindow(Adw.PreferencesWindow): subtitle = re.sub("/run/user/\\d*/doc/.*/", "", str(path)) action_row.set_subtitle(subtitle) - def resolve_locations(self, source): + def resolve_locations(self, source: Source): """Resolve locations and add a warning if location cannot be found""" def clear_warning_selection(_widget, label): label.select_region(-1, -1) - for location_name in ("data", "config", "cache"): + for location_name, location in source.locations._asdict().items(): action_row = getattr(self, f"{source.id}_{location_name}_action_row", None) if not action_row: continue try: - getattr(source, f"{location_name}_location", None).resolve() + location.resolve() except UnresolvableLocationError: popover = Gtk.Popover( @@ -325,10 +325,14 @@ class PreferencesWindow(Adw.PreferencesWindow): return # Good picked location - location = getattr(source, f"{location_name}_location") + location = getattr(source.locations, location_name) if location.check_candidate(path): # Set the schema - infix = "-cache" if location_name == "cache" else "" + match location_name: + case "config" | "data": + infix = "" + case _: + infix = f"-{location_name}" key = f"{source.id}{infix}-location" value = str(path) shared.schema.set_string(key, value) @@ -381,10 +385,12 @@ class PreferencesWindow(Adw.PreferencesWindow): ) # Connect dir picker buttons - for location in ("data", "config", "cache"): - button = getattr(self, f"{source.id}_{location}_file_chooser_button", None) + for location_name in source.locations._asdict(): + button = getattr( + self, f"{source.id}_{location_name}_file_chooser_button", None + ) if button is not None: - button.connect("clicked", self.choose_folder, set_dir, location) + button.connect("clicked", self.choose_folder, set_dir, location_name) # Set the source row subtitles self.resolve_locations(source) From 0677eae0a24d674518472940c283c821f904cc5a Mon Sep 17 00:00:00 2001 From: GeoffreyCoulaud Date: Wed, 26 Jul 2023 03:55:23 +0200 Subject: [PATCH 2/4] Removed unused import --- src/importer/sources/source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/importer/sources/source.py b/src/importer/sources/source.py index 164a792..57b45d7 100644 --- a/src/importer/sources/source.py +++ b/src/importer/sources/source.py @@ -20,7 +20,7 @@ import sys from abc import abstractmethod from collections.abc import Iterable -from typing import Any, Generator, Optional, Collection +from typing import Any, Generator, Collection from src.game import Game from src.importer.sources.location import Location From 04d0e9e90e1646d0621429d592c876cefce1d0f0 Mon Sep 17 00:00:00 2001 From: GeoffreyCoulaud Date: Wed, 26 Jul 2023 04:43:10 +0200 Subject: [PATCH 3/4] Clarified location sub paths --- src/importer/sources/bottles_source.py | 6 +++--- src/importer/sources/flatpak_source.py | 6 +++--- src/importer/sources/heroic_source.py | 6 +++--- src/importer/sources/itch_source.py | 6 ++++-- src/importer/sources/legendary_source.py | 6 +++--- src/importer/sources/location.py | 25 ++++++++++++++---------- src/importer/sources/lutris_source.py | 4 ++-- src/importer/sources/steam_source.py | 6 +++--- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/importer/sources/bottles_source.py b/src/importer/sources/bottles_source.py index 42eebaa..5983c27 100644 --- a/src/importer/sources/bottles_source.py +++ b/src/importer/sources/bottles_source.py @@ -26,7 +26,7 @@ import yaml from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import SourceIterable, URLExecutableSource @@ -98,8 +98,8 @@ class BottlesSource(URLExecutableSource): shared.home / ".local" / "share" / "bottles", ), paths={ - "library.yml": (False, "library.yml"), - "data.yml": (False, "data.yml"), + "library.yml": LocationSubPath("library.yml"), + "data.yml": LocationSubPath("data.yml"), }, ) ) diff --git a/src/importer/sources/flatpak_source.py b/src/importer/sources/flatpak_source.py index f29028c..ec8e2a5 100644 --- a/src/importer/sources/flatpak_source.py +++ b/src/importer/sources/flatpak_source.py @@ -25,7 +25,7 @@ from gi.repository import GLib, Gtk from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import Source, SourceIterable @@ -132,8 +132,8 @@ class FlatpakSource(Source): shared.data_dir / "flatpak", ), paths={ - "applications": (True, "exports/share/applications"), - "icons": (True, "exports/share/icons"), + "applications": LocationSubPath("exports/share/applications", True), + "icons": LocationSubPath("exports/share/icons", True), }, ) ) diff --git a/src/importer/sources/heroic_source.py b/src/importer/sources/heroic_source.py index 1c28f14..2986aad 100644 --- a/src/importer/sources/heroic_source.py +++ b/src/importer/sources/heroic_source.py @@ -30,7 +30,7 @@ from functools import cached_property from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import ( SourceIterable, SourceIterationResult, @@ -374,8 +374,8 @@ class HeroicSource(URLExecutableSource): shared.appdata_dir / "heroic", ), paths={ - "config.json": (False, "config.json"), - "store_config.json": (False, Path("store") / "config.json"), + "config.json": LocationSubPath("config.json"), + "store_config.json": LocationSubPath("store/config.json"), }, ) ) diff --git a/src/importer/sources/itch_source.py b/src/importer/sources/itch_source.py index dc4d4f9..19839cd 100644 --- a/src/importer/sources/itch_source.py +++ b/src/importer/sources/itch_source.py @@ -25,7 +25,7 @@ from typing import NamedTuple from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import SourceIterable, URLExecutableSource from src.utils.sqlite import copy_db @@ -94,6 +94,8 @@ class ItchSource(URLExecutableSource): shared.home / ".config" / "itch", shared.appdata_dir / "itch", ), - paths={"butler.db": (False, "db/butler.db")}, + paths={ + "butler.db": LocationSubPath("db/butler.db"), + }, ) ) diff --git a/src/importer/sources/legendary_source.py b/src/importer/sources/legendary_source.py index 529bd8c..e802f51 100644 --- a/src/importer/sources/legendary_source.py +++ b/src/importer/sources/legendary_source.py @@ -25,7 +25,7 @@ from typing import NamedTuple from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import Source, SourceIterationResult, SourceIterable @@ -107,8 +107,8 @@ class LegendarySource(Source): shared.home / ".config" / "legendary", ), paths={ - "installed.json": (False, "installed.json"), - "metadata": (True, "metadata"), + "installed.json": LocationSubPath("installed.json"), + "metadata": LocationSubPath("metadata", True), }, ) ) diff --git a/src/importer/sources/location.py b/src/importer/sources/location.py index 8374a20..a884ba0 100644 --- a/src/importer/sources/location.py +++ b/src/importer/sources/location.py @@ -1,13 +1,18 @@ import logging from pathlib import Path -from typing import Callable, Mapping, Iterable +from typing import Mapping, Iterable, NamedTuple from os import PathLike from src import shared PathSegment = str | PathLike | Path PathSegments = Iterable[PathSegment] -Candidate = PathSegments | Callable[[], PathSegments] +Candidate = PathSegments + + +class LocationSubPath(NamedTuple): + segment: PathSegment + is_directory: bool = False class UnresolvableLocationError(Exception): @@ -26,14 +31,14 @@ class Location: schema_key: str candidates: Iterable[Candidate] - paths: Mapping[str, tuple[bool, PathSegments]] + paths: Mapping[str, LocationSubPath] root: Path = None def __init__( self, schema_key: str, candidates: Iterable[Candidate], - paths: Mapping[str, tuple[bool, PathSegments]], + paths: Mapping[str, LocationSubPath], ) -> None: super().__init__() self.schema_key = schema_key @@ -42,13 +47,13 @@ class Location: def check_candidate(self, candidate: Path) -> bool: """Check if a candidate root has the necessary files and directories""" - for type_is_dir, subpath in self.paths.values(): - subpath = Path(candidate) / Path(subpath) - if type_is_dir: - if not subpath.is_dir(): + for segment, is_directory in self.paths.values(): + path = Path(candidate) / segment + if is_directory: + if not path.is_dir(): return False else: - if not subpath.is_file(): + if not path.is_file(): return False return True @@ -81,4 +86,4 @@ class Location: def __getitem__(self, key: str): """Get the computed path from its key for the location""" self.resolve() - return self.root / self.paths[key][1] + return self.root / self.paths[key].segment diff --git a/src/importer/sources/lutris_source.py b/src/importer/sources/lutris_source.py index 5a26fc1..627f896 100644 --- a/src/importer/sources/lutris_source.py +++ b/src/importer/sources/lutris_source.py @@ -24,7 +24,7 @@ from typing import NamedTuple from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import SourceIterable, URLExecutableSource from src.utils.sqlite import copy_db @@ -119,7 +119,7 @@ class LutrisSource(URLExecutableSource): shared.home / ".cache" / "lutris", ), paths={ - "coverart": (True, "coverart"), + "coverart": LocationSubPath("coverart", True), }, ), ) diff --git a/src/importer/sources/steam_source.py b/src/importer/sources/steam_source.py index e4e9cfb..39e3c49 100644 --- a/src/importer/sources/steam_source.py +++ b/src/importer/sources/steam_source.py @@ -26,7 +26,7 @@ from typing import Iterable, NamedTuple from src import shared from src.game import Game -from src.importer.sources.location import Location +from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.source import SourceIterable, URLExecutableSource from src.utils.steam import SteamFileHelper, SteamInvalidManifestError @@ -129,8 +129,8 @@ class SteamSource(URLExecutableSource): shared.programfiles32_dir / "Steam", ), paths={ - "libraryfolders.vdf": (False, "steamapps/libraryfolders.vdf"), - "librarycache": (True, "appcache/librarycache"), + "libraryfolders.vdf": LocationSubPath("steamapps/libraryfolders.vdf"), + "librarycache": LocationSubPath("appcache/librarycache", True), }, ) ) From fa8a15addf7c000f8b368ff9fc154782ac12fd01 Mon Sep 17 00:00:00 2001 From: GeoffreyCoulaud Date: Wed, 26 Jul 2023 15:27:28 +0200 Subject: [PATCH 4/4] Moved invalid location message to location --- src/importer/sources/bottles_source.py | 1 + src/importer/sources/flatpak_source.py | 1 + src/importer/sources/heroic_source.py | 1 + src/importer/sources/itch_source.py | 1 + src/importer/sources/legendary_source.py | 1 + src/importer/sources/location.py | 11 +++++++++++ src/importer/sources/lutris_source.py | 2 ++ src/importer/sources/steam_source.py | 1 + src/preferences.py | 12 +----------- 9 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/importer/sources/bottles_source.py b/src/importer/sources/bottles_source.py index 5983c27..5b48078 100644 --- a/src/importer/sources/bottles_source.py +++ b/src/importer/sources/bottles_source.py @@ -101,5 +101,6 @@ class BottlesSource(URLExecutableSource): "library.yml": LocationSubPath("library.yml"), "data.yml": LocationSubPath("data.yml"), }, + invalid_subtitle=Location.DATA_INVALID_SUBTITLE, ) ) diff --git a/src/importer/sources/flatpak_source.py b/src/importer/sources/flatpak_source.py index ec8e2a5..b78815c 100644 --- a/src/importer/sources/flatpak_source.py +++ b/src/importer/sources/flatpak_source.py @@ -135,5 +135,6 @@ class FlatpakSource(Source): "applications": LocationSubPath("exports/share/applications", True), "icons": LocationSubPath("exports/share/icons", True), }, + invalid_subtitle=Location.DATA_INVALID_SUBTITLE, ) ) diff --git a/src/importer/sources/heroic_source.py b/src/importer/sources/heroic_source.py index 2986aad..605ba56 100644 --- a/src/importer/sources/heroic_source.py +++ b/src/importer/sources/heroic_source.py @@ -377,6 +377,7 @@ class HeroicSource(URLExecutableSource): "config.json": LocationSubPath("config.json"), "store_config.json": LocationSubPath("store/config.json"), }, + invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE, ) ) diff --git a/src/importer/sources/itch_source.py b/src/importer/sources/itch_source.py index 19839cd..8302e91 100644 --- a/src/importer/sources/itch_source.py +++ b/src/importer/sources/itch_source.py @@ -97,5 +97,6 @@ class ItchSource(URLExecutableSource): paths={ "butler.db": LocationSubPath("db/butler.db"), }, + invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE, ) ) diff --git a/src/importer/sources/legendary_source.py b/src/importer/sources/legendary_source.py index e802f51..48f0cac 100644 --- a/src/importer/sources/legendary_source.py +++ b/src/importer/sources/legendary_source.py @@ -110,5 +110,6 @@ class LegendarySource(Source): "installed.json": LocationSubPath("installed.json"), "metadata": LocationSubPath("metadata", True), }, + invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE, ) ) diff --git a/src/importer/sources/location.py b/src/importer/sources/location.py index a884ba0..55684b4 100644 --- a/src/importer/sources/location.py +++ b/src/importer/sources/location.py @@ -29,9 +29,18 @@ class Location: * When resolved, the schema is updated with the picked chosen """ + # The variable is the name of the source + CACHE_INVALID_SUBTITLE = _("Select the {} cache directory.") + # The variable is the name of the source + CONFIG_INVALID_SUBTITLE = _("Select the {} configuration directory.") + # The variable is the name of the source + DATA_INVALID_SUBTITLE = _("Select the {} data directory.") + schema_key: str candidates: Iterable[Candidate] paths: Mapping[str, LocationSubPath] + invalid_subtitle: str + root: Path = None def __init__( @@ -39,11 +48,13 @@ class Location: schema_key: str, candidates: Iterable[Candidate], paths: Mapping[str, LocationSubPath], + invalid_subtitle: str, ) -> None: super().__init__() self.schema_key = schema_key self.candidates = candidates self.paths = paths + self.invalid_subtitle = invalid_subtitle def check_candidate(self, candidate: Path) -> bool: """Check if a candidate root has the necessary files and directories""" diff --git a/src/importer/sources/lutris_source.py b/src/importer/sources/lutris_source.py index 627f896..9afc977 100644 --- a/src/importer/sources/lutris_source.py +++ b/src/importer/sources/lutris_source.py @@ -110,6 +110,7 @@ class LutrisSource(URLExecutableSource): paths={ "pga.db": (False, "pga.db"), }, + invalid_subtitle=Location.DATA_INVALID_SUBTITLE, ), Location( schema_key="lutris-cache-location", @@ -121,6 +122,7 @@ class LutrisSource(URLExecutableSource): paths={ "coverart": LocationSubPath("coverart", True), }, + invalid_subtitle=Location.CACHE_INVALID_SUBTITLE, ), ) diff --git a/src/importer/sources/steam_source.py b/src/importer/sources/steam_source.py index 39e3c49..f1c34ba 100644 --- a/src/importer/sources/steam_source.py +++ b/src/importer/sources/steam_source.py @@ -132,5 +132,6 @@ class SteamSource(URLExecutableSource): "libraryfolders.vdf": LocationSubPath("steamapps/libraryfolders.vdf"), "librarycache": LocationSubPath("appcache/librarycache", True), }, + invalid_subtitle=Location.DATA_INVALID_SUBTITLE, ) ) diff --git a/src/preferences.py b/src/preferences.py index 7300523..53bc098 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -351,20 +351,10 @@ class PreferencesWindow(Adw.PreferencesWindow): # Bad picked location, inform user else: title = _("Invalid Directory") - match location_name: - case "cache": - # The variable is the name of the source - subtitle_format = _("Select the {} cache directory.") - case "config": - # The variable is the name of the source - subtitle_format = _("Select the {} configuration directory.") - case "data": - # The variable is the name of the source - subtitle_format = _("Select the {} data directory.") dialog = create_dialog( self, title, - subtitle_format.format(source.name), + location.invalid_subtitle.format(source.name), "choose_folder", _("Set Location"), )