Skip to content

Commit 98cff9c

Browse files
committed
SDK-3069: add OS compatibility import
1 parent 2e2aa8c commit 98cff9c

7 files changed

Lines changed: 314 additions & 18 deletions

File tree

src/galaxy/api/consts.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class Feature(Enum):
111111
ShutdownPlatformClient = "ShutdownPlatformClient"
112112
LaunchPlatformClient = "LaunchPlatformClient"
113113
ImportGameLibrarySettings = "ImportGameLibrarySettings"
114+
ImportOSCompatibility = "ImportOSCompatibility"
114115

115116

116117
class LicenseType(Enum):
@@ -129,3 +130,12 @@ class LocalGameState(Flag):
129130
None_ = 0
130131
Installed = 1
131132
Running = 2
133+
134+
135+
class OSCompatibility(Flag):
136+
"""Possible game OS compatibility.
137+
Use "bitwise or" to express multiple OSs compatibility, e.g. ``os=OSCompatibility.Windows|OSCompatibility.MacOS``
138+
"""
139+
Windows = 0b001
140+
MacOS = 0b010
141+
Linux = 0b100

src/galaxy/api/plugin.py

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
from enum import Enum
88
from typing import Any, Dict, List, Optional, Set, Union
99

10-
from galaxy.api.consts import Feature
10+
from galaxy.api.consts import Feature, OSCompatibility
1111
from galaxy.api.errors import ImportInProgress, UnknownError
1212
from galaxy.api.jsonrpc import ApplicationError, NotificationClient, Server
1313
from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LocalGame, NextStep, GameLibrarySettings
1414
from galaxy.task_manager import TaskManager
1515

16+
1617
class JSONEncoder(json.JSONEncoder):
1718
def default(self, o): # pylint: disable=method-hidden
1819
if dataclasses.is_dataclass(o):
@@ -47,6 +48,7 @@ def __init__(self, platform, version, reader, writer, handshake_token):
4748
self._achievements_import_in_progress = False
4849
self._game_times_import_in_progress = False
4950
self._game_library_settings_import_in_progress = False
51+
self._os_compatibility_import_in_progress = False
5052

5153
self._persistent_cache = dict()
5254

@@ -113,6 +115,9 @@ def __init__(self, platform, version, reader, writer, handshake_token):
113115
self._register_method("start_game_library_settings_import", self._start_game_library_settings_import)
114116
self._detect_feature(Feature.ImportGameLibrarySettings, ["get_game_library_settings"])
115117

118+
self._register_method("start_os_compatibility_import", self._start_os_compatibility_import)
119+
self._detect_feature(Feature.ImportOSCompatibility, ["get_os_compatibility"])
120+
116121
async def __aenter__(self):
117122
return self
118123

