goalMessenger.py (18289B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\corporation\client\goals\goalMessenger.py 2 import datetime 3 import corporation.client.goals.goalMessengerSignals as messenger_signals 4 import datetimeutils 5 import gametime 6 from six.moves import http_client as httplib 7 import logging 8 import uuid 9 from corporation.client.goals.errors import GoalMessengerError, AtGoalCapacity, GoalError, GoalNotFound, BadRequestToReserveAsset, WalletAccessForbidden, InternalErrorReservingAsset, InvalidExpirationTime 10 from corporation.client.goals.goalMessengerUtil import goal_formatter, add_contribution_method 11 from dateutil.relativedelta import relativedelta 12 from eveProto.generated.eve_public.corporationgoal.api.notices_pb2 import RedeemedNotice, ExpiredNotice 13 from eveProto.generated.eve_public.corporationgoal.api.requests_pb2 import GetActiveRequest, GetActiveResponse, GetMyContributorSummaryForGoalRequest, GetMyContributorSummaryForGoalResponse, GetInactiveRequest, GetInactiveResponse, GetContributorSummariesForGoalRequest, GetContributorSummariesForGoalResponse, RedeemMyRewardsRequest, RedeemMyRewardsResponse, GetMineWithRewardsRequest, GetMineWithRewardsResponse 14 from eveProto.generated.eve_public.goal.api.notices_pb2 import ClosedNotice, CompletedNotice, CreatedNotice, DeletedNotice, ProgressedNotice, NameChangedNotice, DescriptionChangedNotice 15 from eveProto.generated.eve_public.goal.api.requests_pb2 import CreateRequest, CreateResponse, GetRequest, GetResponse, CloseRequest, CloseResponse, DeleteRequest, DeleteResponse, SetCurrentProgressRequest, SetCurrentProgressResponse, GetCapacityResponse, GetCapacityRequest 16 from eveProto.generated.eve_public.goal.contribution.api.notices_pb2 import ContributedNotice 17 from eveProto.generated.eve_public.goal.goal_pb2 import RewardPool, PaymentPeriod 18 from eveProto.monolith_converters.isk import split_isk 19 from goals.common.goalConst import CareerPathId, UPDATE_TIMEOUT_SECONDS 20 from stackless_response_router.exceptions import TimeoutException 21 logger = logging.getLogger('corporation_goals') 22 CAREER_PATH_RESOLVER = {CareerPathId.UNSPECIFIED: CreateRequest.Career.CAREER_UNSPECIFIED, 23 CareerPathId.EXPLORER: CreateRequest.Career.CAREER_EXPLORER, 24 CareerPathId.INDUSTRIALIST: CreateRequest.Career.CAREER_INDUSTRIALIST, 25 CareerPathId.SOLDIER_OF_FORTUNE: CreateRequest.Career.CAREER_SOLDIER_OF_FORTUNE, 26 CareerPathId.ENFORCER: CreateRequest.Career.CAREER_ENFORCER} 27 PAGE_SIZE = 25 28 MIN_PROJECT_TIME = 15 29 MAX_PROJECT_TIME = 1 30 31 class GoalMessenger(object): 32 _instance = None 33 public_gateway = None 34 35 @classmethod 36 def get_instance(cls, public_gateway): 37 if not cls._instance: 38 cls._instance = GoalMessenger(public_gateway) 39 return cls._instance 40 41 def __init__(self, public_gateway): 42 self.public_gateway = public_gateway 43 self._subscribe_to_notices() 44 45 def _subscribe_to_notices(self): 46 self.public_gateway.subscribe_to_notice(RedeemedNotice, lambda payload, _: self.on_redeemed(goal_id=uuid.UUID(bytes=payload.goal.uuid), quantity=payload.quantity)) 47 self.public_gateway.subscribe_to_notice(ClosedNotice, lambda payload, _: self.on_goal_closed(goal_id=uuid.UUID(bytes=payload.goal.uuid))) 48 self.public_gateway.subscribe_to_notice(CompletedNotice, lambda payload, _d: self.on_goal_completed(goal_id=uuid.UUID(bytes=payload.goal.uuid))) 49 self.public_gateway.subscribe_to_notice(CreatedNotice, lambda payload, _: self.on_goal_created(goal_id=uuid.UUID(bytes=payload.id.uuid), goal_data=payload.goal)) 50 self.public_gateway.subscribe_to_notice(DeletedNotice, lambda payload, _: self.on_goal_deleted(goal_id=uuid.UUID(bytes=payload.goal.uuid))) 51 self.public_gateway.subscribe_to_notice(ContributedNotice, lambda payload, _: self.on_goal_contribution(goal_id=uuid.UUID(bytes=payload.goal.uuid), previous_progress=payload.previous_progress, current_progress=payload.current_progress)) 52 self.public_gateway.subscribe_to_notice(ProgressedNotice, lambda payload, _: self.on_goal_progressed(goal_id=uuid.UUID(bytes=payload.goal.uuid), current_progress=payload.current_progress)) 53 self.public_gateway.subscribe_to_notice(NameChangedNotice, lambda payload, _: self.on_name_changed(goal_id=uuid.UUID(bytes=payload.goal.uuid), new_name=payload.admin_designated_new_name)) 54 self.public_gateway.subscribe_to_notice(DescriptionChangedNotice, lambda payload, _: self.on_description_changed(goal_id=uuid.UUID(bytes=payload.goal.uuid), new_description=payload.admin_designated_new_description)) 55 self.public_gateway.subscribe_to_notice(ExpiredNotice, lambda payload, _: self.on_goal_expired(goal_id=uuid.UUID(bytes=payload.goal.uuid))) 56 57 def get_goal(self, goal_id): 58 request = GetRequest() 59 request.goal.uuid = goal_id.bytes 60 logger.info('GetRequest - sending for goal %s', goal_id) 61 payload = self._send_request(request, GetResponse) 62 formatted_goal = goal_formatter(goal_id=goal_id, goal=payload.goal) 63 logger.info('GetRequest - received data for goal %s: %s', goal_id, formatted_goal) 64 return formatted_goal 65 66 def get_active_goal_ids(self, token = '', size = PAGE_SIZE): 67 request = GetActiveRequest() 68 request.page.size = size 69 request.page.token = token 70 payload = self._send_request(request, GetActiveResponse) 71 goal_ids = [ uuid.UUID(bytes=goal_id.uuid) for goal_id in payload.goal_ids ] 72 token = payload.next_page.token 73 return (token, goal_ids) 74 75 def get_unclaimed_goal_ids(self, token = '', size = PAGE_SIZE): 76 request = GetMineWithRewardsRequest() 77 request.page.size = size 78 request.page.token = token 79 payload = self._send_request(request, GetMineWithRewardsResponse) 80 goal_ids = [ uuid.UUID(bytes=goal_id.uuid) for goal_id in payload.identifiers ] 81 token = payload.next_page.token 82 return (token, goal_ids) 83 84 def get_inactive_goal_ids(self, start_time, duration, token = '', size = PAGE_SIZE): 85 request = GetInactiveRequest() 86 request.ended_timespan.start_time.FromSeconds(start_time) 87 request.ended_timespan.duration.FromSeconds(duration) 88 request.page.size = size 89 request.page.token = token 90 payload = self._send_request(request, GetInactiveResponse) 91 goal_ids = [ uuid.UUID(bytes=goal_id.uuid) for goal_id in payload.goal_ids ] 92 token = payload.next_page.token 93 return (token, goal_ids) 94 95 def get_my_contributor_summary(self, goal_id): 96 request = GetMyContributorSummaryForGoalRequest() 97 request.goal.uuid = goal_id.bytes 98 payload = self._send_request(request, GetMyContributorSummaryForGoalResponse, not_found_as_none=True) 99 if payload is None: 100 progress = 0 101 unclaimed = 0 102 else: 103 progress = payload.summary.progress 104 if payload.summary.earnings: 105 unclaimed = payload.summary.earnings[0].quantity.total 106 else: 107 unclaimed = 0 108 return {'progress': progress, 109 'rewards_unclaimed': unclaimed} 110 111 def get_all_contributor_summaries_for_goal(self, goal_id, token = '', size = PAGE_SIZE): 112 request = GetContributorSummariesForGoalRequest() 113 request.goal.uuid = goal_id.bytes 114 request.page.size = size 115 request.page.token = token 116 payload = self._send_request(request, GetContributorSummariesForGoalResponse) 117 summaries = [] 118 for summary in payload.summaries: 119 summaries.append((summary.contributor.sequential, summary.progress)) 120 121 next_token = payload.next_page.token 122 return (next_token, summaries) 123 124 def get_capacity(self): 125 request = GetCapacityRequest() 126 payload = self._send_request(request, GetCapacityResponse) 127 return payload.capacity 128 129 def redeem_reward(self, goal_id): 130 request = RedeemMyRewardsRequest() 131 request.goal_id.uuid = goal_id.bytes 132 self._send_request(request, RedeemMyRewardsResponse) 133 134 def on_redeemed(self, goal_id, quantity): 135 logger.info('Emitting Goal Redeemed Notice for goal %s and quantity %s' % (goal_id, quantity)) 136 messenger_signals.on_goal_redeemed_internal(goal_id, quantity) 137 138 def on_goal_created(self, goal_id, goal_data): 139 logger.info('Emitting Goal Created Notice %s' % goal_id) 140 format_goal_data = goal_formatter(goal_id, goal_data) 141 messenger_signals.on_goal_created_internal(goal_id, format_goal_data) 142 143 def on_goal_deleted(self, goal_id): 144 logger.info('Emitting Goal Deleted Notice %s' % goal_id) 145 messenger_signals.on_goal_deleted_internal(goal_id) 146 147 def on_goal_closed(self, goal_id): 148 logger.info('Emitting Goal Closed Notice %s' % goal_id) 149 messenger_signals.on_goal_closed_internal(goal_id) 150 151 def on_goal_completed(self, goal_id): 152 logger.info('Emitting Goal Completed Notice %s' % goal_id) 153 messenger_signals.on_goal_completed_internal(goal_id) 154 155 def on_goal_contribution(self, goal_id, previous_progress, current_progress): 156 progress_added = current_progress - previous_progress 157 logger.info('Emitting OnGoalProgressAdded Notice %s, incrementing by %s, setting progress to %s', goal_id, progress_added, current_progress) 158 messenger_signals.on_goal_contribution_internal(goal_id, progress_added, current_progress) 159 160 def on_goal_progressed(self, goal_id, current_progress): 161 logger.info('Emitting OnGoalProgressSet Notice %s, progress set to %s', goal_id, current_progress) 162 messenger_signals.on_goal_progress_set_internal(goal_id, current_progress) 163 164 def on_name_changed(self, goal_id, new_name): 165 logger.info('Emitting OnGoalNameChanged Notice %s, name set to %s', goal_id, new_name) 166 messenger_signals.on_goal_name_changed_internal(goal_id, new_name) 167 168 def on_description_changed(self, goal_id, new_description): 169 logger.info('Emitting OnGoalDescriptionChanged Notice %s, name set to %s', goal_id, new_description) 170 messenger_signals.on_goal_description_changed_internal(goal_id, new_description) 171 172 def on_goal_expired(self, goal_id): 173 logger.info('Emitting Goal Expired Notice %s' % goal_id) 174 messenger_signals.on_goal_expired_internal(goal_id) 175 176 def create_goal(self, name, description, desired_progress, method_id, contribution_fields, career_path, payment = None, expiration = None, validate_end_date = True, participation_limit = None, coverage_limit = None, multiplier = None): 177 request = CreateRequest() 178 request.name = name 179 request.description = description 180 request.desired_progress = desired_progress 181 add_contribution_method(request, method_id, contribution_fields) 182 if career_path in CAREER_PATH_RESOLVER: 183 request.career = request.career = CAREER_PATH_RESOLVER[career_path] 184 else: 185 raise ValueError('Career path not supported') 186 if payment: 187 units, nanos = split_isk(payment) 188 reward = RewardPool() 189 reward.period = PaymentPeriod.PAYMENT_PERIOD_CONTRIBUTION 190 reward.isk.amount.units = units 191 reward.isk.amount.nanos = nanos 192 request.reward_pools.append(reward) 193 if expiration: 194 expiration_datetime = datetimeutils.filetime_to_datetime(expiration) 195 if validate_end_date: 196 now = gametime.now() 197 min_datetime = now + relativedelta(minutes=MIN_PROJECT_TIME) 198 max_datetime = datetime.datetime.combine(now + relativedelta(years=MAX_PROJECT_TIME), datetime.time.max) 199 if max_datetime < expiration_datetime or expiration_datetime < min_datetime: 200 raise InvalidExpirationTime('Expiration time too short or long') 201 request.timestamp.FromDatetime(expiration_datetime) 202 else: 203 request.no_expiry = True 204 if participation_limit: 205 request.limit = participation_limit 206 else: 207 request.unlimited = True 208 if coverage_limit is not None: 209 request.contribution_limit = coverage_limit 210 else: 211 request.contribution_unlimited = True 212 if multiplier: 213 request.scalar = multiplier 214 else: 215 request.default = True 216 217 def _response_handler(primitive, payload): 218 status_code = primitive.status_code 219 status_message = primitive.status_message 220 if status_code != httplib.OK: 221 logger.info('Unexpected error when creating a goal - statusCode: %d, statusMessage: %s', status_code, status_message) 222 if status_message == 'at capacity of active goals': 223 raise AtGoalCapacity(status_message) 224 if 'unable to reserve backing asset for the rewards specified' in status_message: 225 if status_code == httplib.BAD_REQUEST: 226 raise BadRequestToReserveAsset(status_message) 227 elif status_code == httplib.FORBIDDEN: 228 raise WalletAccessForbidden(status_message) 229 else: 230 raise InternalErrorReservingAsset(status_message) 231 raise GoalError(status_message) 232 goal_id = uuid.UUID(bytes=payload.goal.uuid) 233 logger.info('Goal created successfully - Identifier: %s', repr(goal_id)) 234 235 def _timeout_handler(timeout_exception): 236 logger.error('create_goal timed out: %s' % timeout_exception, exc_info=True) 237 raise GoalError(timeout_exception) 238 239 logger.info('Sending CreateGoalRequest with data %s', request) 240 self._send_request_and_wait_for_response(request, CreateResponse, _response_handler, _timeout_handler) 241 242 def close_goal(self, goal_id): 243 request = CloseRequest() 244 request.goal.uuid = goal_id.bytes 245 246 def _response_handler(primitive, _payload): 247 if primitive.status_code != httplib.OK: 248 logger.error('unexpected error when closing goal %s - statusCode: %s, statusMessage: %s', goal_id, primitive.status_code, primitive.status_message) 249 if primitive.status_code == httplib.NOT_FOUND: 250 raise GoalNotFound(goal_id, session.corpid) 251 raise GoalError(primitive.status_message) 252 253 def _timeout_handler(timeout_exception): 254 logger.error('close_goal timed out: %s' % timeout_exception, exc_info=True) 255 raise GoalError(timeout_exception) 256 257 self._send_request_and_wait_for_response(request, CloseResponse, _response_handler, _timeout_handler) 258 259 def delete_goal(self, goal_id): 260 request = DeleteRequest() 261 request.goal.uuid = goal_id.bytes 262 263 def _response_handler(primitive, _payload): 264 if primitive.status_code != httplib.OK: 265 logger.error('unexpected error when deleting goal %s - statusCode: %s, statusMessage: %s', goal_id, primitive.status_code, primitive.status_message) 266 if primitive.status_code == httplib.NOT_FOUND: 267 raise GoalNotFound(goal_id, session.corpid) 268 raise GoalError(primitive.status_message) 269 270 def _timeout_handler(timeout_exception): 271 logger.error('delete_goal timed out: %s' % timeout_exception, exc_info=True) 272 raise GoalError(timeout_exception) 273 274 self._send_request_and_wait_for_response(request, DeleteResponse, _response_handler, _timeout_handler) 275 276 def set_manual_goal_progress(self, goal_id, current_progress, updated_progress): 277 request = SetCurrentProgressRequest() 278 request.goal.uuid = goal_id.bytes 279 request.current_progress = current_progress 280 request.new_progress = updated_progress 281 282 def _response_handler(primitive, _payload): 283 if primitive.status_code != httplib.OK: 284 logger.error('unexpected error when setting progress of goal %s - statusCode: %s, statusMessage: %s', goal_id, primitive.status_code, primitive.status_message) 285 if primitive.status_code == httplib.NOT_FOUND: 286 raise GoalNotFound(goal_id, session.corpid) 287 raise GoalError(primitive.status_message) 288 289 def _timeout_handler(timeout_exception): 290 logger.error('set_current_progress timed out: %s' % timeout_exception, exc_info=True) 291 raise GoalError(timeout_exception) 292 293 self._send_request_and_wait_for_response(request, SetCurrentProgressResponse, _response_handler, _timeout_handler) 294 295 def _send_request_and_wait_for_response(self, request, expected_response_class, response_handler, timeout_handler = None): 296 request_primitive, response_channel = self.public_gateway.send_character_request(request, expected_response_class, UPDATE_TIMEOUT_SECONDS) 297 try: 298 response_primitive, payload = response_channel.receive() 299 response_handler(response_primitive, payload) 300 except TimeoutException as e: 301 if timeout_handler is not None: 302 timeout_handler(e) 303 304 def _send_request(self, request, response_class, not_found_as_none = False): 305 logger.info('Sending Request %s:\n%s', request.__class__.__name__, request) 306 request_primitive, response_channel = self.public_gateway.send_character_request(request, response_class) 307 response_primitive, payload = response_channel.receive() 308 if response_primitive.status_code == httplib.OK: 309 logger.info('Send Request Response %s:\nRequest:\n%s\nResponse:\n%s', request.__class__.__name__, request, payload) 310 return payload 311 if not_found_as_none and response_primitive.status_code == httplib.NOT_FOUND: 312 logger.warning('%s returned with statusCode %s, statusMessage: %s', request.DESCRIPTOR.full_name, response_primitive.status_code, response_primitive.status_message) 313 return None 314 logger.error('%s returned with statusCode %s, statusMessage: %s', request.DESCRIPTOR.full_name, response_primitive.status_code, response_primitive.status_message) 315 raise GoalMessengerError(request.DESCRIPTOR.name, response_primitive.status_code)