shipSkinComponentSvc.py (16835B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\cosmetics\client\shipSkinComponentSvc.py 2 import logging 3 import random 4 import evetypes 5 import uthread 6 import uthread2 7 from carbon.common.script.sys.serviceConst import ROLE_PROGRAMMER 8 from cosmetics.client.messengers.cosmetics.ship.shipSkinComponentLicenseNoticeMessenger import PublicShipComponentLicenseNoticeMessenger 9 from cosmetics.client.messengers.cosmetics.ship.shipSkinComponentLicenseRequestMessenger import PublicShipComponentLicenseRequestMessenger 10 from cosmetics.client.ships import ship_skin_signals, ship_skin_svc_signals 11 from cosmetics.client.ships.skins.errors import ConsumeComponentItemConflict 12 from cosmetics.client.ships.skins.live_data.component_license import ComponentLicense 13 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 14 from cosmetics.common.ships.skins.static_data.component import ComponentsDataLoader 15 from cosmetics.common.ships.skins.static_data.slot import SlotsDataLoader 16 from inventorycommon.const import groupShipSkinDesignComponents 17 from localization import GetByLabel 18 from stackless_response_router.exceptions import TimeoutException 19 _instance = None 20 logger = logging.getLogger(__name__) 21 _POPULATE_CACHE_TIMEOUT_IN_SECONDS = 5 22 _POPULATE_CACHE_INCREMENT_IN_SECONDS = 0.1 23 _MAX_CONCURRENT_COMPONENT_ITEM_ACTIVATIONS = 10 24 25 def get_ship_skin_component_svc(): 26 global _instance 27 if _instance is None: 28 _instance = _ShipSkinComponentSvc() 29 return _instance 30 31 32 class _ShipSkinComponentSvc(object): 33 __startupdependencies__ = ['publicGatewaySvc'] 34 __notifyevents__ = ['OnSessionChanged'] 35 36 def __init__(self): 37 self._licenses_by_element_type = {} 38 self._owned_licenses_already_fetched = False 39 self._owned_licenses_is_fetching = False 40 self._items_being_consumed = set() 41 self._components_flagged_as_new = set() 42 public_gateway = sm.GetService('publicGatewaySvc') 43 self._request_messenger = PublicShipComponentLicenseRequestMessenger(public_gateway) 44 self._notice_messenger = PublicShipComponentLicenseNoticeMessenger(public_gateway) 45 sm.RegisterNotify(self) 46 ship_skin_svc_signals.on_component_license_granted_internal.connect(self._on_component_license_granted_internal) 47 48 def __del__(self): 49 ship_skin_svc_signals.on_component_license_granted_internal.disconnect(self._on_component_license_granted_internal) 50 51 def OnSessionChanged(self, _isRemote, _sess, change): 52 if 'charid' in change: 53 self.clear_cache() 54 self._items_being_consumed = set() 55 56 def _check_populate_cache(self, force_refresh = False): 57 if not self._owned_licenses_already_fetched or force_refresh: 58 self._populate_cache() 59 60 def clear_cache(self, licenses_to_flush = None): 61 if licenses_to_flush is None: 62 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - flushing all component licenses cache') 63 self._licenses_by_element_type = {} 64 self._components_flagged_as_new.clear() 65 elif len(licenses_to_flush) > 0: 66 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - flushing cache for component licenses for components %s' % licenses_to_flush) 67 for component_id in licenses_to_flush: 68 if component_id in self._licenses_by_element_type: 69 self._licenses_by_element_type.pop(component_id) 70 71 else: 72 return 73 self._owned_licenses_already_fetched = False 74 ship_skin_signals.on_component_license_cache_invalidated(licenses_to_flush or []) 75 76 def _populate_cache(self): 77 if self._owned_licenses_is_fetching: 78 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - cache is already being populated, waiting for results') 79 timeout = 0 80 while self._owned_licenses_is_fetching: 81 if timeout >= _POPULATE_CACHE_TIMEOUT_IN_SECONDS: 82 raise TimeoutException 83 timeout += _POPULATE_CACHE_INCREMENT_IN_SECONDS 84 uthread2.Sleep(_POPULATE_CACHE_INCREMENT_IN_SECONDS) 85 86 return 87 try: 88 self._owned_licenses_is_fetching = True 89 self._licenses_by_element_type = {} 90 component_ids_per_license_type = self._request_messenger.get_owned_licenses_request() 91 self._get_licenses_parallel(component_ids_per_license_type) 92 self._owned_licenses_already_fetched = True 93 finally: 94 self._owned_licenses_is_fetching = False 95 96 def _get_licenses_parallel(self, component_ids_per_license_type): 97 ComponentsDataLoader.load_components_data() 98 parallel_calls = [] 99 for license_type, component_ids in component_ids_per_license_type.iteritems(): 100 for component_id in component_ids: 101 component_data = ComponentsDataLoader.get_component_data(component_id) 102 if component_data: 103 parallel_calls.append((self.get_license, (component_id, component_data.category, license_type))) 104 else: 105 logger.warning('SKIN COMPONENT LICENSES MANAGEMENT - could not find component data for component %s' % component_id) 106 107 uthread.parallel(parallel_calls) if len(parallel_calls) > 0 else [] 108 109 def _cache_license(self, component_id, license_type, license): 110 if license_type == ComponentLicenseType.LIMITED and license is not None and license.remaining_license_uses == 0: 111 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - %s %s not cached, bound license with 0 remaining uses' % (license_type, component_id)) 112 return 113 if component_id not in self._licenses_by_element_type: 114 self._licenses_by_element_type[component_id] = {} 115 self._licenses_by_element_type[component_id][license_type] = license 116 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - cached %s %s' % (license_type, component_id)) 117 118 def get_license(self, component_id, component_type, license_type, force_refresh = False): 119 if license_type == ComponentLicenseType.LIMITED: 120 return self.get_bound_license(component_id, component_type, force_refresh) 121 elif license_type == ComponentLicenseType.UNLIMITED: 122 return self.get_unbound_license(component_id, component_type, force_refresh) 123 else: 124 return None 125 126 def get_all_owned_licenses(self): 127 self._check_populate_cache() 128 licenses = [] 129 for component_id, license_types in self._licenses_by_element_type.iteritems(): 130 for license_type, license in license_types.iteritems(): 131 if license: 132 licenses.append(license) 133 134 return licenses 135 136 def get_all_owned_licenses_in_category(self, category): 137 licenses = self.get_all_owned_licenses() 138 licenses = [ x for x in licenses if x is not None and x.get_component_data().category == category ] 139 return licenses 140 141 def get_owned_licenses_by_type(self, component_id): 142 self._check_populate_cache() 143 return self._licenses_by_element_type.get(component_id, {}) 144 145 def get_bound_license(self, component_id, component_type, force_refresh = False): 146 if not force_refresh and component_id in self._licenses_by_element_type and ComponentLicenseType.LIMITED in self._licenses_by_element_type[component_id]: 147 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - returning cached license for %s %s' % (ComponentLicenseType.LIMITED, component_id)) 148 return self._licenses_by_element_type[component_id][ComponentLicenseType.LIMITED] 149 license = self._request_messenger.get_finite_license_request(session.charid, component_id, component_type) 150 self._cache_license(component_id, ComponentLicenseType.LIMITED, license) 151 return self._licenses_by_element_type.get(component_id, {}).get(ComponentLicenseType.LIMITED, None) 152 153 def get_unbound_license(self, component_id, component_type, force_refresh = False): 154 if not force_refresh and component_id in self._licenses_by_element_type and ComponentLicenseType.UNLIMITED in self._licenses_by_element_type[component_id]: 155 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - returning cached license for %s %s' % (ComponentLicenseType.UNLIMITED, component_id)) 156 return self._licenses_by_element_type[component_id][ComponentLicenseType.UNLIMITED] 157 license = self._request_messenger.get_infinite_license_request(session.charid, component_id, component_type) 158 self._cache_license(component_id, ComponentLicenseType.UNLIMITED, license) 159 return self._licenses_by_element_type.get(component_id, {}).get(ComponentLicenseType.UNLIMITED, None) 160 161 def get_default_license_to_use(self, component_id, component_type): 162 license = self.get_unbound_license(component_id, component_type) 163 if license: 164 return license 165 return self.get_bound_license(component_id, component_type) 166 167 def consume_component_items(self, items): 168 if len(self._items_being_consumed) > 0: 169 return GetByLabel('UI/Personalization/ShipSkins/DesignComponents/UnableToActivateComponentsTooFast') 170 if len(items) > _MAX_CONCURRENT_COMPONENT_ITEM_ACTIVATIONS: 171 return GetByLabel('UI/Personalization/ShipSkins/DesignComponents/UnableToActivateTooManyComponents', max_designs=_MAX_CONCURRENT_COMPONENT_ITEM_ACTIVATIONS) 172 error_msg = None 173 item_ids_to_consume = set([ item_id for item_id, _, _ in items ]) 174 item_ids_to_consume = item_ids_to_consume.difference(self._items_being_consumed) 175 self._items_being_consumed.update(item_ids_to_consume) 176 items_to_consume = [ (item_id, type_id, qty) for item_id, type_id, qty in items if item_id in item_ids_to_consume ] 177 try: 178 first_exception = None 179 for item in items_to_consume: 180 try: 181 itemID, typeID, quantity = item 182 self._consume_component_item(itemID, typeID, quantity) 183 except Exception as e: 184 if first_exception is None: 185 first_exception = e 186 187 if first_exception: 188 if isinstance(first_exception, ConsumeComponentItemConflict): 189 error_msg = GetByLabel('UI/Personalization/ShipSkins/DesignComponents/FailedToConsumeSKINDesignComponentItemAlreadyOwned') 190 else: 191 error_msg = GetByLabel('UI/Personalization/ShipSkins/DesignComponents/FailedToConsumeSKINDesignComponentItem') 192 finally: 193 for item_id in item_ids_to_consume: 194 self._items_being_consumed.remove(item_id) 195 196 return error_msg 197 198 def get_random_component_license(self, slot_id): 199 component_data = self.get_random_component_data(slot_id) 200 if not component_data: 201 return None 202 bound_license = self.get_bound_license(component_id=component_data.component_id, component_type=component_data.category) 203 unbound_license = self.get_unbound_license(component_id=component_data.component_id, component_type=component_data.category) 204 if unbound_license: 205 return unbound_license 206 if bound_license: 207 return bound_license 208 209 def get_random_component_data(self, slot_id, only_owned = True): 210 allowed_components = SlotsDataLoader.get_allowed_component(slot_id).values() 211 if only_owned: 212 owned_licenses = self.get_all_owned_licenses() 213 owned_ids = {l.component_id for l in owned_licenses} 214 components = [ c for c in allowed_components if c.component_id in owned_ids and c.published == True ] 215 else: 216 components = [ c for c in allowed_components if c and c.published == True ] 217 components = sorted(components, key=lambda component: component.color_shade_sort_index, reverse=False) 218 if len(components) == 0: 219 return None 220 component_data = random.choice(components) 221 return component_data 222 223 def _consume_component_item(self, item_id, type_id, quantity): 224 group_id = evetypes.GetGroupID(type_id) 225 if group_id == groupShipSkinDesignComponents: 226 if self._is_license_type(type_id, ComponentLicenseType.UNLIMITED): 227 quantity = 1 228 self._request_messenger.consume_request(item_id, quantity) 229 if self._is_license_type(type_id, ComponentLicenseType.LIMITED): 230 component_id = self._get_component_id_by_type(type_id) 231 if component_id: 232 self.clear_cache([component_id]) 233 else: 234 raise Exception('item %s must be of group %s to be consumed for skin component licenses' % (item_id, group_id)) 235 236 def _is_license_type(self, type_id, license_type): 237 component_data = ComponentsDataLoader.get_component_for_component_type(type_id) 238 if component_data: 239 return component_data.get_license_type(type_id) == license_type 240 return False 241 242 def _get_component_id_by_type(self, type_id): 243 component_data = ComponentsDataLoader.get_component_for_component_type(type_id) 244 if component_data: 245 return component_data.component_id 246 247 def admin_grant_license(self, component_id, license_type, component_type, license_count = None): 248 if session and session.role & ROLE_PROGRAMMER: 249 if license_type == ComponentLicenseType.LIMITED: 250 self._request_messenger.admin_grant_finite_request(component_id, component_type, license_count) 251 elif license_type == ComponentLicenseType.UNLIMITED: 252 self._request_messenger.admin_grant_infinite_request(component_id, component_type) 253 self.clear_cache([component_id]) 254 255 def admin_revoke_license(self, component_id, license_type, component_type): 256 if session and session.role & ROLE_PROGRAMMER: 257 if license_type == ComponentLicenseType.LIMITED: 258 self._request_messenger.admin_revoke_finite_request(session.charid, component_id, component_type) 259 elif license_type == ComponentLicenseType.UNLIMITED: 260 self._request_messenger.admin_revoke_infinite_request(session.charid, component_id, component_type) 261 self.clear_cache([component_id]) 262 263 def set_component_is_new_flag(self, component_id, is_new): 264 if is_new: 265 self._components_flagged_as_new.add(component_id) 266 else: 267 self._components_flagged_as_new.discard(component_id) 268 269 def is_component_new(self, component_id): 270 return component_id in self._components_flagged_as_new 271 272 def _on_component_license_granted_internal(self, component_id, quantity): 273 component_data = ComponentsDataLoader.get_component_data(component_id) 274 if component_data: 275 license_type = ComponentLicenseType.LIMITED if quantity is not None else ComponentLicenseType.UNLIMITED 276 license = self.get_license(component_id, component_data.category, license_type, force_refresh=True) 277 self._set_component_is_new_flag(component_data, license, quantity) 278 logger.info('SKIN COMPONENT LICENSES MANAGEMENT - license %s granted' % license) 279 ship_skin_signals.on_component_license_granted(component_id, license_type, quantity) 280 else: 281 raise Exception('could not find component data for component %s' % component_id) 282 283 def _set_component_is_new_flag(self, component_data, license, quantity): 284 if license.license_type == ComponentLicenseType.UNLIMITED: 285 existing_limited_license = self.get_license(component_data.component_id, component_data.category, ComponentLicenseType.LIMITED) 286 if not existing_limited_license: 287 self.set_component_is_new_flag(component_data.component_id, is_new=True) 288 elif license.license_type == ComponentLicenseType.LIMITED: 289 existing_unlimited_license = self.get_license(component_data.component_id, component_data.category, ComponentLicenseType.UNLIMITED) 290 if not existing_unlimited_license: 291 is_first_grant = license.remaining_license_uses - quantity == 0 292 if is_first_grant: 293 self.set_component_is_new_flag(component_data.component_id, is_new=is_first_grant) 294 295 def has_existing_license(self, component_id): 296 component_data = ComponentsDataLoader.get_component_data(component_id) 297 bound_license = self.get_bound_license(component_data.component_id, component_data.category) 298 unbound_license = self.get_unbound_license(component_data.component_id, component_data.category) 299 return bound_license is not None or unbound_license is not None