Merge pull request #158 from kra-mo/locations-improvements

Locations improvements
This commit is contained in:
kramo
2023-07-26 16:01:32 +02:00
committed by GitHub
10 changed files with 216 additions and 146 deletions

View File

@@ -20,12 +20,13 @@
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import NamedTuple
import yaml import yaml
from src import shared from src import shared
from src.game import Game 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.importer.sources.source import SourceIterable, URLExecutableSource
@@ -35,7 +36,7 @@ class BottlesSourceIterable(SourceIterable):
def __iter__(self): def __iter__(self):
"""Generator method producing games""" """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) library: dict = yaml.safe_load(data)
added_time = int(time()) added_time = int(time())
@@ -58,11 +59,11 @@ class BottlesSourceIterable(SourceIterable):
# as Cartridges can't access directories picked via Bottles' file picker portal # as Cartridges can't access directories picked via Bottles' file picker portal
bottles_location = Path( bottles_location = Path(
yaml.safe_load( 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"] )["custom_bottles_path"]
) )
except (FileNotFoundError, KeyError): except (FileNotFoundError, KeyError):
bottles_location = self.source.data_location.root / "bottles" bottles_location = self.source.locations.data.root / "bottles"
bottle_path = entry["bottle"]["path"] bottle_path = entry["bottle"]["path"]
@@ -76,6 +77,10 @@ class BottlesSourceIterable(SourceIterable):
yield (game, additional_data) yield (game, additional_data)
class BottlesLocations(NamedTuple):
data: Location
class BottlesSource(URLExecutableSource): class BottlesSource(URLExecutableSource):
"""Generic Bottles source""" """Generic Bottles source"""
@@ -84,15 +89,18 @@ class BottlesSource(URLExecutableSource):
url_format = 'bottles:run/"{bottle_name}"/"{game_name}"' url_format = 'bottles:run/"{bottle_name}"/"{game_name}"'
available_on = {"linux"} available_on = {"linux"}
data_location = Location( locations = BottlesLocations(
schema_key="bottles-location", Location(
candidates=( schema_key="bottles-location",
shared.flatpak_dir / "com.usebottles.bottles" / "data" / "bottles", candidates=(
shared.data_dir / "bottles/", shared.flatpak_dir / "com.usebottles.bottles" / "data" / "bottles",
shared.home / ".local" / "share" / "bottles", shared.data_dir / "bottles/",
), shared.home / ".local" / "share" / "bottles",
paths={ ),
"library.yml": (False, "library.yml"), paths={
"data.yml": (False, "data.yml"), "library.yml": LocationSubPath("library.yml"),
}, "data.yml": LocationSubPath("data.yml"),
},
invalid_subtitle=Location.DATA_INVALID_SUBTITLE,
)
) )

View File

@@ -19,12 +19,13 @@
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import NamedTuple
from gi.repository import GLib, Gtk from gi.repository import GLib, Gtk
from src import shared from src import shared
from src.game import Game 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 from src.importer.sources.source import Source, SourceIterable
@@ -37,7 +38,7 @@ class FlatpakSourceIterable(SourceIterable):
added_time = int(time()) added_time = int(time())
icon_theme = Gtk.IconTheme.new() 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 = ( blacklist = (
{"hu.kramo.Cartridges", "hu.kramo.Cartridges.Devel"} {"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": if entry.suffix != ".desktop":
continue continue
@@ -111,6 +112,10 @@ class FlatpakSourceIterable(SourceIterable):
yield (game, additional_data) yield (game, additional_data)
class FlatpakLocations(NamedTuple):
data: Location
class FlatpakSource(Source): class FlatpakSource(Source):
"""Generic Flatpak source""" """Generic Flatpak source"""
@@ -119,14 +124,17 @@ class FlatpakSource(Source):
executable_format = "flatpak run {flatpak_id}" executable_format = "flatpak run {flatpak_id}"
available_on = {"linux"} available_on = {"linux"}
data_location = Location( locations = FlatpakLocations(
schema_key="flatpak-location", Location(
candidates=( schema_key="flatpak-location",
"/var/lib/flatpak/", candidates=(
shared.data_dir / "flatpak", "/var/lib/flatpak/",
), shared.data_dir / "flatpak",
paths={ ),
"applications": (True, "exports/share/applications"), paths={
"icons": (True, "exports/share/icons"), "applications": LocationSubPath("exports/share/applications", True),
}, "icons": LocationSubPath("exports/share/icons", True),
},
invalid_subtitle=Location.DATA_INVALID_SUBTITLE,
)
) )

View File

@@ -25,12 +25,12 @@ from hashlib import sha256
from json import JSONDecodeError from json import JSONDecodeError
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import Iterable, Optional, TypedDict from typing import Iterable, NamedTuple, Optional, TypedDict
from functools import cached_property from functools import cached_property
from src import shared from src import shared
from src.game import Game 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 ( from src.importer.sources.source import (
SourceIterable, SourceIterable,
SourceIterationResult, SourceIterationResult,
@@ -87,7 +87,7 @@ class SubSourceIterable(Iterable):
@cached_property @cached_property
def library_path(self) -> Path: 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) logging.debug("Using Heroic %s library.json path %s", self.name, path)
return path return path
@@ -116,7 +116,7 @@ class SubSourceIterable(Iterable):
# Filenames are derived from the URL that heroic used to get the file # Filenames are derived from the URL that heroic used to get the file
uri: str = entry["art_square"] + self.image_uri_params uri: str = entry["art_square"] + self.image_uri_params
digest = sha256(uri.encode()).hexdigest() 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} additional_data = {"local_image_path": image_path}
return (game, additional_data) return (game, additional_data)
@@ -159,7 +159,7 @@ class StoreSubSourceIterable(SubSourceIterable):
@cached_property @cached_property
def installed_path(self) -> Path: 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) logging.debug("Using Heroic %s installed.json path %s", self.name, path)
return path return path
@@ -226,7 +226,7 @@ class LegendaryIterable(StoreSubSourceIterable):
and remove this property override. and remove this property override.
""" """
heroic_config_path = self.source.config_location.root heroic_config_path = self.source.locations.config.root
# Heroic >= 2.9 # Heroic >= 2.9
if (path := heroic_config_path / "legendaryConfig").is_dir(): if (path := heroic_config_path / "legendaryConfig").is_dir():
logging.debug("Using Heroic >= 2.9 legendary file") logging.debug("Using Heroic >= 2.9 legendary file")
@@ -308,7 +308,7 @@ class HeroicSourceIterable(SourceIterable):
""" """
try: 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 = { self.hidden_app_names = {
app_name app_name
for game in store["games"]["hidden"] for game in store["games"]["hidden"]
@@ -349,6 +349,10 @@ class HeroicSourceIterable(SourceIterable):
continue continue
class HeroicLocations(NamedTuple):
config: Location
class HeroicSource(URLExecutableSource): class HeroicSource(URLExecutableSource):
"""Generic Heroic Games Launcher source""" """Generic Heroic Games Launcher source"""
@@ -357,18 +361,24 @@ class HeroicSource(URLExecutableSource):
url_format = "heroic://launch/{app_name}" url_format = "heroic://launch/{app_name}"
available_on = {"linux", "win32"} available_on = {"linux", "win32"}
config_location = Location( locations = HeroicLocations(
schema_key="heroic-location", Location(
candidates=( schema_key="heroic-location",
shared.config_dir / "heroic", candidates=(
shared.home / ".config" / "heroic", shared.config_dir / "heroic",
shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config" / "heroic", shared.home / ".config" / "heroic",
shared.appdata_dir / "heroic", shared.flatpak_dir
), / "com.heroicgameslauncher.hgl"
paths={ / "config"
"config.json": (False, "config.json"), / "heroic",
"store_config.json": (False, Path("store") / "config.json"), shared.appdata_dir / "heroic",
}, ),
paths={
"config.json": LocationSubPath("config.json"),
"store_config.json": LocationSubPath("store/config.json"),
},
invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE,
)
) )
@property @property

View File

@@ -21,10 +21,11 @@
from shutil import rmtree from shutil import rmtree
from sqlite3 import connect from sqlite3 import connect
from time import time from time import time
from typing import NamedTuple
from src import shared from src import shared
from src.game import Game 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.importer.sources.source import SourceIterable, URLExecutableSource
from src.utils.sqlite import copy_db from src.utils.sqlite import copy_db
@@ -51,7 +52,7 @@ class ItchSourceIterable(SourceIterable):
caves.game_id = games.id 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) connection = connect(db_path)
cursor = connection.execute(db_request) cursor = connection.execute(db_request)
@@ -74,19 +75,28 @@ class ItchSourceIterable(SourceIterable):
rmtree(str(db_path.parent)) rmtree(str(db_path.parent))
class ItchLocations(NamedTuple):
config: Location
class ItchSource(URLExecutableSource): class ItchSource(URLExecutableSource):
name = _("itch") name = _("itch")
iterable_class = ItchSourceIterable iterable_class = ItchSourceIterable
url_format = "itch://caves/{cave_id}/launch" url_format = "itch://caves/{cave_id}/launch"
available_on = {"linux", "win32"} available_on = {"linux", "win32"}
config_location = Location( locations = ItchLocations(
schema_key="itch-location", Location(
candidates=( schema_key="itch-location",
shared.flatpak_dir / "io.itch.itch" / "config" / "itch", candidates=(
shared.config_dir / "itch", shared.flatpak_dir / "io.itch.itch" / "config" / "itch",
shared.home / ".config" / "itch", shared.config_dir / "itch",
shared.appdata_dir / "itch", shared.home / ".config" / "itch",
), shared.appdata_dir / "itch",
paths={"butler.db": (False, "db/butler.db")}, ),
paths={
"butler.db": LocationSubPath("db/butler.db"),
},
invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE,
)
) )

View File

@@ -21,10 +21,11 @@ import json
import logging import logging
from json import JSONDecodeError from json import JSONDecodeError
from time import time from time import time
from typing import NamedTuple
from src import shared from src import shared
from src.game import Game 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 from src.importer.sources.source import Source, SourceIterationResult, SourceIterable
@@ -50,7 +51,7 @@ class LegendarySourceIterable(SourceIterable):
data = {} data = {}
# Get additional metadata from file (optional) # 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: try:
metadata = json.load(metadata_file.open()) metadata = json.load(metadata_file.open())
values["developer"] = metadata["metadata"]["developer"] values["developer"] = metadata["metadata"]["developer"]
@@ -66,7 +67,7 @@ class LegendarySourceIterable(SourceIterable):
def __iter__(self): def __iter__(self):
# Open library # Open library
file = self.source.config_location["installed.json"] file = self.source.locations.config["installed.json"]
try: try:
library: dict = json.load(file.open()) library: dict = json.load(file.open())
except (JSONDecodeError, OSError): except (JSONDecodeError, OSError):
@@ -88,20 +89,27 @@ class LegendarySourceIterable(SourceIterable):
yield result yield result
class LegendaryLocations(NamedTuple):
config: Location
class LegendarySource(Source): class LegendarySource(Source):
name = _("Legendary") name = _("Legendary")
executable_format = "legendary launch {app_name}" executable_format = "legendary launch {app_name}"
available_on = {"linux"} available_on = {"linux"}
iterable_class = LegendarySourceIterable iterable_class = LegendarySourceIterable
config_location: Location = Location(
schema_key="legendary-location", locations = LegendaryLocations(
candidates=( Location(
shared.config_dir / "legendary", schema_key="legendary-location",
shared.home / ".config" / "legendary", candidates=(
), shared.config_dir / "legendary",
paths={ shared.home / ".config" / "legendary",
"installed.json": (False, "installed.json"), ),
"metadata": (True, "metadata"), paths={
}, "installed.json": LocationSubPath("installed.json"),
"metadata": LocationSubPath("metadata", True),
},
invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE,
)
) )

View File

@@ -1,13 +1,18 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Callable, Mapping, Iterable from typing import Mapping, Iterable, NamedTuple
from os import PathLike from os import PathLike
from src import shared from src import shared
PathSegment = str | PathLike | Path PathSegment = str | PathLike | Path
PathSegments = Iterable[PathSegment] PathSegments = Iterable[PathSegment]
Candidate = PathSegments | Callable[[], PathSegments] Candidate = PathSegments
class LocationSubPath(NamedTuple):
segment: PathSegment
is_directory: bool = False
class UnresolvableLocationError(Exception): class UnresolvableLocationError(Exception):
@@ -24,31 +29,42 @@ class Location:
* When resolved, the schema is updated with the picked chosen * 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 schema_key: str
candidates: Iterable[Candidate] candidates: Iterable[Candidate]
paths: Mapping[str, tuple[bool, PathSegments]] paths: Mapping[str, LocationSubPath]
invalid_subtitle: str
root: Path = None root: Path = None
def __init__( def __init__(
self, self,
schema_key: str, schema_key: str,
candidates: Iterable[Candidate], candidates: Iterable[Candidate],
paths: Mapping[str, tuple[bool, PathSegments]], paths: Mapping[str, LocationSubPath],
invalid_subtitle: str,
) -> None: ) -> None:
super().__init__() super().__init__()
self.schema_key = schema_key self.schema_key = schema_key
self.candidates = candidates self.candidates = candidates
self.paths = paths self.paths = paths
self.invalid_subtitle = invalid_subtitle
def check_candidate(self, candidate: Path) -> bool: def check_candidate(self, candidate: Path) -> bool:
"""Check if a candidate root has the necessary files and directories""" """Check if a candidate root has the necessary files and directories"""
for type_is_dir, subpath in self.paths.values(): for segment, is_directory in self.paths.values():
subpath = Path(candidate) / Path(subpath) path = Path(candidate) / segment
if type_is_dir: if is_directory:
if not subpath.is_dir(): if not path.is_dir():
return False return False
else: else:
if not subpath.is_file(): if not path.is_file():
return False return False
return True return True
@@ -81,4 +97,4 @@ class Location:
def __getitem__(self, key: str): def __getitem__(self, key: str):
"""Get the computed path from its key for the location""" """Get the computed path from its key for the location"""
self.resolve() self.resolve()
return self.root / self.paths[key][1] return self.root / self.paths[key].segment

View File

@@ -20,10 +20,11 @@
from shutil import rmtree from shutil import rmtree
from sqlite3 import connect from sqlite3 import connect
from time import time from time import time
from typing import NamedTuple
from src import shared from src import shared
from src.game import Game 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.importer.sources.source import SourceIterable, URLExecutableSource
from src.utils.sqlite import copy_db from src.utils.sqlite import copy_db
@@ -51,7 +52,7 @@ class LutrisSourceIterable(SourceIterable):
"import_steam": shared.schema.get_boolean("lutris-import-steam"), "import_steam": shared.schema.get_boolean("lutris-import-steam"),
"import_flatpak": shared.schema.get_boolean("lutris-import-flatpak"), "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) connection = connect(db_path)
cursor = connection.execute(request, params) cursor = connection.execute(request, params)
@@ -73,7 +74,7 @@ class LutrisSourceIterable(SourceIterable):
game = Game(values) game = Game(values)
# Get official image path # 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} additional_data = {"local_image_path": image_path}
# Produce game # Produce game
@@ -83,6 +84,11 @@ class LutrisSourceIterable(SourceIterable):
rmtree(str(db_path.parent)) rmtree(str(db_path.parent))
class LutrisLocations(NamedTuple):
config: Location
cache: Location
class LutrisSource(URLExecutableSource): class LutrisSource(URLExecutableSource):
"""Generic Lutris source""" """Generic Lutris source"""
@@ -91,30 +97,33 @@ class LutrisSource(URLExecutableSource):
url_format = "lutris:rungameid/{game_id}" url_format = "lutris:rungameid/{game_id}"
available_on = {"linux"} 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( locations = LutrisLocations(
schema_key="lutris-location", Location(
candidates=( schema_key="lutris-location",
shared.flatpak_dir / "net.lutris.Lutris" / "data" / "lutris", candidates=(
shared.data_dir / "lutris", shared.flatpak_dir / "net.lutris.Lutris" / "data" / "lutris",
shared.home / ".local" / "share" / "lutris", shared.data_dir / "lutris",
shared.home / ".local" / "share" / "lutris",
),
paths={
"pga.db": (False, "pga.db"),
},
invalid_subtitle=Location.DATA_INVALID_SUBTITLE,
), ),
paths={ Location(
"pga.db": (False, "pga.db"), schema_key="lutris-cache-location",
}, candidates=(
) shared.flatpak_dir / "net.lutris.Lutris" / "cache" / "lutris",
shared.cache_dir / "lutris",
cache_location = Location( shared.home / ".cache" / "lutris",
schema_key="lutris-cache-location", ),
candidates=( paths={
shared.flatpak_dir / "net.lutris.Lutris" / "cache" / "lutris", "coverart": LocationSubPath("coverart", True),
shared.cache_dir / "lutris", },
shared.home / ".cache" / "lutris", invalid_subtitle=Location.CACHE_INVALID_SUBTITLE,
), ),
paths={
"coverart": (True, "coverart"),
},
) )
@property @property

View File

@@ -19,8 +19,8 @@
import sys import sys
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Iterable, Iterator from collections.abc import Iterable
from typing import Any, Generator, Optional from typing import Any, Generator, Collection
from src.game import Game from src.game import Game
from src.importer.sources.location import Location from src.importer.sources.location import Location
@@ -54,10 +54,8 @@ class Source(Iterable):
name: str name: str
variant: str = None variant: str = None
available_on: set[str] = set() available_on: set[str] = set()
data_location: Optional[Location] = None
cache_location: Optional[Location] = None
config_location: Optional[Location] = None
iterable_class: type[SourceIterable] iterable_class: type[SourceIterable]
locations: Collection[Location]
@property @property
def full_name(self) -> str: def full_name(self) -> str:

View File

@@ -22,11 +22,11 @@ import logging
import re import re
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import Iterable from typing import Iterable, NamedTuple
from src import shared from src import shared
from src.game import Game 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.importer.sources.source import SourceIterable, URLExecutableSource
from src.utils.steam import SteamFileHelper, SteamInvalidManifestError from src.utils.steam import SteamFileHelper, SteamInvalidManifestError
@@ -36,7 +36,7 @@ class SteamSourceIterable(SourceIterable):
def get_manifest_dirs(self) -> Iterable[Path]: def get_manifest_dirs(self) -> Iterable[Path]:
"""Get dirs that contain steam app manifests""" """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: with open(libraryfolders_path, "r", encoding="utf-8") as file:
contents = file.read() contents = file.read()
return [ return [
@@ -100,7 +100,7 @@ class SteamSourceIterable(SourceIterable):
# Add official cover image # Add official cover image
image_path = ( image_path = (
self.source.data_location["librarycache"] self.source.locations.data["librarycache"]
/ f"{appid}_library_600x900.jpg" / f"{appid}_library_600x900.jpg"
) )
additional_data = {"local_image_path": image_path, "steam_appid": appid} additional_data = {"local_image_path": image_path, "steam_appid": appid}
@@ -109,22 +109,29 @@ class SteamSourceIterable(SourceIterable):
yield (game, additional_data) yield (game, additional_data)
class SteamLocations(NamedTuple):
data: Location
class SteamSource(URLExecutableSource): class SteamSource(URLExecutableSource):
name = _("Steam") name = _("Steam")
available_on = {"linux", "win32"} available_on = {"linux", "win32"}
iterable_class = SteamSourceIterable iterable_class = SteamSourceIterable
url_format = "steam://rungameid/{game_id}" url_format = "steam://rungameid/{game_id}"
data_location = Location( locations = SteamLocations(
schema_key="steam-location", Location(
candidates=( schema_key="steam-location",
shared.home / ".steam" / "steam", candidates=(
shared.data_dir / "Steam", shared.home / ".steam" / "steam",
shared.flatpak_dir / "com.valvesoftware.Steam" / "data" / "Steam", shared.data_dir / "Steam",
shared.programfiles32_dir / "Steam", shared.flatpak_dir / "com.valvesoftware.Steam" / "data" / "Steam",
), shared.programfiles32_dir / "Steam",
paths={ ),
"libraryfolders.vdf": (False, "steamapps/libraryfolders.vdf"), paths={
"librarycache": (True, "appcache/librarycache"), "libraryfolders.vdf": LocationSubPath("steamapps/libraryfolders.vdf"),
}, "librarycache": LocationSubPath("appcache/librarycache", True),
},
invalid_subtitle=Location.DATA_INVALID_SUBTITLE,
)
) )

View File

@@ -262,19 +262,19 @@ class PreferencesWindow(Adw.PreferencesWindow):
subtitle = re.sub("/run/user/\\d*/doc/.*/", "", str(path)) subtitle = re.sub("/run/user/\\d*/doc/.*/", "", str(path))
action_row.set_subtitle(subtitle) 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""" """Resolve locations and add a warning if location cannot be found"""
def clear_warning_selection(_widget, label): def clear_warning_selection(_widget, label):
label.select_region(-1, -1) 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) action_row = getattr(self, f"{source.id}_{location_name}_action_row", None)
if not action_row: if not action_row:
continue continue
try: try:
getattr(source, f"{location_name}_location", None).resolve() location.resolve()
except UnresolvableLocationError: except UnresolvableLocationError:
popover = Gtk.Popover( popover = Gtk.Popover(
@@ -325,10 +325,14 @@ class PreferencesWindow(Adw.PreferencesWindow):
return return
# Good picked location # Good picked location
location = getattr(source, f"{location_name}_location") location = getattr(source.locations, location_name)
if location.check_candidate(path): if location.check_candidate(path):
# Set the schema # 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" key = f"{source.id}{infix}-location"
value = str(path) value = str(path)
shared.schema.set_string(key, value) shared.schema.set_string(key, value)
@@ -347,20 +351,10 @@ class PreferencesWindow(Adw.PreferencesWindow):
# Bad picked location, inform user # Bad picked location, inform user
else: else:
title = _("Invalid Directory") 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( dialog = create_dialog(
self, self,
title, title,
subtitle_format.format(source.name), location.invalid_subtitle.format(source.name),
"choose_folder", "choose_folder",
_("Set Location"), _("Set Location"),
) )
@@ -381,10 +375,12 @@ class PreferencesWindow(Adw.PreferencesWindow):
) )
# Connect dir picker buttons # Connect dir picker buttons
for location in ("data", "config", "cache"): for location_name in source.locations._asdict():
button = getattr(self, f"{source.id}_{location}_file_chooser_button", None) button = getattr(
self, f"{source.id}_{location_name}_file_chooser_button", None
)
if button is not 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 # Set the source row subtitles
self.resolve_locations(source) self.resolve_locations(source)