diff --git a/lutris/gui/application.py b/lutris/gui/application.py index aca738d72..904fd60d0 100644 --- a/lutris/gui/application.py +++ b/lutris/gui/application.py @@ -54,7 +54,7 @@ from lutris.util.http import HTTPError, Request from lutris.util.log import logger from lutris.util.steam.appmanifest import AppManifest, get_appmanifests from lutris.util.steam.config import get_steamapps_dirs -from lutris.services import get_enabled_services +from lutris.services import get_service from lutris.database.services import ServiceGameCollection from .lutriswindow import LutrisWindow @@ -663,7 +663,7 @@ class Application(Gtk.Application): if service: service_game = ServiceGameCollection.get_game(service, appid) if service_game: - service = get_enabled_services()[service]() + service = get_service(service) service.install(service_game) return 0 @@ -747,8 +747,8 @@ class Application(Gtk.Application): @watch_errors(error_result=True) def on_game_install(self, game): """Request installation of a game""" - if game.service and game.service != "lutris": - service = get_enabled_services()[game.service]() + service = get_service(game.service) if game.service else None + if service and service.type != "lutris": db_game = ServiceGameCollection.get_game(service.id, game.appid) if not db_game: logger.error("Can't find %s for %s", game.name, service.name) @@ -780,7 +780,7 @@ class Application(Gtk.Application): @watch_errors(error_result=True) def on_game_install_update(self, game): - service = get_enabled_services()[game.service]() + service = get_service(game.service) db_game = games_db.get_game_by_field(game.id, "id") installers = service.get_update_installers(db_game) if installers: @@ -791,7 +791,7 @@ class Application(Gtk.Application): @watch_errors(error_result=True) def on_game_install_dlc(self, game): - service = get_enabled_services()[game.service]() + service = get_service(game.service) db_game = games_db.get_game_by_field(game.id, "id") installers = service.get_dlc_installers_runner(db_game, db_game["runner"]) if installers: diff --git a/lutris/gui/config/services_box.py b/lutris/gui/config/services_box.py index 74e9bf987..ac5287e05 100644 --- a/lutris/gui/config/services_box.py +++ b/lutris/gui/config/services_box.py @@ -45,7 +45,7 @@ class ServicesBox(BaseConfigBox): ) service = SERVICES[service_key] - icon = ScaledImage.get_runtime_icon_image(service.icon, service.id, + icon = ScaledImage.get_runtime_icon_image(service.icon, service.type, scale_factor=self.get_scale_factor(), visible=True) box.pack_start(icon, False, False, 0) diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index bac576ecb..ddc2deeaf 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -448,16 +448,16 @@ class LutrisWindow(Gtk.ApplicationWindow, return True return self.filters["text"] in game["name"].lower() - def set_service(self, service_name): - if self.service and self.service.id == service_name: + def set_service(self, service_id): + if self.service and self.service.id == service_id: return self.service - if not service_name: + if not service_id: self.service = None return try: - self.service = services.SERVICES[service_name]() + self.service = services.get_service(service_id) except KeyError: - logger.error("Non existent service '%s'", service_name) + logger.error("Non existent service '%s'", service_id) self.service = None return self.service @@ -469,10 +469,10 @@ class LutrisWindow(Gtk.ApplicationWindow, service_game[field] = lutris_game[field] return service_game - def get_service_games(self, service_name): - """Switch the current service to service_name and return games if available""" - service_games = ServiceGameCollection.get_for_service(service_name) - if service_name == "lutris": + def get_service_games(self, service_id): + """Switch the current service to service_id and return games if available""" + service_games = ServiceGameCollection.get_for_service(service_id) + if service_id == "lutris": lutris_games = {g["slug"]: g for g in games_db.get_games()} else: lutris_games = {g["service_id"]: g for g in games_db.get_games(filters={"service": self.service.id})} @@ -485,12 +485,12 @@ class LutrisWindow(Gtk.ApplicationWindow, ] def get_games_from_filters(self): - service_name = self.filters.get("service") - if service_name in services.SERVICES: + service_id = self.filters.get("service") + if service_id and services.service_type_for_id(service_id) in services.SERVICES: if self.service.online and not self.service.is_authenticated(): self.show_label(_("Connect your %s account to access your games") % self.service.name) return [] - return self.get_service_games(service_name) + return self.get_service_games(service_id) if self.filters.get("dynamic_category") in self.dynamic_categories_game_factories: return self.dynamic_categories_game_factories[self.filters["dynamic_category"]]() if self.filters.get("category") and self.filters["category"] != "all": @@ -706,7 +706,7 @@ class LutrisWindow(Gtk.ApplicationWindow, def load_icon_type(self): """Return the icon style depending on the type of view.""" setting_key = "icon_type_%sview" % self.current_view_type - if self.service and self.service.id != "lutris": + if self.service and self.service.type != "lutris": setting_key += "_%s" % self.service.id self.icon_type = settings.read_setting(setting_key) return self.icon_type @@ -715,7 +715,7 @@ class LutrisWindow(Gtk.ApplicationWindow, """Save icon type to settings""" self.icon_type = icon_type setting_key = "icon_type_%sview" % self.current_view_type - if self.service and self.service.id != "lutris": + if self.service and self.service.type != "lutris": setting_key += "_%s" % self.service.id settings.write_setting(setting_key, self.icon_type) self.redraw_view() @@ -967,8 +967,8 @@ class LutrisWindow(Gtk.ApplicationWindow, row_type = "category" self.filters[row_type] = row_id - service_name = self.filters.get("service") - self.set_service(service_name) + service_id = self.filters.get("service") + self.set_service(service_id) self._bind_zoom_adjustment() self.redraw_view() @@ -1074,7 +1074,7 @@ class LutrisWindow(Gtk.ApplicationWindow, if self.service: logger.debug("Looking up %s game %s", self.service.id, game_id) db_game = games_db.get_game_for_service(self.service.id, game_id) - if self.service.id == "lutris": + if self.service.type == "lutris": if not db_game or not db_game["installed"]: self.service.install(game_id) return diff --git a/lutris/gui/views/store_item.py b/lutris/gui/views/store_item.py index a3adcf649..9b573c784 100644 --- a/lutris/gui/views/store_item.py +++ b/lutris/gui/views/store_item.py @@ -4,7 +4,7 @@ import time from lutris.database import games from lutris.database.games import get_service_games from lutris.runners import get_runner_human_name -from lutris.services import SERVICES +from lutris.services import get_service from lutris.util.log import logger from lutris.util.strings import get_formatted_playtime, gtk_safe @@ -104,11 +104,15 @@ class StoreItem: """Platform""" _platform = self._get_game_attribute("platform") - if not _platform and self.service in SERVICES: - service = SERVICES[self.service]() - _platforms = service.get_game_platforms(self._game_data) - if _platforms: - _platform = ", ".join(_platforms) + if not _platform: + try: + service = get_service(self.service) + except KeyError: + pass + else: + _platforms = service.get_game_platforms(self._game_data) + if _platforms: + _platform = ", ".join(_platforms) return gtk_safe(_platform) diff --git a/lutris/gui/widgets/game_bar.py b/lutris/gui/widgets/game_bar.py index 3f83d128d..e0a7eb8c1 100644 --- a/lutris/gui/widgets/game_bar.py +++ b/lutris/gui/widgets/game_bar.py @@ -39,7 +39,7 @@ class GameBar(Gtk.Box): self.service = None if db_game.get("service"): try: - self.service = services.SERVICES[db_game["service"]]() + self.service = services.get_service(db_game["service"]) except KeyError: pass diff --git a/lutris/gui/widgets/sidebar.py b/lutris/gui/widgets/sidebar.py index ec97e2851..a019f0f71 100644 --- a/lutris/gui/widgets/sidebar.py +++ b/lutris/gui/widgets/sidebar.py @@ -138,7 +138,7 @@ class ServiceSidebarRow(SidebarRow): @property def sort_key(self): - return SERVICE_INDICES[self.id] + return SERVICE_INDICES[services.service_type_for_id(self.id)] def get_actions(self): """Return the definition of buttons to be added to the row""" @@ -527,12 +527,11 @@ class LutrisSidebar(Gtk.ListBox): self.installed_runners = [runner.name for runner in runners.get_installed()] self.active_platforms = games_db.get_used_platforms() - for service_name, service_class in self.active_services.items(): - if service_name not in self.service_rows: - service = service_class() + for service_id, service in self.active_services.items(): + if service_id not in self.service_rows: row_class = OnlineServiceSidebarRow if service.online else ServiceSidebarRow service_row = row_class(service) - self.service_rows[service_name] = service_row + self.service_rows[service_id] = service_row insert_row(service_row) for runner_name in self.installed_runners: diff --git a/lutris/installer/installer.py b/lutris/installer/installer.py index 38ba5ad02..84cfa849a 100644 --- a/lutris/installer/installer.py +++ b/lutris/installer/installer.py @@ -11,7 +11,7 @@ from lutris.installer.errors import ScriptingError from lutris.installer.installer_file import InstallerFile from lutris.installer.legacy import get_game_launcher from lutris.runners import import_runner -from lutris.services import SERVICES +from lutris.services import SERVICES, get_service from lutris.util.game_finder import find_linux_game_executable, find_windows_game_executable from lutris.util.gog import convert_gog_config_to_lutris, get_gog_config_from_path, get_gog_game_path from lutris.util.log import logger @@ -52,12 +52,12 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes if initial: return initial if "steam" in self.runner and "steam" in SERVICES: - return SERVICES["steam"]() + return get_service("steam") version = self.version.lower() if "humble" in version and "humblebundle" in SERVICES: - return SERVICES["humblebundle"]() + return get_service("humblebundle") if "gog" in version and "gog" in SERVICES: - return SERVICES["gog"]() + return get_service("gog") if "itch.io" in version and "itchio" in SERVICES: return SERVICES["itchio"]() @@ -69,12 +69,12 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes if not self.service: return service_id = None - if self.service.id == "steam": + if self.service.type == "steam": service_id = installer.get("steamid") or installer.get("service_id") game_config = self.script.get("game", {}) - if self.service.id == "gog": + if self.service.type == "gog": service_id = game_config.get("gogid") or installer.get("gogid") or installer.get("service_id") - if self.service.id == "humblebundle": + if self.service.type == "humblebundle": service_id = game_config.get("humbleid") or installer.get("humblestoreid") or installer.get("service_id") if self.service.id == "itchio": service_id = game_config.get("itchid") or installer.get("itchid") or installer.get("service_id") diff --git a/lutris/services/__init__.py b/lutris/services/__init__.py index 0b030cf52..596697980 100644 --- a/lutris/services/__init__.py +++ b/lutris/services/__init__.py @@ -26,7 +26,8 @@ DEFAULT_SERVICES = ["lutris", "gog", "egs", "ea_app", "ubisoft", "steam"] def get_services(): - """Return a mapping of available services""" + """Return a mapping of available service classes by their respective + service type names""" _services = { "lutris": LutrisService, "gog": GOGService, @@ -64,8 +65,21 @@ if os.environ.get("LUTRIS_ENABLE_ALL_SERVICES"): SERVICES.update(WIP_SERVICES) +def service_type_for_id(service_id): + """Derive the service type name from a service ID by dropping everything + following the first tilde character""" + return service_id.split("~", 1)[0] + + +def get_service(service_id): + """Return a new service instance object for the given service ID + + Raises `KeyError` if no matching service """ + return SERVICES[service_type_for_id(service_id)](id=service_id) + + def get_enabled_services(): return { - key: _class for key, _class in SERVICES.items() - if settings.read_setting(key, section="services").lower() == "true" + _type: _class(id=_type) for _type, _class in SERVICES.items() + if settings.read_setting(_type, section="services").lower() == "true" } diff --git a/lutris/services/amazon.py b/lutris/services/amazon.py index 69294f721..2fe6a403f 100644 --- a/lutris/services/amazon.py +++ b/lutris/services/amazon.py @@ -61,7 +61,7 @@ class AmazonGame(ServiceGame): class AmazonService(OnlineService): """Service class for Amazon""" - id = "amazon" + type = "amazon" name = _("Amazon Prime Gaming") icon = "amazon" has_extras = False diff --git a/lutris/services/base.py b/lutris/services/base.py index 5bb62c49d..2363b06fe 100644 --- a/lutris/services/base.py +++ b/lutris/services/base.py @@ -73,7 +73,8 @@ class LutrisCoverartMedium(LutrisCoverart): class BaseService(GObject.Object): """Base class for local services""" - id = NotImplemented + type = NotImplemented # String identifier for this kind of service + id: str # Identifier of a single account created at this service _matcher = None has_extras = False name = NotImplemented @@ -95,6 +96,10 @@ class BaseService(GObject.Object): "service-logout": (GObject.SIGNAL_RUN_FIRST, None, ()), } + def __init__(self, id): + super().__init__() + self.id = id + @property def matcher(self): if self._matcher: diff --git a/lutris/services/battlenet.py b/lutris/services/battlenet.py index 1be5032e7..a2910cf75 100644 --- a/lutris/services/battlenet.py +++ b/lutris/services/battlenet.py @@ -87,7 +87,7 @@ class BattleNetGame(ServiceGame): class BattleNetService(BaseService): """Service class for Battle.net""" - id = "battlenet" + type = "battlenet" name = _("Battle.net") icon = "battlenet" runner = "wine" diff --git a/lutris/services/dolphin.py b/lutris/services/dolphin.py index eee63f220..a3e125bf2 100644 --- a/lutris/services/dolphin.py +++ b/lutris/services/dolphin.py @@ -24,7 +24,7 @@ class DolphinBanner(ServiceMedia): class DolphinService(BaseService): - id = "dolphin" + type = "dolphin" icon = "dolphin" name = _("Dolphin") local = True diff --git a/lutris/services/ea_app.py b/lutris/services/ea_app.py index 433c2c1d6..0541dbedb 100644 --- a/lutris/services/ea_app.py +++ b/lutris/services/ea_app.py @@ -131,7 +131,7 @@ class LegacyRenegotiationHTTPAdapter(requests.adapters.HTTPAdapter): class EAAppService(OnlineService): """Service class for EA App""" - id = "ea_app" + type = "ea_app" name = _("EA App") icon = "ea_app" client_installer = "ea-app" @@ -159,8 +159,8 @@ class EAAppService(OnlineService): ) % origin_redirect_uri login_user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0 QtWebEngine/5.8.0" - def __init__(self): - super().__init__() + def __init__(self, id): + super().__init__(id=id) self.session = requests.session() self.session.mount("https://", LegacyRenegotiationHTTPAdapter()) diff --git a/lutris/services/egs.py b/lutris/services/egs.py index 6659834ee..65944b39b 100644 --- a/lutris/services/egs.py +++ b/lutris/services/egs.py @@ -132,7 +132,7 @@ class EGSGame(ServiceGame): class EpicGamesStoreService(OnlineService): """Service class for Epic Games Store""" - id = "egs" + type = "egs" name = _("Epic Games Store") login_window_width = 500 login_window_height = 850 @@ -170,8 +170,8 @@ class EpicGamesStoreService(OnlineService): 'Chrome/84.0.4147.38 Safari/537.36' ) - def __init__(self): - super().__init__() + def __init__(self, id): + super().__init__(id) self.session = requests.session() self.session.headers['User-Agent'] = self.user_agent if os.path.exists(self.token_path): diff --git a/lutris/services/flathub.py b/lutris/services/flathub.py index 0c6130821..e003d2fd5 100644 --- a/lutris/services/flathub.py +++ b/lutris/services/flathub.py @@ -54,7 +54,7 @@ class FlathubGame(ServiceGame): class FlathubService(BaseService): """Service class for Flathub""" - id = "flathub" + type = "flathub" name = _("Flathub") icon = "flathub" medias = { diff --git a/lutris/services/gog.py b/lutris/services/gog.py index dd98f3139..3b0c6507a 100644 --- a/lutris/services/gog.py +++ b/lutris/services/gog.py @@ -54,7 +54,7 @@ class GOGGame(ServiceGame): @classmethod def new_from_gog_game(cls, gog_game): """Return a GOG game instance from the API info""" - service_game = GOGGame() + service_game = cls() service_game.appid = str(gog_game["id"]) service_game.slug = gog_game["slug"] service_game.name = gog_game["title"] @@ -65,7 +65,7 @@ class GOGGame(ServiceGame): class GOGService(OnlineService): """Service class for GOG""" - id = "gog" + type = "gog" name = _("GOG") icon = "gog" has_extras = True @@ -89,8 +89,8 @@ class GOGService(OnlineService): token_path = os.path.join(settings.CACHE_DIR, ".gog.token") cache_path = os.path.join(settings.CACHE_DIR, "gog-library.json") - def __init__(self): - super().__init__() + def __init__(self, id): + super().__init__(id) gog_locales = { "en": "en-US", @@ -133,7 +133,7 @@ class GOGService(OnlineService): if not self.is_connected(): logger.error("User not connected to GOG") return - games = [GOGGame.new_from_gog_game(game) for game in self.get_library()] + games = [self.get_service_game(game) for game in self.get_library()] for game in games: game.save() self.match_games() diff --git a/lutris/services/humblebundle.py b/lutris/services/humblebundle.py index 9156b17f8..7883c25a4 100644 --- a/lutris/services/humblebundle.py +++ b/lutris/services/humblebundle.py @@ -56,7 +56,7 @@ class HumbleBundleGame(ServiceGame): class HumbleBundleService(OnlineService): """Service for Humble Bundle""" - id = "humblebundle" + type = "humblebundle" _matcher = "humble" name = _("Humble Bundle") icon = "humblebundle" diff --git a/lutris/services/itchio.py b/lutris/services/itchio.py index 8513d3d8a..4edfd640d 100644 --- a/lutris/services/itchio.py +++ b/lutris/services/itchio.py @@ -74,7 +74,7 @@ class ItchIoGameTraits(): class ItchIoService(OnlineService): """Service class for itch.io""" - id = "itchio" + type = "itchio" # According to their branding, "itch.io" is supposed to be all lowercase name = _("itch.io") icon = "itchio" diff --git a/lutris/services/lutris.py b/lutris/services/lutris.py index 2e0085473..296e26e1d 100644 --- a/lutris/services/lutris.py +++ b/lutris/services/lutris.py @@ -35,7 +35,7 @@ class LutrisGame(ServiceGame): class LutrisService(OnlineService): """Service for Lutris games""" - id = "lutris" + type = "lutris" name = _("Lutris") icon = "lutris" online = True diff --git a/lutris/services/mame.py b/lutris/services/mame.py index 971670b2a..0433b8365 100644 --- a/lutris/services/mame.py +++ b/lutris/services/mame.py @@ -7,6 +7,6 @@ from lutris.services.base import BaseService class MAMEService(BaseService): """Service class for MAME""" - id = "mame" + type = "mame" name = _("MAME") icon = "mame" diff --git a/lutris/services/origin.py b/lutris/services/origin.py index 68c307ff4..8d05c3140 100644 --- a/lutris/services/origin.py +++ b/lutris/services/origin.py @@ -125,7 +125,7 @@ class LegacyRenegotiationHTTPAdapter(requests.adapters.HTTPAdapter): class OriginService(OnlineService): """Service class for EA Origin""" - id = "origin" + type = "origin" name = _("Origin") icon = "origin" client_installer = "origin" @@ -149,8 +149,8 @@ class OriginService(OnlineService): ) % redirect_uri login_user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0 QtWebEngine/5.8.0" - def __init__(self): - super().__init__() + def __init__(self, id): + super().__init__(id) self.session = requests.session() self.session.mount("https://", LegacyRenegotiationHTTPAdapter()) diff --git a/lutris/services/steam.py b/lutris/services/steam.py index 315fb36ef..af83e8bf7 100644 --- a/lutris/services/steam.py +++ b/lutris/services/steam.py @@ -72,7 +72,7 @@ class SteamGame(ServiceGame): class SteamService(BaseService): - id = "steam" + type = "steam" name = _("Steam") icon = "steam-client" medias = { diff --git a/lutris/services/steamwindows.py b/lutris/services/steamwindows.py index 4d52284d8..866de40c5 100644 --- a/lutris/services/steamwindows.py +++ b/lutris/services/steamwindows.py @@ -21,7 +21,7 @@ class SteamWindowsGame(SteamGame): class SteamWindowsService(SteamService): - id = "steamwindows" + type = "steamwindows" name = _("Steam for Windows") runner = "wine" game_class = SteamWindowsGame diff --git a/lutris/services/ubisoft.py b/lutris/services/ubisoft.py index 2c3e4a214..ed86c1b40 100644 --- a/lutris/services/ubisoft.py +++ b/lutris/services/ubisoft.py @@ -78,7 +78,7 @@ class UbisoftGame(ServiceGame): class UbisoftConnectService(OnlineService): """Service class for Ubisoft Connect""" - id = "ubisoft" + type = "ubisoft" name = _("Ubisoft Connect") icon = "ubisoft" runner = "wine" @@ -105,8 +105,8 @@ class UbisoftConnectService(OnlineService): } default_format = "cover" - def __init__(self): - super().__init__() + def __init__(self, id): + super().__init__(id) self.client = UbisoftConnectClient(self) def auth_lost(self): diff --git a/lutris/services/xdg.py b/lutris/services/xdg.py index ce8b28f3c..467e5cfa3 100644 --- a/lutris/services/xdg.py +++ b/lutris/services/xdg.py @@ -40,7 +40,7 @@ class XDGMedia(ServiceMedia): class XDGService(BaseService): - id = "xdg" + type = "xdg" name = _("Local") icon = "linux" online = False