messenger.py (26235B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\freelanceprojects\client\messenger.py 2 import datetimeutils 3 import logging 4 import uuid 5 from careergoals.client.const import get_proto_id_from_career_path_id 6 from eveProto.generated.eve_public.freelance.project.api.events_pb2 import DiscoveryOverflowed 7 from eveProto.generated.eve_public.freelance.project.api.requests_pb2 import GetRequest, GetResponse, CloseRequest, CloseResponse, KickRequest, KickResponse, ResignRequest, ResignResponse, CommitRequest, CommitResponse, RedeemRewardRequest, RedeemRewardResponse, GetAllActiveForCorporationRequest, GetAllActiveForCorporationResponse, GetAllBroadcastedRequest, GetAllBroadcastedResponse, GetAllCommittedRequest, GetAllCommittedResponse, GetAllUnredeemedRequest, GetAllUnredeemedResponse, GetCommitLimitsRequest, GetCommitLimitsResponse, GetParticipationDetailsRequest, GetParticipationDetailsResponse, GetCreationLimitsRequest, GetCreationLimitsResponse, GetCreationPricesRequest, GetCreationPricesResponse, GetAllInactiveRequest, GetAllInactiveResponse, CreateRequest, CreateResponse, GetStatsRequest, GetStatsResponse, GetForManagerRequest, GetForManagerResponse, GetAllActiveRequest, GetAllActiveResponse 8 from eveProto.generated.eve_public.freelance.project.project_pb2 import Identifier as PROJECT_ID 9 from eveProto.generated.eve_public.solarsystem.solarsystem_pb2 import Identifier as SOLAR_SYSTEM_ID 10 from eveProto.monolith_converters.units import split_precision 11 from freelanceprojects.client.data.creation_costs import CreationPrices 12 from freelanceprojects.client.data.project_limits import CreationLimits, CommitLimits 13 from freelanceprojects.client.errors import MaxActiveProjectsPerSystemReachedError, MaxActiveProjectsPerCorpReachedError, InvalidBroadcastingLocationError, MissingRoleError, OutdatedMethodError, OutdatedProjectCostError, GenericProjectUnauthorizedError, GenericProjectForbiddenError, ProjectStateError, ParticipantStateError, MaxCommittedParticipantsPerProjectError, MaxProjectsCommittedPerParticipantError, InvalidCharacterAgeError, CannotManageACLError 14 from freelanceprojects.client.payload_parser import parse_project_details, parse_participants, parse_list_of_projects, parse_managed_project 15 from freelanceprojects.common.errors import ServiceUnavailableError, GenericProjectError, GenericProjectInternalServerError, GenericProjectNotFoundError, GenericProjectBadRequestError 16 from six.moves.http_client import OK, BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_SERVER_ERROR, responses as httplib_responses 17 from stackless_response_router.exceptions import TimeoutException, UnpackException 18 logger = logging.getLogger('freelance_projects') 19 PAGE_SIZE = 25 20 21 class FreelanceProjectsMessenger(object): 22 23 def __init__(self, public_gateway): 24 self.public_gateway = public_gateway 25 self._mock_messenger = None 26 self._should_mock = None 27 28 def set_mocked(self, should_mock): 29 self._should_mock = should_mock 30 31 @property 32 def mock_messenger(self): 33 if not self._mock_messenger: 34 from freelanceprojects.client.mock.mock_messengers import MockFreelanceProjectsMessenger 35 self._mock_messenger = MockFreelanceProjectsMessenger() 36 return self._mock_messenger 37 38 def close(self, project_id): 39 if self._should_mock: 40 return self.mock_messenger.close(project_id) 41 request = CloseRequest() 42 request.project.uuid = project_id.bytes 43 info_log = ' CloseRequest: \n{request}\n'.format(request=request) 44 status_code, payload = self._send_request(request, response_class=CloseResponse, info_log=info_log) 45 if status_code == OK: 46 logger.info(info_log + 'SUCCESS') 47 return 48 if payload.HasField('unspecified_error'): 49 self._raise_generic_error(info_log, status_code) 50 if payload.HasField('cannot_manage_projects'): 51 error = MissingRoleError() 52 self._raise_custom_error(info_log, error) 53 if payload.HasField('invalid_project_state'): 54 error = ProjectStateError() 55 self._raise_custom_error(info_log, error) 56 57 def commit(self, project_id): 58 if self._should_mock: 59 return self.mock_messenger.commit(project_id) 60 request = CommitRequest() 61 request.project.uuid = project_id.bytes 62 info_log = ' CommitRequest: \n{request}\n'.format(request=request) 63 status_code, payload = self._send_request(request, response_class=CommitResponse, info_log=info_log) 64 if status_code == OK: 65 logger.info(info_log + 'SUCCESS') 66 return 67 if payload.HasField('unspecified_error'): 68 self._raise_generic_error(info_log, status_code) 69 if payload.HasField('invalid_project_state'): 70 error = ProjectStateError() 71 self._raise_custom_error(info_log, error) 72 if payload.HasField('invalid_participation_state'): 73 error = ParticipantStateError() 74 self._raise_custom_error(info_log, error) 75 if payload.HasField('max_committed_participants_per_project'): 76 error = MaxCommittedParticipantsPerProjectError() 77 self._raise_custom_error(info_log, error) 78 if payload.HasField('max_projects_committed_per_participant'): 79 error = MaxProjectsCommittedPerParticipantError() 80 self._raise_custom_error(info_log, error) 81 if payload.HasField('invalid_character_age'): 82 age_restriction = payload.invalid_character_age.character_age_restriction 83 max_age = age_restriction.max_age if age_restriction.HasField('max_age') else None 84 min_age = age_restriction.min_age if age_restriction.HasField('min_age') else None 85 error = InvalidCharacterAgeError(min_age, max_age) 86 self._raise_custom_error(info_log, error) 87 if payload.HasField('restricted'): 88 error = GenericProjectUnauthorizedError() 89 self._raise_custom_error(info_log, error) 90 91 def create(self, project_draft): 92 if self._should_mock: 93 return self.mock_messenger.create(project_draft) 94 request = CreateRequest() 95 request.name = project_draft.name 96 request.description = project_draft.description 97 request.target_progress = project_draft.target_progress 98 career_path = project_draft.career_path 99 request.career = get_proto_id_from_career_path_id(career_path) 100 request.deadline.FromDatetime(datetimeutils.filetime_to_datetime(project_draft.end_time)) 101 request.configuration.CopyFrom(project_draft.contribution_method_configuration.to_proto()) 102 contribution_modifiers = request.contribution_modifiers 103 if project_draft.max_contributions_per_participant: 104 contribution_modifiers.contributions_per_participant_limit = project_draft.max_contributions_per_participant 105 else: 106 contribution_modifiers.contributions_per_participant_unlimited = True 107 if project_draft.max_progress_per_contribution: 108 contribution_modifiers.progress_per_contribution_limit = project_draft.max_progress_per_contribution 109 else: 110 contribution_modifiers.progress_per_contribution_unlimited = True 111 if project_draft.contribution_multiplier: 112 contribution_modifiers.scalar_multiplier = project_draft.contribution_multiplier * 0.01 113 else: 114 contribution_modifiers.default_multiplier = True 115 participation_restrictions = request.participation_restrictions 116 participation_restrictions.max_committed_participants = project_draft.max_committed_participants 117 if project_draft.max_character_age is None: 118 participation_restrictions.age_restriction.no_max_age = True 119 else: 120 participation_restrictions.age_restriction.max_age = project_draft.max_character_age 121 if project_draft.min_character_age is None: 122 participation_restrictions.age_restriction.no_min_age = True 123 else: 124 participation_restrictions.age_restriction.min_age = project_draft.min_character_age 125 solar_systems = [] 126 for solar_system_id in project_draft.broadcast_locations: 127 solar_systems.append(SOLAR_SYSTEM_ID(sequential=solar_system_id)) 128 129 request.broadcast_locations.solar_systems.extend(solar_systems) 130 units, nanos = split_precision(project_draft.payout_per_unit) 131 request.reward.payout_per_unit.units = units 132 request.reward.payout_per_unit.nanos = nanos 133 request.expected_cost.CopyFrom(project_draft.expected_cost.to_proto()) 134 if project_draft.access_list is None: 135 request.no_acl = True 136 else: 137 request.acl.sequential = project_draft.access_list 138 info_log = ' CreateRequest: \n{request}\n'.format(request=request) 139 status_code, payload = self._send_request(request, response_class=CreateResponse, info_log=info_log, max_attempts=3) 140 info_log += 'RESPONSE:\n status_code: {status_code}\n{payload}'.format(status_code=status_code, payload=payload) 141 if payload.HasField('created') and status_code == OK: 142 logger.info(info_log + 'SUCCESS') 143 return uuid.UUID(bytes=payload.created.project.uuid) 144 if payload.HasField('unspecified_error'): 145 self._raise_generic_error(info_log, status_code) 146 if payload.HasField('max_active_projects_per_corporation_reached'): 147 error = MaxActiveProjectsPerCorpReachedError(limit=payload.max_active_projects_per_corporation_reached.limit) 148 self._raise_custom_error(info_log, error) 149 if payload.HasField('invalid_broadcasting_locations'): 150 solar_systems = [] 151 for solar_system in payload.invalid_broadcasting_locations.solar_systems: 152 solar_systems.append(int(solar_system.sequential)) 153 154 error = InvalidBroadcastingLocationError(solar_systems=solar_systems) 155 self._raise_custom_error(info_log, error) 156 if payload.HasField('cannot_manage_projects'): 157 error = MissingRoleError() 158 self._raise_custom_error(info_log, error) 159 if payload.HasField('max_active_projects_broadcast_per_system_reached'): 160 solar_systems = [] 161 for solar_system in payload.max_active_projects_broadcast_per_system_reached.solar_systems: 162 solar_systems.append(int(solar_system.sequential)) 163 164 error = MaxActiveProjectsPerSystemReachedError(limit=payload.max_active_projects_broadcast_per_system_reached.limit, solar_systems=solar_systems) 165 self._raise_custom_error(info_log, error) 166 if payload.HasField('outdated_contribution_methods'): 167 error = OutdatedMethodError() 168 self._raise_custom_error(info_log, error) 169 if payload.HasField('outdated_project_cost'): 170 error = OutdatedProjectCostError() 171 self._raise_custom_error(info_log, error) 172 if payload.HasField('cannot_manage_acl'): 173 error = CannotManageACLError(acl=project_draft.access_list) 174 self._raise_custom_error(info_log, error) 175 self._raise_custom_error(info_log, error=GenericProjectError('Unsupported error in create response')) 176 177 def get_broadcasted(self, solar_system_ids): 178 if self._should_mock: 179 return self.mock_messenger.get_broadcasted(solar_system_ids) 180 request = GetAllBroadcastedRequest() 181 for solar_system_id in solar_system_ids: 182 request.solar_systems.append(SOLAR_SYSTEM_ID(sequential=solar_system_id)) 183 184 info_log = ' GetAllBroadcastedRequest: \n{request}\n'.format(request=request) 185 status_code, payload = self._send_request(request, response_class=GetAllBroadcastedResponse, info_log=info_log) 186 if status_code != OK: 187 self._raise_generic_error(info_log, status_code) 188 logger.info(info_log + 'SUCCESS') 189 project_ids_by_system = {} 190 for broadcast in payload.broadcasted: 191 solar_system_id = broadcast.solar_system.sequential 192 projects = parse_list_of_projects(broadcast.projects) 193 project_ids_by_system.update({solar_system_id: projects}) 194 195 return project_ids_by_system 196 197 def get_by_corporation(self, corporation_id): 198 if self._should_mock: 199 return self.mock_messenger.get_by_corporation(corporation_id) 200 request = GetAllActiveForCorporationRequest() 201 request.corporation.sequential = corporation_id 202 info_log = ' GetAllActiveForCorporationRequest: \n{request}\n'.format(request=request) 203 status_code, payload = self._send_request(request, response_class=GetAllActiveForCorporationResponse, info_log=info_log) 204 if status_code != OK: 205 self._raise_generic_error(info_log, status_code) 206 logger.info(info_log + 'SUCCESS') 207 return parse_list_of_projects(payload.projects) 208 209 def get_all_managed(self): 210 if self._should_mock: 211 return self.mock_messenger.get_for_managers() 212 request = GetAllActiveRequest() 213 info_log = ' GetAllActiveRequest: \n{request}\n'.format(request=request) 214 status_code, payload = self._send_request(request, response_class=GetAllActiveResponse, info_log=info_log) 215 if status_code != OK: 216 self._raise_generic_error(info_log, status_code) 217 logger.info(info_log + 'SUCCESS') 218 return parse_list_of_projects(payload.projects) 219 220 def get_commit_limit(self): 221 if self._should_mock: 222 return self.mock_messenger.get_commit_limit() 223 request = GetCommitLimitsRequest() 224 info_log = ' GetCommitLimitsRequest: \n{request}\n'.format(request=request) 225 status_code, payload = self._send_request(request, response_class=GetCommitLimitsResponse, info_log=info_log) 226 if status_code != OK: 227 self._raise_generic_error(info_log, status_code) 228 logger.info(info_log + 'SUCCESS') 229 return payload.max_committed_participants_per_project 230 231 def get_committed(self): 232 if self._should_mock: 233 return self.mock_messenger.get_committed() 234 request = GetAllCommittedRequest() 235 info_log = ' GetAllCommittedRequest: \n{request}\n'.format(request=request) 236 info_log += '{request}\n'.format(request=request) 237 status_code, payload = self._send_request(request, response_class=GetAllCommittedResponse, info_log=info_log) 238 if status_code != OK: 239 self._raise_generic_error(info_log, status_code) 240 logger.info(info_log + 'SUCCESS') 241 return parse_list_of_projects(payload.projects) 242 243 def get_creation_prices(self): 244 request = GetCreationPricesRequest() 245 info_log = ' GetCreationPricesRequest: \n{request}\n'.format(request=request) 246 status_code, payload = self._send_request(request, response_class=GetCreationPricesResponse, info_log=info_log, timeout_seconds=2, max_attempts=3) 247 if status_code != OK: 248 self._raise_generic_error(info_log, status_code) 249 logger.info(info_log + 'SUCCESS: \n{response}'.format(response=payload)) 250 return CreationPrices.from_proto(payload.prices) 251 252 def get_creation_limits(self): 253 request = GetCreationLimitsRequest() 254 info_log = ' GetCreationLimitsRequest: \n{request}\n'.format(request=request) 255 status_code, payload = self._send_request(request, response_class=GetCreationLimitsResponse, info_log=info_log) 256 if status_code != OK: 257 self._raise_generic_error(info_log, status_code) 258 logger.info(info_log + 'SUCCESS') 259 return CreationLimits(max_active_projects_per_corporation=payload.max_active_projects_per_corporation, max_active_projects_broadcast_per_system=payload.max_active_projects_broadcast_per_system, max_character_age_days=payload.max_character_age_days, max_broadcasting_locations_per_project=payload.max_broadcasting_locations_per_project, max_committed_participants_per_project=payload.max_committed_participants_per_project, max_contribution_multiplier=payload.max_contribution_multiplier, max_project_duration=payload.max_project_duration.seconds) 260 261 def get_inactive(self, start_time, duration, page_token): 262 if self._should_mock: 263 return self.mock_messenger.get_inactive(start_time, duration, page_token) 264 request = GetAllInactiveRequest() 265 request.start_time.FromSeconds(start_time) 266 request.duration.FromSeconds(duration) 267 request.page.size = PAGE_SIZE 268 if page_token: 269 request.page.token = page_token 270 info_log = ' GetAllInactiveRequest: \n{request}\n'.format(request=request) 271 status_code, payload = self._send_request(request, response_class=GetAllInactiveResponse, info_log=info_log) 272 if status_code != OK: 273 self._raise_generic_error(info_log, status_code) 274 logger.info(info_log + 'SUCCESS') 275 project_ids = parse_list_of_projects(payload.projects) 276 next_page_token = payload.next_page.token 277 return (next_page_token, project_ids) 278 279 def get_participants(self, project_id, page_token = ''): 280 if self._should_mock: 281 return self.mock_messenger.get_participants(project_id, page_token) 282 request = GetParticipationDetailsRequest() 283 request.project.uuid = project_id.bytes 284 request.page.token = page_token 285 request.page.size = PAGE_SIZE 286 info_log = ' GetParticipationDetailsRequest: \n{request}\n'.format(request=request) 287 status_code, payload = self._send_request(request, response_class=GetParticipationDetailsResponse, info_log=info_log) 288 if status_code != OK: 289 self._raise_generic_error(info_log, status_code) 290 logger.info(info_log + 'SUCCESS') 291 participants = parse_participants(payload) 292 next_token = payload.next_page.token 293 return (next_token, participants) 294 295 def get_stats_request(self, project_ids): 296 if self._should_mock: 297 return self.mock_messenger.get_stats(project_ids) 298 request = GetStatsRequest() 299 for project_id in project_ids: 300 request.projects.append(PROJECT_ID(uuid=project_id.bytes)) 301 302 info_log = ' GetStatsRequest: \n{request}\n'.format(request=request) 303 status_code, payload = self._send_request(request, response_class=GetStatsResponse, info_log=info_log) 304 if status_code != OK: 305 self._raise_generic_error(info_log, status_code) 306 logger.info(info_log + 'SUCCESS') 307 stats_by_project = {} 308 redacted_projects = [] 309 for project in payload.redacted: 310 redacted_projects.append(project.sequential) 311 312 for project_stats_data in payload.project_stats: 313 project_id = uuid.UUID(bytes=project_stats_data.project.uuid) 314 stats_by_project.update({project_id: {'current_progress': project_stats_data.current_progress, 315 'committed_participants': project_stats_data.committed_participants, 316 'total_participants': project_stats_data.total_participants, 317 'total_contributors': project_stats_data.total_contributors}}) 318 319 return (stats_by_project, redacted_projects) 320 321 def get_project(self, project_id): 322 if self._should_mock: 323 return self.mock_messenger.get_project(project_id) 324 request = GetRequest() 325 request.project.uuid = project_id.bytes 326 info_log = ' GetRequest: \n{request}\n'.format(request=request) 327 status_code, payload = self._send_request(request, response_class=GetResponse, info_log=info_log) 328 if status_code != OK: 329 self._raise_generic_error(info_log, status_code) 330 logger.info(info_log + 'SUCCESS') 331 return parse_project_details(project_id, payload.project, payload.state, payload.progress, payload.units_unredeemed) 332 333 def get_managed_project(self, project_id): 334 if self._should_mock: 335 return self.mock_messenger.get_managed_project(project_id) 336 request = GetForManagerRequest() 337 request.project.uuid = project_id.bytes 338 info_log = ' GetForManagerRequest: \n{request}\n'.format(request=request) 339 status_code, payload = self._send_request(request, response_class=GetForManagerResponse, info_log=info_log) 340 if status_code != OK: 341 self._raise_generic_error(info_log, status_code) 342 logger.info(info_log + 'SUCCESS') 343 project_details = parse_managed_project(project_id, payload.project, payload.state, payload.progress, payload.units_unredeemed, payload.admin_project_details) 344 return project_details 345 346 def get_unredeemed(self, page_token): 347 if self._should_mock: 348 return self.mock_messenger.get_unredeemed(page_token) 349 request = GetAllUnredeemedRequest() 350 request.page.size = PAGE_SIZE 351 request.page.token = page_token 352 info_log = ' GetAllUnredeemedRequest: \n{request}\n'.format(request=request) 353 status_code, payload = self._send_request(request, response_class=GetAllUnredeemedResponse, info_log=info_log) 354 if status_code != OK: 355 self._raise_generic_error(info_log, status_code) 356 logger.info(info_log + 'SUCCESS') 357 project_ids = parse_list_of_projects(payload.projects) 358 next_token = payload.next_page.token 359 return (next_token, project_ids) 360 361 def kick(self, character_id, project_id): 362 if self._should_mock: 363 return self.mock_messenger.kick(character_id, project_id) 364 request = KickRequest() 365 request.project.uuid = project_id.bytes 366 request.participant.sequential = character_id 367 info_log = ' KickRequest: \n{request}\n'.format(request=request) 368 status_code, payload = self._send_request(request, response_class=KickResponse, info_log=info_log) 369 if status_code == OK: 370 logger.info(info_log + 'SUCCESS') 371 return 372 if payload.HasField('unspecified_error'): 373 self._raise_generic_error(info_log, status_code) 374 if payload.HasField('cannot_manage_projects'): 375 error = MissingRoleError() 376 self._raise_custom_error(info_log, error) 377 if payload.HasField('invalid_project_state'): 378 error = ProjectStateError() 379 self._raise_custom_error(info_log, error) 380 if payload.HasField('invalid_participation_state'): 381 error = ParticipantStateError() 382 self._raise_custom_error(info_log, error) 383 384 def redeem_rewards(self, project_id): 385 if self._should_mock: 386 return self.mock_messenger.redeem_rewards(project_id) 387 request = RedeemRewardRequest() 388 request.project.uuid = project_id.bytes 389 info_log = ' RedeemRewardRequest: \n{request}\n'.format(request=request) 390 status_code, _ = self._send_request(request, response_class=RedeemRewardResponse, info_log=info_log) 391 if status_code != OK: 392 self._raise_generic_error(info_log, status_code) 393 logger.info(info_log + 'SUCCESS') 394 395 def resign(self, project_id): 396 if self._should_mock: 397 return self.mock_messenger.resign(project_id) 398 request = ResignRequest() 399 request.project.uuid = project_id.bytes 400 info_log = ' ResignRequest: \n{request}\n'.format(request=request) 401 status_code, payload = self._send_request(request, response_class=ResignResponse, info_log=info_log) 402 if status_code == OK: 403 logger.info(info_log + 'SUCCESS') 404 return 405 if payload.HasField('unspecified_error'): 406 self._raise_generic_error(info_log, status_code) 407 if payload.HasField('invalid_project_state'): 408 error = ProjectStateError() 409 self._raise_custom_error(info_log, error) 410 if payload.HasField('invalid_participation_state'): 411 error = ParticipantStateError() 412 self._raise_custom_error(info_log, error) 413 414 def publish_discovery_overflow_event(self, solar_system_id, total_received_project_amount, project_cap): 415 event = DiscoveryOverflowed() 416 event.solar_system.sequential = solar_system_id 417 event.total_received_project_amount = total_received_project_amount 418 event.project_cap = project_cap 419 self.public_gateway.publish_event_payload(event) 420 logger.info('Published DiscoveryOverflowed event: %s', event) 421 422 def _send_request(self, request, response_class, info_log, timeout_seconds = 10, max_attempts = 2): 423 try: 424 response_primitive, response = self.public_gateway.send_blocking_character_request_and_receive_response(request_payload=request, expected_response_class=response_class, timeout_seconds=timeout_seconds, max_attempts=max_attempts, retry_delay_in_seconds=1) 425 except (TimeoutException, UnpackException) as exc: 426 error_info = 'ERROR: %s' % exc.__class__.__name__ 427 info_log += error_info 428 logger.info(info_log) 429 request_cls = request.__class__.__name__ 430 raise ServiceUnavailableError('%s, Failed to receive response from %s: %s' % (error_info, request_cls, exc)) 431 432 return (response_primitive.status_code, response) 433 434 @staticmethod 435 def _raise_generic_error(error_text, status_code): 436 error_text += 'Request for freelance project raises %s(%s)\n' % (httplib_responses[status_code], status_code) 437 logger.info(error_text) 438 if status_code == BAD_REQUEST: 439 raise GenericProjectBadRequestError(error_text) 440 if status_code == UNAUTHORIZED: 441 raise GenericProjectUnauthorizedError(error_text) 442 if status_code == FORBIDDEN: 443 raise GenericProjectForbiddenError(error_text) 444 if status_code == NOT_FOUND: 445 raise GenericProjectNotFoundError(error_text) 446 if status_code == INTERNAL_SERVER_ERROR: 447 raise GenericProjectInternalServerError(error_text) 448 raise GenericProjectError(error_text) 449 450 @staticmethod 451 def _raise_custom_error(info_log, error): 452 info_log += '\nRaising %s' % error.__str__() 453 logger.info(info_log) 454 raise error