@@ -173,6 +178,7 @@ def _register_notification(self, name, handler, internal=False, immediate=False,
173178
def _wrap_external_method(self, handler, name: str):
174179
async def wrapper(*args, **kwargs):
175180
return await self._external_task_manager.create_task(handler(*args, **kwargs), name, False)
181+
176182
return wrapper
177183

178184
async def run(self):
@@ -420,6 +426,27 @@ def _game_library_settings_import_failure(self, game_id: str, error: Application
420426
def _game_library_settings_import_finished(self) -> None:
421427
self._notification_client.notify("game_library_settings_import_finished", None)
422428

429+
def _os_compatibility_import_success(self, game_id: str, os_compatibility: Optional[OSCompatibility]) -> None:
430+
self._notification_client.notify(
431+
"os_compatibility_import_success",
432+
{
433+
"game_id": game_id,
434+
"os_compatibility": os_compatibility
435+
}
436+
)
437+
438+
def _os_compatibility_import_failure(self, game_id: str, error: ApplicationError) -> None:
439+
self._notification_client.notify(
440+
"os_compatibility_import_failure",
441+
{
442+
"game_id": game_id,
443+
"error": error.json()
444+
}
445+
)
446+
447+
def _os_compatibility_import_finished(self) -> None:
448+
self._notification_client.notify("os_compatibility_import_finished", None)
449+
423450
def lost_authentication(self) -> None:
424451
"""Notify the client that integration has lost authentication for the
425452
current user and is unable to perform actions which would require it.
@@ -805,7 +832,7 @@ async def prepare_game_library_settings_context(self, game_ids: List[str]) -> An
805832
This allows for optimizations like batch requests to platform API.
806833
Default implementation returns None.
807834
808-
:param game_ids: the ids of the games for which game time are imported
835+
:param game_ids: the ids of the games for which game library settings are imported
809836
:return: context
810837
"""
811838
return None
@@ -815,17 +842,74 @@ async def get_game_library_settings(self, game_id: str, context: Any) -> GameLib
815842
identified by the provided game_id.
816843
This method is called by import task initialized by GOG Galaxy Client.
817844
818-
:param game_id: the id of the game for which the game time is returned
845+
:param game_id: the id of the game for which the game library settings are imported
819846
:param context: the value returned from :meth:`prepare_game_library_settings_context`
820847
:return: GameLibrarySettings object
821848
"""
822849
raise NotImplementedError()
823850

824851
def game_library_settings_import_complete(self) -> None:
825-
"""Override this method to handle operations after game times import is finished
852+
"""Override this method to handle operations after game library settings import is finished
826853
(like updating cache).
827854
"""
828855

856+
async def _start_os_compatibility_import(self, game_ids: List[str]) -> None:
857+
if self._os_compatibility_import_in_progress:
858+
raise ImportInProgress()
859+
860+
context = await self.prepare_os_compatibility_context(game_ids)
861+
862+
async def import_os_compatibility(game_id, context_):
863+
try:
864+
os_compatibility = await self.get_os_compatibility(game_id, context_)
865+
self._os_compatibility_import_success(game_id, os_compatibility)
866+
except ApplicationError as error:
867+
self._os_compatibility_import_failure(game_id, error)
868+
except Exception:
869+
logging.exception("Unexpected exception raised in import_os_compatibility")
870+
self._os_compatibility_import_failure(game_id, UnknownError())
871+
872+
async def import_os_compatibility_set(game_ids_, context_):
873+
try:
874+
await asyncio.gather(*[
875+
import_os_compatibility(game_id, context_) for game_id in game_ids_
876+
])
877+
finally:
878+
self._os_compatibility_import_finished()
879+
self._os_compatibility_import_in_progress = False
880+
self.os_compatibility_import_complete()
881+
882+
self._external_task_manager.create_task(
883+
import_os_compatibility_set(game_ids, context),
884+
"game OS compatibility import",
885+
handle_exceptions=False
886+
)
887+
self._os_compatibility_import_in_progress = True
888+
889+
async def prepare_os_compatibility_context(self, game_ids: List[str]) -> Any:
890+
"""Override this method to prepare context for get_os_compatibility.
891+
This allows for optimizations like batch requests to platform API.
892+
Default implementation returns None.
893+
894+
:param game_ids: the ids of the games for which game os compatibility is imported
895+
:return: context
896+
"""
897+
return None
898+
899+
async def get_os_compatibility(self, game_id: str, context: Any) -> Optional[OSCompatibility]:
900+
"""Override this method to return the OS compatibility for the game with the provided game_id.
901+
This method is called by import task initialized by GOG Galaxy Client.
902+
903+
:param game_id: the id of the game for which the game os compatibility is imported
904+
:param context: the value returned from :meth:`prepare_os_compatibility_context`
905+
:return: OSCompatibility flags indicating compatible OSs, or None if compatibility is not know
906+
"""
907+
raise NotImplementedError()
908+
909+
def os_compatibility_import_complete(self) -> None:
910+
"""Override this method to handle operations after OS compatibility import is finished (like updating cache)."""
911+
912+
829913
def create_and_run_plugin(plugin_class, argv):
830914
"""Call this method as an entry point for the implemented integration.
831915

src/galaxy/api/types.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from dataclasses import dataclass
2-
from typing import List, Dict, Optional
2+
from typing import Dict, List, Optional
33

44
from galaxy.api.consts import LicenseType, LocalGameState
55

6+
67
@dataclass
7-
class Authentication():
8+
class Authentication:
89
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials`
910
to inform the client that authentication has successfully finished.
1011
@@ -14,8 +15,9 @@ class Authentication():
1415
user_id: str
1516
user_name: str
1617

18+
1719
@dataclass
18-
class Cookie():
20+
class Cookie:
1921
"""Cookie
2022
2123
:param name: name of the cookie
@@ -28,8 +30,9 @@ class Cookie():
2830
domain: Optional[str] = None
2931
path: Optional[str] = None
3032

33+
3134
@dataclass
32-
class NextStep():
35+
class NextStep:
3336
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials` to open client built-in browser with given url.
3437
For example:
3538
@@ -67,8 +70,9 @@ async def authenticate(self, stored_credentials=None):
6770
cookies: Optional[List[Cookie]] = None
6871
js: Optional[Dict[str, List[str]]] = None
6972

73+
7074
@dataclass
71-
class LicenseInfo():
75+
class LicenseInfo:
7276
"""Information about the license of related product.
7377
7478
:param license_type: type of license
@@ -77,8 +81,9 @@ class LicenseInfo():
7781
license_type: LicenseType
7882
owner: Optional[str] = None
7983

84+
8085
@dataclass
81-
class Dlc():
86+
class Dlc:
8287
"""Downloadable content object.
8388
8489
:param dlc_id: id of the dlc
@@ -89,8 +94,9 @@ class Dlc():
8994
dlc_title: str
9095
license_info: LicenseInfo
9196

97+
9298
@dataclass
93-
class Game():
99+
class Game:
94100
"""Game object.
95101
96102
:param game_id: unique identifier of the game, this will be passed as parameter for methods such as launch_game
@@ -103,8 +109,9 @@ class Game():
103109
dlcs: Optional[List[Dlc]]
104110
license_info: LicenseInfo
105111

112+
106113
@dataclass
107-
class Achievement():
114+
class Achievement:
108115
"""Achievement, has to be initialized with either id or name.
109116
110117
:param unlock_time: unlock time of the achievement
@@ -119,8 +126,9 @@ def __post_init__(self):
119126
assert self.achievement_id or self.achievement_name, \
120127
"One of achievement_id or achievement_name is required"
121128

129+
122130
@dataclass
123-
class LocalGame():
131+
class LocalGame:
124132
"""Game locally present on the authenticated user's computer.
125133
126134
:param game_id: id of the game
@@ -129,8 +137,9 @@ class LocalGame():
129137
game_id: str
130138
local_game_state: LocalGameState
131139

140+
132141
@dataclass
133-
class FriendInfo():
142+
class FriendInfo:
134143
"""Information about a friend of the currently authenticated user.
135144
136145
:param user_id: id of the user
@@ -139,8 +148,9 @@ class FriendInfo():
139148
user_id: str
140149
user_name: str
141150

151+
142152
@dataclass
143-
class GameTime():
153+
class GameTime:
144154
"""Game time of a game, defines the total time spent in the game
145155
and the last time the game was played.
146156
@@ -152,8 +162,9 @@ class GameTime():
152162
time_played: Optional[int]
153163
last_played_time: Optional[int]
154164

165+
155166
@dataclass
156-
class GameLibrarySettings():
167+
class GameLibrarySettings:
157168
"""Library settings of a game, defines assigned tags and visibility flag.
158169
159170
:param game_id: id of the related game

tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ async def plugin(reader, writer):
5353
"get_game_library_settings",
5454
"prepare_game_library_settings_context",
5555
"game_library_settings_import_complete",
56+
"get_os_compatibility",
57+
"prepare_os_compatibility_context",
58+
"os_compatibility_import_complete",
5659
)
5760

5861
with ExitStack() as stack:

tests/test_features.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ def test_base_class():
1515
Feature.ImportFriends,
1616
Feature.ShutdownPlatformClient,
1717
Feature.LaunchPlatformClient,
18-
Feature.ImportGameLibrarySettings
18+
Feature.ImportGameLibrarySettings,
19+
Feature.ImportOSCompatibility
1920
}
2021

2122

tests/test_game_library_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
@pytest.mark.asyncio
12-
async def test_get_game_time_success(plugin, read, write):
12+
async def test_get_library_settings_success(plugin, read, write):
1313
plugin.prepare_game_library_settings_context.return_value = async_return_value("abc")
1414
request = {
1515
"jsonrpc": "2.0",

0 commit comments

Comments
 (0)