NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

channel_controller.py (19507B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\chat\client\local_chat\channel_controller.py
      2 import datetime
      3 from carbon.common.script.sys.serviceConst import ROLE_CHTINVISIBLE
      4 import sentry_sdk
      5 from collections import deque
      6 import httplib
      7 import gametime
      8 import logging
      9 import localization
     10 import uthread2
     11 import uuid
     12 from inventorycommon.const import ownerSystem
     13 from eve.common.script.sys.idCheckers import IsEvePlayerCharacter
     14 from eveprefs import prefs
     15 from chat.common.util import is_local_chat_suppressed, is_local_chat_delayed
     16 from chat.client.channel_controller import BaseChatChannelController
     17 from chat.client.const import IDLE_MEMBER_REMOVAL_TIME
     18 from .messenger import LocalChatMessenger, on_exit_notice, on_message_broadcast_notice, on_membership_list_notice, on_membership_refreshed_notice, on_join_notice
     19 from ..hide_messages import open_hide_message_settings
     20 from ..util import should_hide_message
     21 WAIT_ON_MEMBERSHIP_NOTICE_SECONDS = 4
     22 ENSURE_MEMBERS_MAX_DELAY = 60
     23 logger = logging.getLogger('chat')
     24 
     25 class LocalChatChannelController(BaseChatChannelController):
     26     __notifyevents__ = ['OnSessionChanged', 'ProcessSessionReset']
     27 
     28     def __init__(self, *args, **kwargs):
     29         self._character_id = None
     30         self._solar_system_id = None
     31         self._detailed_channel_id = None
     32         self._is_suppressed = True
     33         self._is_delayed = False
     34         self._initial_members_received = False
     35         self._member_request_tasklet = None
     36         self._member_activity_timestamp = {}
     37         self._member_activity_queue = deque()
     38         self._member_activity_tasklet = None
     39         self._ensure_membership_retry_count = 0
     40         self._ensure_membership_retry_start_time = None
     41         self._pending_member_data = {}
     42         self._pending_member_data_timestamp = None
     43         self._messenger = LocalChatMessenger.get_instance(sm.GetService('publicGatewaySvc'))
     44         super(LocalChatChannelController, self).__init__(*args, **kwargs)
     45         if session.charid:
     46             uthread2.start_tasklet(self._update_character_and_system, True)
     47 
     48     def close(self):
     49         super(LocalChatChannelController, self).close()
     50         self._messenger = None
     51         self._initial_members_received = True
     52         self._kill_member_request_tasklet()
     53 
     54     def _register(self):
     55         super(LocalChatChannelController, self)._register()
     56         on_message_broadcast_notice.connect(self._on_message_broadcast_notice)
     57         on_exit_notice.connect(self._on_exit_notice)
     58         on_membership_list_notice.connect(self._on_membership_list_notice)
     59         on_membership_refreshed_notice.connect(self._on_membership_refreshed_notice)
     60         on_join_notice.connect(self._on_join_notice)
     61 
     62     def _unregister(self):
     63         super(LocalChatChannelController, self)._unregister()
     64         on_message_broadcast_notice.disconnect(self._on_message_broadcast_notice)
     65         on_exit_notice.disconnect(self._on_exit_notice)
     66         on_membership_list_notice.disconnect(self._on_membership_list_notice)
     67         on_membership_refreshed_notice.disconnect(self._on_membership_refreshed_notice)
     68         on_join_notice.disconnect(self._on_join_notice)
     69 
     70     def ProcessSessionReset(self):
     71         if self._is_closing:
     72             return
     73         uthread2.start_tasklet(self._update_character_and_system, False)
     74 
     75     def OnSessionChanged(self, is_remote, session, change):
     76         if self._is_closing:
     77             return
     78         if 'charid' in change:
     79             if self._character_id != session.charid:
     80                 self._update_character_id(ensure_members=True)
     81             else:
     82                 self.chat_service.open_channel_window(self.channel_id)
     83         if 'solarsystemid2' in change:
     84             self._update_system_id(ensure_members='charid' not in change)
     85 
     86     def _update_character_and_system(self, ensure_members):
     87         self._update_character_id(ensure_members=ensure_members)
     88         self._update_system_id(ensure_members=False)
     89 
     90     def _update_character_id(self, ensure_members):
     91         if self._character_id == session.charid:
     92             return
     93         self._character_id = session.charid
     94         self._kill_member_request_tasklet()
     95         self._clear_members()
     96         self.clear_messages()
     97         self._close_logfile()
     98         if self._character_id:
     99             self._setup_log_file()
    100             if ensure_members:
    101                 self._ensure_members_for_new_solar_system(wait_time=1)
    102                 self.chat_service.open_channel_window(self.channel_id)
    103 
    104     def _update_system_id(self, ensure_members):
    105         if self._solar_system_id == session.solarsystemid2:
    106             return
    107         self._clear_member_activity()
    108         self._solar_system_id = session.solarsystemid2
    109         self._detailed_channel_id = '{}_{}'.format(self._channel_id, self._solar_system_id)
    110         self._is_suppressed = self._solar_system_id is None or is_local_chat_suppressed(self._solar_system_id)
    111         self._is_delayed = self._solar_system_id is not None and is_local_chat_delayed(self._solar_system_id)
    112         if not self._solar_system_id:
    113             return
    114         if self._is_delayed:
    115             self._member_activity_tasklet = uthread2.start_tasklet(self._remove_inactive_members_routine)
    116         self._clear_members()
    117         self._add_system_changed_message()
    118         self._add_invisible_role_message()
    119         added_pending = self._process_pending_member_data()
    120         if ensure_members and not added_pending:
    121             self._ensure_members_for_new_solar_system()
    122 
    123     @property
    124     def window_display_name(self):
    125         return localization.GetByLabel('UI/Chat/Local')
    126 
    127     @property
    128     def member_count(self):
    129         if self._is_suppressed or self._is_delayed:
    130             return 0
    131         return super(LocalChatChannelController, self).member_count
    132 
    133     @property
    134     def detailed_channel_id(self):
    135         return self._detailed_channel_id
    136 
    137     def set_members(self, members):
    138         if self._is_suppressed:
    139             return
    140         super(LocalChatChannelController, self).set_members(members)
    141 
    142     def add_member(self, character_id, character_info = None):
    143         if self._is_suppressed:
    144             return
    145         super(LocalChatChannelController, self).add_member(character_id, character_info)
    146 
    147     def send_chat_message(self, text):
    148         if self._is_suppressed:
    149             return None
    150         message_id = gametime.GetWallclockTime()
    151         self._add_to_messages(session.charid, text)
    152         self._add_pending_message(message_id)
    153 
    154         def _response_handler(payload, response):
    155             if response is None:
    156                 self._remove_pending_message(message_id, was_delivered=False)
    157             elif response.status_code != httplib.OK:
    158                 logger.error('LocalChat BroadcastMessageResponse returned with statusCode {}, statusMessage: {}'.format(response.status_code, response.status_message))
    159                 self._remove_pending_message(message_id, was_delivered=False)
    160             else:
    161                 self._remove_pending_message(message_id, was_delivered=True)
    162 
    163         def _timeout_handler(*args, **kwargs):
    164             self._remove_pending_message(message_id, was_delivered=False)
    165 
    166         self._messenger.broadcast_message(text, _response_handler, _timeout_handler)
    167         return message_id
    168 
    169     def should_hide_message(self, text):
    170         return should_hide_message(self.detailed_channel_id, self.category_id, text)
    171 
    172     def open_hide_message_settings(self):
    173         open_hide_message_settings(category_id=self.category_id, channel_id=self.detailed_channel_id, channel_name=self.channel_id)
    174 
    175     def _on_message_broadcast_notice(self, solar_system_id, sender_id, message_content):
    176         if self._is_closing or self._is_suppressed:
    177             return
    178         if solar_system_id != session.solarsystemid2:
    179             logger.warn('LocalChat on_message_broadcast_notice got bad solar_system_id=%d', solar_system_id)
    180             return
    181         if self._character_id != session.charid or session.charid is None:
    182             return
    183         self._update_member_activity(sender_id)
    184         if sender_id != session.charid:
    185             self.message_received(sender_id, message_content)
    186 
    187     def _on_membership_list_notice(self, solar_system_id, members):
    188         if self._is_closing:
    189             return
    190         if solar_system_id != session.solarsystemid2:
    191             logger.warn('LocalChat _on_membership_notice received bad solar_system_id=%d', solar_system_id)
    192             self._pending_member_data[solar_system_id] = members
    193             self._pending_member_data_timestamp = gametime.GetWallclockTime()
    194             return
    195         self._update_member_list(members)
    196 
    197     def _update_member_list(self, members):
    198         if self._is_closing:
    199             return
    200         chars_missing_corp_id = []
    201         for member_id, member_data in members.iteritems():
    202             if member_data['corporation_id'] == 0:
    203                 chars_missing_corp_id.append(member_id)
    204 
    205         if len(chars_missing_corp_id) > 0:
    206             charSvc = sm.RemoteSvc('charMgr')
    207             fallback_data = charSvc.GetOrganizationInfoForCharacters(chars_missing_corp_id)
    208             for char_id, data in fallback_data.iteritems():
    209                 members[char_id]['corporation_id'] = data.corporationID
    210                 members[char_id]['alliance_id'] = data.allianceID
    211                 members[char_id]['war_faction_id'] = data.warFactionID
    212 
    213             with sentry_sdk.configure_scope() as scope:
    214                 scope.set_tag('solar_system_id', session.solarsystemid2)
    215                 logger.exception('LocalChat _update_member_list received members with missing corporation_id. Requesting fallback data.', extra={'character_ids': chars_missing_corp_id,
    216                  'fallback_data': fallback_data})
    217                 scope.remove_tag('solar_system_id')
    218         self._update_character_id(ensure_members=False)
    219         self._update_system_id(ensure_members=False)
    220         if session.charid is None:
    221             return
    222         self._initial_members_received = True
    223         self.set_members(members)
    224 
    225     def _on_membership_refreshed_notice(self, solar_system_id, member_id, member_data):
    226         if self._is_closing:
    227             return
    228         if solar_system_id != session.solarsystemid2:
    229             logger.warn('LocalChat _on_membership_refreshed_notice received bad solar_system_id=%d', solar_system_id)
    230             if solar_system_id in self._pending_member_data and member_id in self._pending_member_data[solar_system_id]:
    231                 self._pending_member_data[solar_system_id][member_id] = member_data
    232             return
    233         if not self._is_delayed and member_id not in self._member_ids:
    234             return
    235         self.chat_service.update_members_info({member_id: member_data}, signal=True)
    236 
    237     def _on_join_notice(self, solar_system_id, character_id, character_info):
    238         if self._is_closing:
    239             return
    240         if solar_system_id != session.solarsystemid2:
    241             logger.warn('LocalChat _on_join_notice got bad solar_system_id=%d', solar_system_id)
    242             if solar_system_id in self._pending_member_data:
    243                 self._pending_member_data[solar_system_id][character_id] = character_info
    244             return
    245         if self._character_id != session.charid or session.charid is None:
    246             return
    247         self.add_member(character_id, character_info)
    248 
    249     def _on_exit_notice(self, solar_system_id, character_id):
    250         if self._is_closing:
    251             return
    252         if solar_system_id != session.solarsystemid2:
    253             logger.warn('LocalChat _on_exit_notice got bad solar_system_id=%d', solar_system_id)
    254             if solar_system_id in self._pending_member_data:
    255                 self._pending_member_data[solar_system_id].pop(character_id, None)
    256             return
    257         if self._character_id != session.charid or session.charid is None:
    258             return
    259         self.remove_member(character_id)
    260 
    261     def _process_pending_member_data(self):
    262         added = False
    263         if self._solar_system_id in self._pending_member_data and gametime.GetSecondsSinceWallclockTime(self._pending_member_data_timestamp) < 5:
    264             added = True
    265             self._update_member_list(self._pending_member_data[self._solar_system_id])
    266         self._pending_member_data.clear()
    267         self._pending_member_data_timestamp = None
    268         return added
    269 
    270     def _ensure_members_for_new_solar_system(self, wait_time = None):
    271         self._initial_members_received = False
    272         self._kill_member_request_tasklet()
    273         self._member_request_tasklet = uthread2.StartTasklet(self._ensure_members_for_new_solar_system_tasklet, WAIT_ON_MEMBERSHIP_NOTICE_SECONDS if wait_time is None else wait_time)
    274 
    275     def _ensure_members_for_new_solar_system_tasklet(self, wait_time = None):
    276         if wait_time:
    277             uthread2.sleep(wait_time)
    278         if self._is_closing or session.charid is None or self._initial_members_received:
    279             return
    280         logger.info('LocalChat timeout waiting on membership notice for solar_system_id=%d', session.solarsystemid2)
    281         self._messenger.request_solar_system_membership(self._ensure_members_membership_response_callback, self._ensure_membership_timeout_callback)
    282 
    283     def _ensure_members_membership_response_callback(self, payload, response):
    284         if self._is_closing or self._initial_members_received or response is None:
    285             return
    286         if response.status_code != 200:
    287             logger.error('LocalChat error in response for solar system membership request', extra={'status_code': response.status_code,
    288              'status_message': response.status_message})
    289             return
    290         self._ensure_membership_retry_count = 0
    291         self._ensure_membership_retry_start_time = None
    292         solar_system_id = payload.solar_system.sequential
    293         members = self._messenger.parse_members_from_payload(payload)
    294         self._on_membership_list_notice(solar_system_id, members)
    295 
    296     def _ensure_membership_timeout_callback(self, error, request_primitive):
    297         if self._is_closing or self._initial_members_received:
    298             return
    299         public_gateway_service = sm.GetService('publicGatewaySvc')
    300         logger.error('LocalChat request for membership list timed out', extra={'error': error,
    301          'request_broker_details': public_gateway_service.get_request_broker_details(),
    302          'notice_consumer_details': public_gateway_service.get_notice_consumer_details(),
    303          'public_gateway_svc_uptime_ms': public_gateway_service.get_gateway_uptime_ms(),
    304          'request_correlation_uuid': str(uuid.UUID(bytes=request_primitive.correlation_uuid)) if request_primitive else ''})
    305         if self._ensure_membership_retry_start_time is None:
    306             self._ensure_membership_retry_start_time = datetime.datetime.now()
    307 
    308         def get_wait_time(retry_count):
    309             if retry_count <= 1:
    310                 return WAIT_ON_MEMBERSHIP_NOTICE_SECONDS
    311             return min(ENSURE_MEMBERS_MAX_DELAY, get_wait_time(retry_count - 1) * 2)
    312 
    313         if datetime.datetime.now() - self._ensure_membership_retry_start_time < datetime.timedelta(minutes=5):
    314             self._ensure_membership_retry_count += 1
    315             self._ensure_members_for_new_solar_system(wait_time=get_wait_time(self._ensure_membership_retry_count))
    316         else:
    317             logger.error('LocalChat failed to get membership list after {} retries'.format(self._ensure_membership_retry_count))
    318             self._ensure_membership_retry_start_time = None
    319             self._ensure_membership_retry_count = 0
    320 
    321     def _add_system_changed_message(self):
    322         if self._is_suppressed:
    323             system_name = localization.GetByLabel('UI/Chat/Unknown')
    324         else:
    325             system_name = cfg.evelocations.Get(session.solarsystemid2).name
    326         channel_name = localization.GetByLabel('UI/Chat/FeatureChannelName', featureType=localization.GetByLabel('UI/Chat/Local'), featureName=system_name)
    327         self.add_game_message(localization.GetByLabel('UI/Chat/ChannelWindow/ChannelChangedToChannelName', channelName=channel_name), sender_id=ownerSystem)
    328 
    329     def _add_invisible_role_message(self):
    330         if session.role & ROLE_CHTINVISIBLE == ROLE_CHTINVISIBLE:
    331             self.add_game_message(localization.GetByLabel('UI/Chat/InvisibleWarning', color='#ffffff00'), sender_id=ownerSystem)
    332 
    333     def _update_member_activity(self, member_id):
    334         if not IsEvePlayerCharacter(member_id):
    335             return
    336         self.add_member(member_id)
    337         if self._is_delayed:
    338             timestamp = gametime.GetWallclockTime()
    339             self._member_activity_timestamp[member_id] = timestamp
    340             self._member_activity_queue.append((timestamp, member_id))
    341 
    342     def _remove_inactive_members_routine(self):
    343         while self._is_delayed:
    344             now = gametime.GetWallclockTime()
    345             inactive_member_ids = []
    346             max_activity_seconds = prefs.GetValue('chat_idle_character_removal_period', IDLE_MEMBER_REMOVAL_TIME)
    347             sleep_duration = max_activity_seconds
    348             while len(self._member_activity_queue) > 0:
    349                 activity_timestamp, member_id = self._member_activity_queue[0]
    350                 if self._member_activity_timestamp[member_id] != activity_timestamp:
    351                     self._member_activity_queue.popleft()
    352                     continue
    353                 time_since_activity = gametime.GetTimeDiff(activity_timestamp, now) / gametime.SEC
    354                 if time_since_activity > max_activity_seconds:
    355                     self._member_activity_queue.popleft()
    356                     inactive_member_ids.append(member_id)
    357                 else:
    358                     sleep_duration = max_activity_seconds - time_since_activity + 1
    359                     break
    360 
    361             for character_id in inactive_member_ids:
    362                 self.remove_member(character_id)
    363 
    364             uthread2.sleep(sleep_duration)
    365 
    366     def _kill_member_request_tasklet(self):
    367         if self._member_request_tasklet:
    368             self._member_request_tasklet.kill()
    369             self._member_request_tasklet = None
    370 
    371     def _clear_member_activity(self):
    372         if self._member_activity_tasklet:
    373             self._member_activity_tasklet.kill()
    374             self._member_activity_tasklet = None
    375         self._member_activity_timestamp.clear()
    376         self._member_activity_queue.clear()
    377 
    378     def mute_player(self, character_id, duration, reason):
    379         self._messenger.mute_player(character_id, duration, reason, self._mute_player_response_callback, self._mute_player_timeout_callback)
    380 
    381     def _mute_player_response_callback(self, payload, response):
    382         if response is None:
    383             return
    384         if response.status_code != httplib.OK:
    385             logger.error('LocalChat MuteResponse returned with statusCode {}, statusMessage: {}'.format(response.status_code, response.status_message))
    386 
    387     def _mute_player_timeout_callback(self, error, request_primitive):
    388         public_gateway_service = sm.GetService('publicGatewaySvc')
    389         logger.error('LocalChat request to mute player timed out', extra={'error': error,
    390          'request_broker_details': public_gateway_service.get_request_broker_details(),
    391          'notice_consumer_details': public_gateway_service.get_notice_consumer_details(),
    392          'public_gateway_svc_uptime_ms': public_gateway_service.get_gateway_uptime_ms(),
    393          'request_correlation_uuid': str(uuid.UUID(bytes=request_primitive.correlation_uuid)) if request_primitive else ''})