catalogue_svc.py (6239B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\store\client\targetaudience\catalogue_svc.py 2 from __future__ import absolute_import, division, print_function 3 import logging 4 from collections import namedtuple 5 import evetypes 6 import uthread 7 from caching.memoize import MemoizeWithLock 8 from imagecaching.client.api.error import ImageCacheError 9 from imagecaching.client.api.repository import get_repository 10 from imagecaching.client.const import user_cache_folder 11 from publicGateway.grpc.exceptions import GenericException 12 from stackless_response_router.exceptions import TimeoutException 13 from store.client.const import LOG_ENTRY_NAME 14 from store.client.targetaudience.const import MAX_OFFER_LIST_PAGES, OFFER_CACHE_TTL_MINUTES, OFFER_LIST_PAGE_SIZE, TAO_IMAGE_CACHE_FOLDER 15 from store.client.targetaudience.feature_flags import tao_enabled 16 from store.client.targetaudience.messenger.request_messenger import PublicTargetAudienceOfferRequestMessenger 17 logger = logging.getLogger(__name__) 18 CatalogueOffer = namedtuple('CatalogueOffer', ['offer_id', 'offer', 'thumbnail_res_path']) 19 _instance = None 20 21 @MemoizeWithLock(OFFER_CACHE_TTL_MINUTES) 22 def get_catalogue(): 23 return get_catalogue_svc().fetch_catalogue() 24 25 26 def get_catalogue_svc(): 27 global _instance 28 if not tao_enabled(): 29 logger.info('%s - target audience offers are disabled, returning no service', LOG_ENTRY_NAME) 30 return 31 if _instance is None: 32 _instance = _CatalogueSvc(request_messenger=PublicTargetAudienceOfferRequestMessenger(sm.GetService('publicGatewaySvc')), image_repository=get_repository(), service_manager=sm) 33 return _instance 34 35 36 def clear_catalogue_svc(): 37 global _instance 38 if _instance is not None: 39 _instance.shutdown() 40 _instance = None 41 get_catalogue.clear_memoized() 42 43 44 class _CatalogueSvc(object): 45 __startupdependencies__ = ['publicGatewaySvc'] 46 __notifyevents__ = ['OnSessionChanged'] 47 48 def __init__(self, request_messenger, image_repository, service_manager): 49 self._request_messenger = request_messenger 50 self._image_repository = image_repository 51 self._service_manager = service_manager 52 self._service_manager.RegisterNotify(self) 53 54 def shutdown(self): 55 self._service_manager.UnregisterNotify(self) 56 57 def OnSessionChanged(self, _is_remote, _session, change): 58 if 'userid' in change or 'charid' in change: 59 get_catalogue.clear_memoized() 60 61 def fetch_catalogue(self): 62 if not tao_enabled(): 63 logger.info('%s - target audience offers are disabled, returning an empty catalogue', LOG_ENTRY_NAME) 64 return [] 65 cache_folder = _thumbnail_cache_folder() 66 if cache_folder is None: 67 logger.info('%s - no logged-in user, returning an empty target audience catalogue', LOG_ENTRY_NAME) 68 return [] 69 offer_ids = self._list_offer_ids() 70 offers = self._get_offers(offer_ids) 71 catalogue = [] 72 for offer_id in offer_ids: 73 offer = offers.get(offer_id) 74 if offer is None: 75 continue 76 if not self._has_known_type_ids(offer_id, offer): 77 continue 78 catalogue.append(CatalogueOffer(offer_id=offer_id, offer=offer, thumbnail_res_path=self._cache_thumbnail(cache_folder, offer_id, offer))) 79 80 self._prune_thumbnails(cache_folder, [ x.offer_id for x in catalogue ]) 81 logger.info('%s - target audience catalogue fetched: %s of %s offers usable', LOG_ENTRY_NAME, len(catalogue), len(offer_ids)) 82 return catalogue 83 84 def _list_offer_ids(self): 85 offer_ids = [] 86 page_token = None 87 for _page in range(MAX_OFFER_LIST_PAGES): 88 page_offer_ids, page_token = self._request_messenger.list_offers_request(page_size=OFFER_LIST_PAGE_SIZE, page_token=page_token) 89 offer_ids.extend(page_offer_ids) 90 if page_token is None: 91 break 92 else: 93 logger.error('%s - target audience offers pages exceed the maximum amount of %s pages', LOG_ENTRY_NAME, MAX_OFFER_LIST_PAGES) 94 95 return offer_ids 96 97 def _get_offers(self, offer_ids): 98 offers = {} 99 calls = [ (self._get_offer, (offer_id, offers)) for offer_id in offer_ids ] 100 if calls: 101 uthread.parallel(calls) 102 return offers 103 104 def _get_offer(self, offer_id, offers): 105 try: 106 offer = self._request_messenger.get_offer_request(offer_id) 107 except (GenericException, TimeoutException): 108 logger.exception('%s - could not fetch target audience offer %s', LOG_ENTRY_NAME, offer_id) 109 return 110 111 if offer is not None: 112 offers[offer_id] = offer 113 114 @staticmethod 115 def _has_known_type_ids(offer_id, offer): 116 for type_id in offer.get_type_ids(): 117 if not evetypes.Exists(type_id): 118 logger.warning('%s - dropping target audience offer %s, it grants unknown item type id %s', LOG_ENTRY_NAME, offer_id, type_id) 119 return False 120 121 return True 122 123 def _cache_thumbnail(self, cache_folder, offer_id, offer): 124 if not offer.thumbnail_url: 125 return None 126 try: 127 return self._image_repository.cache_image(_image_id(offer_id), offer.thumbnail_url, _thumbnail_cache_path(cache_folder, offer_id)) 128 except ImageCacheError: 129 logger.exception('%s - could not cache the thumbnail of target audience offer %s', LOG_ENTRY_NAME, offer_id) 130 return None 131 132 def _prune_thumbnails(self, cache_folder, offer_ids): 133 deleted_image_ids = self._image_repository.prune_folder(cache_folder, [ _image_id(x) for x in offer_ids ]) 134 if deleted_image_ids: 135 logger.info('%s - pruned %s stale target audience thumbnails', LOG_ENTRY_NAME, len(deleted_image_ids)) 136 137 138 def _image_id(offer_id): 139 return u'%s' % offer_id.catalogue_entry_name 140 141 142 def _thumbnail_cache_folder(): 143 user_id = getattr(session, 'userid', None) 144 if user_id is None: 145 return 146 return user_cache_folder(user_id, TAO_IMAGE_CACHE_FOLDER) 147 148 149 def _thumbnail_cache_path(cache_folder, offer_id): 150 return u'%s/%s' % (cache_folder, _image_id(offer_id))