async def test_logout_while_getting_access_token():
    http_client = AuthenticatedHttpClient(Mock, Mock)
    http_client.get_access_token = AsyncMock(return_value=Mock)

    def cancel_getting_access_token():
        http_client._getting_access_token.cancel()

    http_client._session.close = AsyncMock(
        side_effect=cancel_getting_access_token)

    await asyncio.gather(http_client._refresh_access_token(),
                         http_client.logout())
    assert http_client._getting_access_token.done() == True
    http_client.get_access_token.assert_called_once()
예제 #2
0
class PSNPlugin(Plugin):
    def __init__(self, reader, writer, token):
        super().__init__(Platform.Psn, __version__, reader, writer, token)
        self._http_client = AuthenticatedHttpClient(self.lost_authentication)
        self._psn_client = PSNClient(self._http_client)
        self._comm_ids_cache: Dict[TitleId, CommunicationId] = {}
        self._trophies_cache = Cache()
        logging.getLogger("urllib3").setLevel(logging.FATAL)

    async def _do_auth(self, npsso):
        if not npsso:
            raise InvalidCredentials()

        try:
            await self._http_client.authenticate(npsso)
            user_id, user_name = await self._psn_client.async_get_own_user_info()
        except Exception:
            raise InvalidCredentials()

        return Authentication(user_id=user_id, user_name=user_name)

    async def authenticate(self, stored_credentials=None):
        stored_npsso = stored_credentials.get("npsso") if stored_credentials else None
        if not stored_npsso:
            return NextStep("web_session", AUTH_PARAMS)

        return await self._do_auth(stored_npsso)

    async def pass_login_credentials(self, step, credentials, cookies):
        def get_npsso():
            for c in cookies:
                if c["name"] == "npsso" and c["value"]:
                    return c["value"]

        npsso = get_npsso()
        auth_info = await self._do_auth(npsso)
        self.store_credentials({"npsso": npsso})
        return auth_info

    @staticmethod
    def _is_game(comm_id: CommunicationId) -> bool:
        return comm_id != COMM_ID_NOT_AVAILABLE

    async def update_communication_id_cache(self, title_ids: List[TitleId]) -> Dict[TitleId, CommunicationId]:
        async def updater(title_id_slice: Iterable[TitleId]):
            delta.update(await self._psn_client.async_get_game_communication_id_map(title_id_slice))

        delta: Dict[TitleId, CommunicationId] = dict()
        await asyncio.gather(*[
            updater(title_ids[it:it + MAX_TITLE_IDS_PER_REQUEST])
            for it in range(0, len(title_ids), MAX_TITLE_IDS_PER_REQUEST)
        ])

        self._comm_ids_cache.update(delta)
        return delta

    async def get_game_communication_ids(self, title_ids: List[TitleId]) -> Dict[TitleId, CommunicationId]:
        result: Dict[TitleId, CommunicationId] = dict()
        misses: Set[TitleId] = set()
        for title_id in title_ids:
            comm_id: CommunicationId = self._comm_ids_cache.get(title_id)
            if comm_id:
                result[title_id] = comm_id
            else:
                misses.add(title_id)

        if misses:
            result.update(await self.update_communication_id_cache(list(misses)))

        return result

    async def get_owned_games(self):
        async def filter_games(titles):
            comm_id_map = await self.get_game_communication_ids([t.game_id for t in titles])
            return [title for title in titles if self._is_game(comm_id_map[title.game_id])]

        return await filter_games(
            await self._psn_client.async_get_owned_games()
        )

    # TODO: backward compatibility. remove when GLX handles batch imports
    async def get_unlocked_achievements(self, game_id: TitleId):
        async def get_game_comm_id():
            comm_id: CommunicationId = (await self.get_game_communication_ids([game_id]))[game_id]
            if not self._is_game(comm_id):
                raise InvalidParams()

            return comm_id

        return await self._psn_client.async_get_earned_trophies(
            await get_game_comm_id()
        )

    async def start_achievements_import(self, game_ids: List[TitleId]):
        if not self._http_client.is_authenticated:
            raise AuthenticationRequired
        await super().start_achievements_import(game_ids)

    async def import_games_achievements(self, game_ids: Iterable[TitleId]):
        try:
            comm_ids = await self.get_game_communication_ids(game_ids)
            trophy_titles = await self._psn_client.get_trophy_titles()
        except ApplicationError as error:
            for game_id in game_ids:
                self.game_achievements_import_failure(game_id, error)

        # make a map
        trophy_titles = {trophy_title.communication_id: trophy_title for trophy_title in trophy_titles}
        requests = []
        for game_id, comm_id in comm_ids.items():
            if not self._is_game(comm_id):
                self.game_achievements_import_failure(game_id, InvalidParams())
                continue
            trophy_title = trophy_titles.get(comm_id)
            if trophy_title is None:
                self.game_achievements_import_success(game_id, [])
                continue
            trophies = self._trophies_cache.get(comm_id, trophy_title.last_update_time)
            if trophies is not None:
                self.game_achievements_import_success(game_id, trophies)
                continue
            requests.append(self._import_game_achievements(game_id, comm_id))
        await asyncio.gather(*requests)

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

    async def get_friends(self):
        return await self._psn_client.async_get_friends()

    def shutdown(self):
        asyncio.create_task(self._http_client.logout())