shipSkinTradingSvc.py (15474B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\cosmetics\client\shipSkinTradingSvc.py 2 import datetime 3 import gametime 4 import logging 5 import uthread2 6 import uuid 7 from carbon.common.script.sys.serviceConst import ROLE_PROGRAMMER 8 from cosmetics.client.messengers.cosmetics.ship.shipSkinTradingRequestMessenger import PublicShipSkinTradingRequestMessenger 9 from cosmetics.client.shipCosmeticTransactionSvc import get_ship_cosmetic_transaction_svc 10 from cosmetics.client.shipSkinDataSvc import get_ship_skin_data_svc 11 from cosmetics.client.ships.feature_flag import are_skin_hub_offers_enabled 12 from cosmetics.client.ships.link.validators.paste import ShipSkinListingLinkValidator 13 from cosmetics.client.ships.ship_skin_signals import on_skin_listing_cache_invalidated, on_skin_hub_offers_availability_changed, on_skin_listing_expired 14 from cosmetics.client.ships.skins.errors import GenericError, ListingError 15 from cosmetics.client.ships.skins.live_data.skin_listing_buyer_fee import ListingBuyerFeeType, ListingBuyerFee 16 from cosmetics.client.ships.skins.live_data.skin_listing_target import ListingTarget, ListedTo 17 from cosmetics.client.ships.skins.live_data.skin_listing_target import ListingTargetType 18 from cosmetics.client.skinListingsPaginationController import SkinListingsPaginationController 19 from cosmetics.common.ships.skins.util import SkinListingOrder, Currency 20 from eve.common.script.sys.idCheckers import IsCharacter, IsPlayerCorporation, IsAlliance, IsNPCCorporation 21 from stackless_response_router.exceptions import TimeoutException 22 logger = logging.getLogger(__name__) 23 _instance = None 24 _EXPIRY_WATCHDOG_FREQUENCY = 5 25 26 def get_ship_skin_trading_svc(): 27 global _instance 28 if _instance is None: 29 _instance = _ShipSkinTradingSvc() 30 return _instance 31 32 33 class _ShipSkinTradingSvc(object): 34 __startupdependencies__ = ['publicGatewaySvc'] 35 __notifyevents__ = ['OnSessionChanged', 'OnSkillsChanged'] 36 37 def __init__(self): 38 self._skin_listing_by_id = {} 39 self._cached_broker_fees_per_duration = {} 40 self._cached_broker_fees_listing_target = None 41 self._cached_tax_rate = None 42 self._cached_tax_rate_listing_target = None 43 self._cached_buyer_fees = {} 44 self._cached_buyer_fee_definitions = None 45 self._cached_buyer_fee_definitions_version = None 46 self._listing_expiry_watcher_thread = None 47 self._already_expired = [] 48 public_gateway = sm.GetService('publicGatewaySvc') 49 self._request_messenger = PublicShipSkinTradingRequestMessenger(public_gateway) 50 self._connect_signals() 51 self._listing_expiry_watcher_thread = uthread2.start_tasklet(self._watch_for_expired_listings) 52 sm.GetService('chat').add_paste_validator(ShipSkinListingLinkValidator()) 53 54 def __del__(self): 55 self._disconnect_signals() 56 if self._listing_expiry_watcher_thread: 57 self._listing_expiry_watcher_thread.kill() 58 self._listing_expiry_watcher_thread = None 59 60 def _connect_signals(self): 61 sm.RegisterNotify(self) 62 on_skin_hub_offers_availability_changed.connect(self._on_skin_hub_offers_availability_changed) 63 64 def _disconnect_signals(self): 65 sm.UnregisterNotify(self) 66 on_skin_hub_offers_availability_changed.disconnect(self._on_skin_hub_offers_availability_changed) 67 68 def OnSessionChanged(self, _isRemote, _sess, change): 69 if 'charid' in change or 'corpid' in change or 'allianceid' in change or 'corprole' in change: 70 self._clear_cache() 71 72 def OnSkillsChanged(self, *args): 73 self._cached_tax_rate = None 74 self._cached_tax_rate_listing_target = None 75 76 def _on_skin_hub_offers_availability_changed(self, *args): 77 logger.info('SKIN LICENSE TRADING - feature flag switched for SKIN trading availability, clearing cache') 78 self._clear_cache() 79 80 def _clear_cache(self): 81 self._skin_listing_by_id = {} 82 self._already_expired = [] 83 self._cached_broker_fees_listing_target = None 84 self._cached_broker_fees_per_duration = {} 85 self._cached_tax_rate = None 86 self._cached_tax_rate_listing_target = None 87 self._cached_buyer_fee_definitions = None 88 self._cached_buyer_fee_definitions_version = None 89 self._cached_buyer_fees = {} 90 on_skin_listing_cache_invalidated() 91 92 def _watch_for_expired_listings(self): 93 while True: 94 uthread2.Sleep(_EXPIRY_WATCHDOG_FREQUENCY) 95 for listing_id, listing in self._skin_listing_by_id.iteritems(): 96 if listing and listing.is_expired and listing_id not in self._already_expired: 97 logger.info('SKIN LICENSE TRADING - watchdog found expired listing %s (expired at %s)' % (listing_id, listing.valid_until)) 98 self._already_expired.append(listing_id) 99 on_skin_listing_expired(listing_id) 100 101 def get_broker_fees_per_duration(self, target_id = None): 102 try: 103 target_type = self.get_listing_target_type(target_id) 104 target = ListingTarget(target_type, target_id) 105 if len(self._cached_broker_fees_per_duration) > 0 and self._cached_tax_rate_listing_target and self._cached_broker_fees_listing_target == target: 106 return (self._cached_broker_fees_per_duration, None) 107 self._cached_broker_fees_listing_target = target 108 self._cached_broker_fees_per_duration = self._request_messenger.get_broker_fees_and_available_durations_request(self._cached_broker_fees_listing_target) 109 return (self._cached_broker_fees_per_duration, None) 110 except Exception as e: 111 logger.exception('exception in get_broker_fees_per_duration: %s' % e) 112 return ({}, ListingError.UNKNOWN) 113 114 def get_tax_rate(self, target_id = None): 115 try: 116 target_type = self.get_listing_target_type(target_id) 117 target = ListingTarget(target_type, target_id) 118 if self._cached_tax_rate is not None and self._cached_tax_rate_listing_target and self._cached_tax_rate_listing_target == target: 119 return (self._cached_tax_rate, None) 120 self._cached_tax_rate_listing_target = target 121 self._cached_tax_rate = self._request_messenger.get_tax_rate_request(self._cached_tax_rate_listing_target) 122 return (self._cached_tax_rate, None) 123 except Exception as e: 124 logger.exception('exception in get_tax_rate: %s' % e) 125 return (None, ListingError.UNKNOWN) 126 127 def get_predicted_buyer_fee(self, skin_hex, target_id = None): 128 try: 129 target_type = self.get_listing_target_type(target_id) 130 target = ListingTarget(target_type, target_id) 131 if skin_hex in self._cached_buyer_fees: 132 for cached_target, cached_buyer_fee in self._cached_buyer_fees[skin_hex].iteritems(): 133 if cached_target == target: 134 return (cached_buyer_fee, None) 135 136 else: 137 self._cached_buyer_fees[skin_hex] = {} 138 self._cached_buyer_fees[skin_hex][target] = self._request_messenger.get_buyer_fee_request(skin_hex, target) 139 return (self._cached_buyer_fees[skin_hex][target], None) 140 except Exception as e: 141 logger.exception('exception in get_predicted_buyer_fee: %s' % e) 142 return (None, ListingError.UNKNOWN) 143 144 def get_buyer_fee_for_listing(self, listing): 145 if listing is None: 146 return (None, ListingError.UNKNOWN) 147 try: 148 _, err = self._get_buyer_fees_definitions() 149 if err is not None: 150 return (None, err) 151 skin_data = get_ship_skin_data_svc().get_skin_data(listing.skin_hex) 152 if skin_data and skin_data.tier_level in self._cached_buyer_fee_definitions: 153 if listing.branded: 154 return (self._cached_buyer_fee_definitions[skin_data.tier_level].get(ListingBuyerFeeType.BRANDED_LISTING_BUYER_FEE, None), None) 155 else: 156 return (self._cached_buyer_fee_definitions[skin_data.tier_level].get(ListingBuyerFeeType.REGULAR_LISTING_BUYER_FEE, None), None) 157 except Exception as e: 158 logger.exception('exception in get_buyer_fee_for_listing: %s' % e) 159 return (None, ListingError.UNKNOWN) 160 161 def get_available_listing_durations(self): 162 self.get_broker_fees_per_duration() 163 return sorted(self._cached_broker_fees_per_duration.keys()) 164 165 def get_listing_target_type(self, type_id): 166 if type_id is None: 167 return ListingTargetType.PUBLIC 168 if IsCharacter(type_id): 169 return ListingTargetType.CHARACTER 170 if IsPlayerCorporation(type_id) or IsNPCCorporation(type_id): 171 return ListingTargetType.CORPORATION 172 if IsAlliance(type_id): 173 return ListingTargetType.ALLIANCE 174 return ListingTargetType.PUBLIC 175 176 def get_pagination_controller(self): 177 pagination_controller = SkinListingsPaginationController(get_page_method=self._fetch_page_from_server) 178 return pagination_controller 179 180 def get_owned_listings(self, listed_to = ListedTo.PUBLIC): 181 if not are_skin_hub_offers_enabled(): 182 logger.info('SKIN LICENSE TRADING - Skip fetching owned SKIN listings, feature currently disabled') 183 return [] 184 listings = self._request_messenger.get_all_owned_listings_request() 185 if listed_to == ListedTo.MY_CORP: 186 listings = [ l for l in listings if l.target.target_type == ListingTargetType.CORPORATION and l.target.target_id == session.corpid ] 187 elif listed_to == ListedTo.MY_ALLIANCE: 188 listings = [ l for l in listings if l.target.target_type == ListingTargetType.ALLIANCE and l.target.target_id == session.allianceid ] 189 elif listed_to == ListedTo.SPECIFIC: 190 listings = [ l for l in listings if l.target.target_type in (ListingTargetType.CHARACTER, ListingTargetType.CORPORATION, ListingTargetType.ALLIANCE) ] 191 return listings 192 193 def get_listing(self, listing_id): 194 if not are_skin_hub_offers_enabled(): 195 logger.info('SKIN LICENSE TRADING - Skip fetching SKIN listing, feature currently disabled') 196 return (None, ListingError.FEATURE_FLAG_OFF) 197 listing, error = self._request_messenger.get_listing_request(listing_id) 198 return (listing, error) 199 200 def purchase_listing(self, listing_id, quantity = 1): 201 if not are_skin_hub_offers_enabled(): 202 logger.info('SKIN LICENSE TRADING - Forbid SKIN listing purchase, feature currently disabled') 203 return ListingError.FEATURE_FLAG_OFF 204 if self._cached_buyer_fee_definitions_version is None: 205 _, err = self._get_buyer_fees_definitions(force_refresh=True) 206 if err is not None: 207 return err 208 try: 209 transaction_id, error = self._request_messenger.initiate_purchase_listing_request(listing_id, quantity, self._cached_buyer_fee_definitions_version) 210 if error is not None: 211 if error == ListingError.INVALID_VERSION: 212 self._cached_buyer_fee_definitions_version = None 213 return error 214 try: 215 transaction_result = get_ship_cosmetic_transaction_svc().process_transaction(transaction_id) 216 if transaction_result: 217 return 218 return ListingError.UNKNOWN 219 except TimeoutException as e: 220 logger.warning('transaction timed out for listing %s purchase' % listing_id) 221 return GenericError.TIMEOUT 222 223 except TimeoutException as e: 224 logger.warning('transaction timed out for initiating listing %s purchase' % listing_id) 225 return GenericError.TIMEOUT 226 227 def cancel_listing(self, listing_id): 228 try: 229 error = self._request_messenger.cancel_listing_request(listing_id) 230 if error is not None: 231 return error 232 self._clear_cache() 233 except TimeoutException as e: 234 logger.warning('transaction timed out for requesting listing {listing_id} cancellation'.format(listing_id=listing_id)) 235 return GenericError.TIMEOUT 236 237 def create_listing(self, currency, price, skin_hex, duration_in_seconds, quantity = 1, target_id = None): 238 if not are_skin_hub_offers_enabled(): 239 logger.info('SKIN LICENSE TRADING - Forbid SKIN listing creation, feature currently disabled') 240 return ListingError.FEATURE_FLAG_OFF 241 target_type = self.get_listing_target_type(target_id) 242 target = ListingTarget(target_type, target_id) 243 try: 244 transaction_id, error = self._request_messenger.initiate_place_listing_request(session.charid, currency, price, skin_hex, duration_in_seconds, quantity, target) 245 if error is not None: 246 return error 247 try: 248 transaction_result = get_ship_cosmetic_transaction_svc().process_transaction(transaction_id) 249 if transaction_result: 250 return 251 return ListingError.UNKNOWN 252 except TimeoutException as e: 253 logger.warning('transaction timed out for creating listing for skin_hex %s' % skin_hex) 254 return GenericError.TIMEOUT 255 256 except TimeoutException as e: 257 logger.warning('transaction timed out for initiating listing request for skin_hex %s' % skin_hex) 258 return GenericError.TIMEOUT 259 260 def admin_expire_listing(self, listing_id, minutes_to_expiry): 261 if session and session.role & ROLE_PROGRAMMER: 262 if minutes_to_expiry > 0: 263 timestamp = gametime.now() + datetime.timedelta(minutes=minutes_to_expiry) 264 self._request_messenger.set_expiration_request(listing_id, timestamp) 265 self._clear_cache() 266 267 def _fetch_page_from_server(self, ship_types_filter, order_by, ascending, listing_target_type, seller_membership_type, page_token, num_per_page): 268 self._skin_listing_by_id.clear() 269 if not are_skin_hub_offers_enabled(): 270 logger.info('SKIN LICENSE TRADING - Skip fetching SKIN listings, feature currently disabled') 271 return ([], None, None) 272 try: 273 listings, next_page_token, error = self._request_messenger.get_all_listings_request(ship_types_filter, order_by, ascending, listing_target_type, seller_membership_type, page_token, num_per_page) 274 except Exception as e: 275 logging.exception(e) 276 return ([], None, None) 277 278 if error: 279 logging.exception(error) 280 return ([], None, error) 281 for listing in listings: 282 self._skin_listing_by_id[listing.identifier] = listing 283 284 return (listings, next_page_token, None) 285 286 def _get_buyer_fees_definitions(self, force_refresh = False): 287 try: 288 if force_refresh or self._cached_buyer_fee_definitions is None: 289 self._cached_buyer_fee_definitions_version, self._cached_buyer_fee_definitions = self._request_messenger.get_buyer_fees_definition_request() 290 return (self._cached_buyer_fee_definitions, None) 291 except Exception as e: 292 logger.exception('exception in _get_buyer_fees_definitions: %s' % e) 293 return ({}, ListingError.UNKNOWN)