def handle_exception(): """ Context manager translating network related exceptions to custom :mod:`~galaxy.api.errors`. """ try: yield except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError as error: raise UnknownBackendResponse(error.message) except aiohttp.ClientResponseError as error: if error.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired(error.message) if error.status == HTTPStatus.FORBIDDEN: raise AccessDenied(error.message) if error.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable(error.message) if error.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests(error.message) if error.status >= 500: raise BackendError(error.message) if error.status >= 400: logger.warning("Got status %d while performing %s request for %s", error.status, error.request_info.method, str(error.request_info.url)) raise UnknownError(error.message) except aiohttp.ClientError as e: logger.exception("Caught exception while performing request") raise UnknownError(repr(e))
async def request(self, method, url, *args, **kwargs): try: response = await self._session.request(method, url, *args, **kwargs) except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError: raise UnknownBackendResponse() except aiohttp.ClientError: logging.exception( "Caught exception while running {} request for {}".format( method, url)) raise UnknownError() if response.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests() if response.status >= 500: raise BackendError() if response.status >= 400: logging.warning( "Got status {} while running {} request for {}".format( response.status, method, url)) raise UnknownError() return response
async def get_user_presence(self, user_id: str, context: Any) -> UserPresence: user_info = context.get(user_id) if user_info is None: raise UnknownError( "User {} not in friend list (plugin only supports fetching presence for friends)".format(user_id) ) return await presence_from_user_info(user_info, self._translations_cache)
async def _import_trophies( self, comm_id: CommunicationId, pending_tids: Set[TitleId], pending_tid_cids: _TID_CIDS_DICT, tid_trophies: _TID_TROPHIES_DICT, timestamp: UnixTimestamp ): def handle_error(error_): for tid_ in pending_tids: del pending_tid_cids[tid_] try: trophies: List[Achievement] = await self._psn_client.async_get_earned_trophies(comm_id) self._trophies_cache.update(comm_id, trophies, timestamp) while pending_tids: tid = pending_tids.pop() game_trophies = tid_trophies[tid] game_trophies.extend(trophies) pending_comm_ids = pending_tid_cids[tid] pending_comm_ids.remove(comm_id) if not pending_comm_ids: # the game has already all comm ids processed del pending_tid_cids[tid] except ApplicationError as error: handle_error(error) except Exception: logging.exception("Unhandled exception. Please report it to the plugin developers") handle_error(UnknownError())
def test_get_room_history_from_timestamp_failure(plugin, readline, write): request = { "jsonrpc": "2.0", "id": "3", "method": "import_room_history_from_timestamp", "params": { "room_id": "10", "from_timestamp": 1549454800 } } readline.side_effect = [json.dumps(request), ""] plugin.get_room_history_from_timestamp.coro.side_effect = UnknownError() asyncio.run(plugin.run()) plugin.get_room_history_from_timestamp.assert_called_with( room_id="10", from_timestamp=1549454800 ) response = json.loads(write.call_args[0][0]) assert response == { "jsonrpc": "2.0", "id": "3", "error": { "code": 0, "message": "Unknown error" } }
async def import_user_presence(user_id, context_) -> None: try: self._user_presence_import_success(user_id, await self.get_user_presence(user_id, context_)) except ApplicationError as error: self._user_presence_import_failure(user_id, error) except Exception: logging.exception("Unexpected exception raised in import_user_presence") self._user_presence_import_failure(user_id, UnknownError())
async def get_user_presence(self, user_id: str, context: Any) -> UserPresence: if user_id not in context: raise UnknownError( 'plugin/get_user_presence: failed to get info for user %s' % user_id) return context[user_id]
async def install_game(self, game_id): if game_id in self._under_installation: return self._under_installation.add(game_id) try: game = self._humble_games.get(game_id) if game is None: logging.error( f'Install game: game {game_id} not found. Humble games: {self._humble_games.keys()}' ) return if isinstance(game, ChoiceGame): webbrowser.open(game.presentation_url) return if isinstance(game, Key): try: await gui.show_key(game) except Exception as e: logging.error(e, extra={'platform_info': platform.uname()}) webbrowser.open('https://www.humblebundle.com/home/keys') return try: hp = HP.WINDOWS if IS_WINDOWS else HP.MAC curr_os_download = game.downloads[hp] except KeyError: raise UnknownError( f'{game.human_name} has only downloads for {list(game.downloads.keys())}' ) if isinstance(game, Subproduct): chosen_download_struct = self._download_resolver( curr_os_download) urls = await self._api.sign_url_subproduct( chosen_download_struct, curr_os_download.machine_name) webbrowser.open(urls['signed_url']) if isinstance(game, TroveGame): try: urls = await self._api.sign_url_trove( curr_os_download, game.machine_name) except AuthenticationRequired: logging.info( 'Looks like your Humble Monthly subscription has expired.' ) webbrowser.open( 'https://www.humblebundle.com/subscription/home') else: webbrowser.open(urls['signed_url']) except Exception as e: logging.error(e, extra={'game': game}) raise finally: self._under_installation.remove(game_id)
async def import_os_compatibility(game_id, context_): try: os_compatibility = await self.get_os_compatibility(game_id, context_) self._os_compatibility_import_success(game_id, os_compatibility) except ApplicationError as error: self._os_compatibility_import_failure(game_id, error) except Exception: logging.exception("Unexpected exception raised in import_os_compatibility") self._os_compatibility_import_failure(game_id, UnknownError())
async def get_local_size( self, game_id, context: Dict[str, pathlib.PurePath]) -> Optional[int]: try: return parse_map_crc_for_total_size(context[game_id]) except (KeyError, FileNotFoundError) as e: raise UnknownError( f"Manifest for game {game_id} is not found: {repr(e)} | context: {context}" )
async def import_game_achievements(game_id, context_): try: achievements = await self.get_unlocked_achievements(game_id, context_) self._game_achievements_import_success(game_id, achievements) except ApplicationError as error: self._game_achievements_import_failure(game_id, error) except Exception: logging.exception("Unexpected exception raised in import_game_achievements") self._game_achievements_import_failure(game_id, UnknownError())
async def import_game_time(game_id, context_): try: game_time = await self.get_game_time(game_id, context_) self._game_time_import_success(game_time) except ApplicationError as error: self._game_time_import_failure(game_id, error) except Exception: logging.exception("Unexpected exception raised in import_game_time") self._game_time_import_failure(game_id, UnknownError())
async def get_subscription_games( self, subscription_name: str, context: Dict[str, str]) -> AsyncGenerator[List[SubscriptionGame], None]: try: tier = context[subscription_name] except KeyError: raise UnknownError( f'Unknown subscription name {subscription_name}!') yield await self._backend_client.get_games_in_subscription(tier)
async def _import_element(self, id_, context_): try: element = await self._get(id_, context_) self._notification_success(id_, element) except ApplicationError as error: self._notification_failure(id_, error) except asyncio.CancelledError: pass except Exception: logger.exception("Unexpected exception raised in %s importer", self._name) self._notification_failure(id_, UnknownError())
async def _import_game_achievements(self, title_id: TitleId, comm_id: CommunicationId): try: trophies: List[Achievement] = await self._psn_client.async_get_earned_trophies(comm_id) timestamp = max(trophy.unlock_time for trophy in trophies) self._trophies_cache.update(comm_id, trophies, timestamp) self.game_achievements_import_success(title_id, trophies) except ApplicationError as error: self.game_achievements_import_failure(title_id, error) except Exception: logging.exception("Unhandled exception. Please report it to the plugin developers") self.game_achievements_import_failure(title_id, UnknownError())
def handle_status_code(status_code): if status_code == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if status_code == HTTPStatus.FORBIDDEN: raise AccessDenied() if status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if status_code >= 500: raise BackendError() if status_code >= 400: raise UnknownError()
async def _reedem_trove_download(self, download: TroveDownload, product_machine_name: str): """Unknown purpose - humble http client do this after post for signed_url Response should be text with {'success': True} if everything is OK """ res = await self._request('post', self._TROVE_REDEEM_DOWNLOAD, params={ 'download': download.machine_name, 'download_page': "false", # TODO check what it does 'product': product_machine_name }) content = await res.read() if content != b"{'success': True}": logging.error(f'unexpected response while reedem trove download: {content}') raise UnknownError()
async def import_games_achievements(self, _game_ids): game_ids = set(_game_ids) error = UnknownError() try: achievement_sets = { } # 'offerId' to 'achievementSet' names mapping for offer_id, achievement_set in ( await self._backend_client.get_achievements_sets(self._persona_id )).items(): if not achievement_set: self.game_achievements_import_success(offer_id, []) game_ids.remove(offer_id) else: achievement_sets[offer_id] = achievement_set if not achievement_sets: return for offer_id, achievements in ( await self._backend_client.get_achievements( self._persona_id, achievement_sets)).items(): try: self.game_achievements_import_success( offer_id, [ Achievement(achievement_id=key, achievement_name=value["name"], unlock_time=value["u"]) for key, value in achievements.items() if value["complete"] ]) except KeyError: self.game_achievements_import_failure( offer_id, UnknownBackendResponse()) except ApplicationError as error: self.game_achievements_import_failure(offer_id, error) finally: game_ids.remove(offer_id) except KeyError: error = UnknownBackendResponse() except ApplicationError as _error: error = _error except Exception: pass # handled below finally: # any other exceptions or not answered game_ids are responded with an error [ self.game_achievements_import_failure(game_id, error) for game_id in game_ids ]
async def do_request(self, method, url, data=None, json=True, headers=None, ignore_failure=False): loop = asyncio.get_event_loop() if not headers: headers = self._authentication_client.session.headers try: if data is None: data = {} params = { "method": method, "url": url, "data": data, "timeout": self._authentication_client.timeout, "headers": headers } try: response = await loop.run_in_executor( None, functools.partial( self._authentication_client.session.request, **params)) except requests.Timeout: raise BackendTimeout() if not ignore_failure: if response.status_code == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status_code == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status_code >= 500: raise BackendError() if response.status_code >= 400: raise UnknownError() if json: return response.json() else: return response except Exception as e: log.exception( f"Request exception: {str(e)}; url: {url}, method: {method}, data: {data}, headers: {headers}" ) raise
async def import_game_times(self, game_ids): remaining_game_ids = set(game_ids) try: game_times = await self._get_game_times_dict() for game_id in game_ids: game_time = game_times.get(game_id) if game_time is None: self.game_time_import_failure(game_id, UnknownError()) else: self.game_time_import_success(game_time) remaining_game_ids.remove(game_id) except Exception as error: logging.exception("Fail to import game times") for game_id in remaining_game_ids: self.game_time_import_failure(game_id, error)
async def _do_request(self, method, url, *args, **kwargs): loop = asyncio.get_running_loop() r = await loop.run_in_executor(None, functools.partial(self.session.request, method, url, *args, **kwargs)) log.info(f"{r.status_code}: response from endpoint {url}") if r.status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN): raise AccessDenied() if r.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if r.status_code >= 500: raise BackendError() if r.status_code >= 400: raise UnknownError() j = r.json() # all ubi endpoints return jsons return j
def test_get_friends_failure(plugin, read, write): request = {"jsonrpc": "2.0", "id": "3", "method": "import_friends"} read.side_effect = [json.dumps(request).encode() + b"\n", b""] plugin.get_friends.coro.side_effect = UnknownError() asyncio.run(plugin.run()) plugin.get_friends.assert_called_with() response = json.loads(write.call_args[0][0]) assert response == { "jsonrpc": "2.0", "id": "3", "error": { "code": 0, "message": "Unknown error", } }
async def test_failure(plugin, read, write): request = {"jsonrpc": "2.0", "id": "3", "method": "import_owned_games"} read.side_effect = [ async_return_value(create_message(request)), async_return_value(b"", 10) ] plugin.get_owned_games.side_effect = UnknownError() await plugin.run() plugin.get_owned_games.assert_called_with() assert get_messages(write) == [{ "jsonrpc": "2.0", "id": "3", "error": { "code": 0, "message": "Unknown error" } }]
async def get_game_time(self, game_id: OfferId, last_played_games: Any) -> GameTime: try: offer = self._offer_id_cache.get(game_id) if offer is None: logging.exception("Internal cache out of sync") raise UnknownError() master_title_id: MasterTitleId = offer["masterTitleId"] multiplayer_id: Optional[MultiplayerId] = self._get_multiplayer_id( offer) return await self._get_game_times_for_offer( game_id, master_title_id, multiplayer_id, last_played_games.get(master_title_id)) except KeyError as e: logging.exception("Failed to import game times %s", repr(e)) raise UnknownBackendResponse()
async def prepare_subscription_games_context( self, subscription_names: List[str]) -> Any: self._check_authenticated() subscription_name_to_tier = { 'EA Play': 'standard', 'EA Play Pro': 'premium' } subscriptions = {} for sub_name in subscription_names: try: tier = subscription_name_to_tier[sub_name] except KeyError: logging.error( "Assertion: 'Galaxy passed unknown subscription name %s. This should not happen!", sub_name) raise UnknownError(f'Unknown subscription name {sub_name}!') subscriptions[ sub_name] = await self._backend_client.get_games_in_subscription( tier) return subscriptions
async def _request(self, method, *args, **kwargs): try: response = await self._session.request(method, *args, **kwargs) except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ClientConnectionError: raise NetworkError() logging.debug(f"Request response status: {response.status}") if response.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status >= 500: raise BackendError() if response.status >= 400: raise UnknownError() return response
def test_get_rooms_failure(plugin, readline, write): request = { "jsonrpc": "2.0", "id": "9", "method": "import_rooms" } readline.side_effect = [json.dumps(request), ""] plugin.get_rooms.coro.side_effect = UnknownError() asyncio.run(plugin.run()) plugin.get_rooms.assert_called_with() response = json.loads(write.call_args[0][0]) assert response == { "jsonrpc": "2.0", "id": "9", "error": { "code": 0, "message": "Unknown error" } }
async def import_game_times(self, game_ids: List[str]): """ Override this method to return game times for games owned by the currently authenticated user. Call game_time_import_success/game_time_import_failure for each game_id on the list. This method is called by GOG Galaxy Client. :param game_ids: ids of the games for which the game time is imported """ try: game_times = await self.get_game_times() game_ids_set = set(game_ids) for game_time in game_times: if game_time.game_id not in game_ids_set: continue self.game_time_import_success(game_time) game_ids_set.discard(game_time.game_id) for game_id in game_ids_set: self.game_time_import_failure(game_id, UnknownError()) except Exception as error: for game_id in game_ids: self.game_time_import_failure(game_id, error)
async def import_game_time(offer_id: OfferId): try: offer = self._offer_id_cache.get(offer_id) if offer is None: raise Exception("Internal cache out of sync") master_title_id: MasterTitleId = offer["masterTitleId"] multiplayer_id: Optional[ MultiplayerId] = self._get_multiplayer_id(offer) self.game_time_import_success( await self._get_game_times_for_offer( offer_id, master_title_id, multiplayer_id, self._last_played_games.get(master_title_id))) except KeyError: self.game_time_import_failure(offer_id, UnknownBackendResponse()) except ApplicationError as error: self.game_time_import_failure(offer_id, error) except Exception: logging.exception( "Unhandled exception. Please report it to the plugin developers" ) self.game_time_import_failure(offer_id, UnknownError())
async def test_start_game_times_import(plugin, write, mocker): game_time_import_success = mocker.patch.object(plugin, "game_time_import_success") game_time_import_failure = mocker.patch.object(plugin, "game_time_import_failure") game_times_import_finished = mocker.patch.object(plugin, "game_times_import_finished") game_ids = ["1", "5"] game_time = GameTime("1", 10, 1549550502) plugin.get_game_times.coro.return_value = [ game_time ] await plugin.start_game_times_import(game_ids) with pytest.raises(ImportInProgress): await plugin.start_game_times_import(["4", "8"]) # wait until all tasks are finished for _ in range(4): await asyncio.sleep(0) plugin.get_game_times.coro.assert_called_once_with() game_time_import_success.assert_called_once_with(game_time) game_time_import_failure.assert_called_once_with("5", UnknownError()) game_times_import_finished.assert_called_once_with()