Distinguish between service type and service ID, where the ID contains extra information to distingish between different service accounts

This commit is contained in:
Yuki Schlarb 2022-09-06 23:04:13 +02:00 committed by Mathieu Comandon
parent 3c6245546c
commit 5eb0d11dfa
25 changed files with 97 additions and 75 deletions

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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")

View file

@ -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"
}

View file

@ -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

View file

@ -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:

View file

@ -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"

View file

@ -24,7 +24,7 @@ class DolphinBanner(ServiceMedia):
class DolphinService(BaseService):
id = "dolphin"
type = "dolphin"
icon = "dolphin"
name = _("Dolphin")
local = True

View file

@ -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())

View file

@ -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):

View file

@ -54,7 +54,7 @@ class FlathubGame(ServiceGame):
class FlathubService(BaseService):
"""Service class for Flathub"""
id = "flathub"
type = "flathub"
name = _("Flathub")
icon = "flathub"
medias = {

View file

@ -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()

View file

@ -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"

View file

@ -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"

View file

@ -35,7 +35,7 @@ class LutrisGame(ServiceGame):
class LutrisService(OnlineService):
"""Service for Lutris games"""
id = "lutris"
type = "lutris"
name = _("Lutris")
icon = "lutris"
online = True

View file

@ -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"

View file

@ -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())

View file

@ -72,7 +72,7 @@ class SteamGame(ServiceGame):
class SteamService(BaseService):
id = "steam"
type = "steam"
name = _("Steam")
icon = "steam-client"
medias = {

View file

@ -21,7 +21,7 @@ class SteamWindowsGame(SteamGame):
class SteamWindowsService(SteamService):
id = "steamwindows"
type = "steamwindows"
name = _("Steam for Windows")
runner = "wine"
game_class = SteamWindowsGame

View file

@ -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):

View file

@ -40,7 +40,7 @@ class XDGMedia(ServiceMedia):
class XDGService(BaseService):
id = "xdg"
type = "xdg"
name = _("Local")
icon = "linux"
online = False