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

sessions.py (40218B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\script\sys\sessions.py
      2 import copy
      3 import sys
      4 import types
      5 from collections import defaultdict
      6 import blue
      7 import localization
      8 import localstorage
      9 import log
     10 import uthread
     11 from carbon.common.script.net import machobase
     12 from carbon.common.script.net.machoNetAddress import MachoAddress
     13 from carbon.common.script.net.machoNetExceptions import UnMachoDestination
     14 from carbon.common.script.sys import service
     15 from carbon.common.script.sys.serviceConst import ROLE_ADMIN, ROLE_PLAYER, ROLE_SERVICE
     16 from carbon.common.script.util.format import FmtDateEng
     17 from eveexceptions import CancelSessionChange, UserError
     18 from eveprefs import prefs
     19 from eveservices.roles import has_any_role
     20 from markers import PopMark, PushMark
     21 from stringutil import strx
     22 from carbon.common.script.sys.basesession import allConnectedObjects, allObjectConnections, allSessionsBySID, CallTimer, CallTimersEnabled, CloseSession, CreateSession, dyingObjects, EnableCallTimers, FindClientsAndHoles, FindSessions, GetCallTimes, GetNewSid, GetObjectByUUID, GetObjectUUID, GetServiceSession, IsInClientContext, methodCallHistory, ObjectConnection, outstandingCallTimers, ReadContextSessionTypesPrefs, SESSIONCHANGEDELAY, sessionsByAttribute, sessionsBySID
     23 
     24 def ThrottlePerSeconds(max = 1, seconds = 10, message = 'GenericStopSpamming'):
     25 
     26     def Helper(f):
     27 
     28         def Wrapper(*args, **kwargs):
     29             if session and has_any_role(session, ROLE_PLAYER):
     30                 session.Throttle(f.__name__, max, seconds * const.SEC, message)
     31             return f(*args, **kwargs)
     32 
     33         return Wrapper
     34 
     35     return Helper
     36 
     37 
     38 def ThrottlePerMinute(max = 1, message = 'GenericStopSpamming'):
     39 
     40     def Helper(f):
     41 
     42         def Wrapper(*args, **kwargs):
     43             if session and has_any_role(session, ROLE_PLAYER):
     44                 session.Throttle(f.__name__, max, const.MIN, message)
     45             return f(*args, **kwargs)
     46 
     47         return Wrapper
     48 
     49     return Helper
     50 
     51 
     52 def ThrottlePer5Minutes(max = 1, message = 'GenericStopSpamming'):
     53 
     54     def Helper(f):
     55 
     56         def Wrapper(*args, **kwargs):
     57             if session and has_any_role(session, ROLE_PLAYER):
     58                 session.Throttle(f.__name__, max, 5 * const.MIN, message)
     59             return f(*args, **kwargs)
     60 
     61         return Wrapper
     62 
     63     return Helper
     64 
     65 
     66 def ThrottlePerSecond(max = 1, message = 'GenericStopSpamming'):
     67 
     68     def Helper(f):
     69 
     70         def Wrapper(*args, **kwargs):
     71             if session and has_any_role(session, ROLE_PLAYER):
     72                 session.Throttle(f.__name__, max, const.SEC, message)
     73             return f(*args, **kwargs)
     74 
     75         return Wrapper
     76 
     77     return Helper
     78 
     79 
     80 def ThrottlePerDay(max = 1, message = 'GenericStopSpamming'):
     81 
     82     def Helper(f):
     83 
     84         def Wrapper(*args, **kwargs):
     85             if session and has_any_role(session, ROLE_PLAYER):
     86                 session.Throttle(f.__name__, max, const.DAY, message)
     87             return f(*args, **kwargs)
     88 
     89         return Wrapper
     90 
     91     return Helper
     92 
     93 
     94 def SessionKillah(machoNet):
     95     DEF_SESS_TIMEOUT = 120
     96     DEF_CTXSESS_TIMEOUT = 60
     97     DEF_SLEEPTIME = 30
     98     sleepTime = prefs.GetValue('sessionKillah.SleepTime', DEF_SLEEPTIME) * 1000
     99     while True:
    100         try:
    101             blue.pyos.synchro.SleepWallclock(sleepTime)
    102             killSessions = prefs.GetValue('sessionKillah.Enable', False)
    103             sessTimeout = prefs.GetValue('sessionKillah.SessionTimeout', DEF_SESS_TIMEOUT) * 1000
    104             ctxSessTimeout = prefs.GetValue('sessionKillah.ContextSessionTimeout', DEF_CTXSESS_TIMEOUT) * 1000
    105             sleepTime = prefs.GetValue('sessionKillah.SleepTime', DEF_SLEEPTIME) * 1000
    106             ReadContextSessionTypesPrefs()
    107             if killSessions:
    108                 now = blue.os.GetWallclockTime()
    109                 toRemove = defaultdict(list)
    110                 for transport in machoNet.transportsByID.itervalues():
    111 
    112                     def AddIfIrrelevant(sess, timeout):
    113                         if sess.role & ROLE_SERVICE == 0 and not sess.connectedObjects and getattr(sess, 'charid', None):
    114                             if sess.contextOnly:
    115                                 irrelevant = now - sess.lastRemoteCall >= timeout
    116                             else:
    117                                 irrelevant = sess.irrelevanceTime is not None and now - sess.irrelevanceTime >= timeout
    118                             if irrelevant:
    119                                 toRemove[transport].append(sess.sid)
    120 
    121                     for sess in transport.sessions.itervalues():
    122                         AddIfIrrelevant(sess, sessTimeout)
    123 
    124                     for sess in transport.contextSessions.itervalues():
    125                         AddIfIrrelevant(sess, ctxSessTimeout)
    126 
    127                 myNodeID = machoNet.GetNodeID()
    128                 for transport, sids in toRemove.iteritems():
    129                     try:
    130                         log.LogInfo('Asking proxy ', transport.nodeID, ' to remove ', len(sids), ' session(s)')
    131                         proxySessionMgr = machoNet.session.ConnectToRemoteService('sessionMgr', nodeID=transport.nodeID)
    132                         proxySessionMgr.RemoveSessionsFromServer(myNodeID, sids)
    133                     except Exception:
    134                         log.LogException('While removing irrelevant session from serer')
    135                         sys.exc_clear()
    136 
    137         except Exception:
    138             log.LogException('In SessionKillah loop, caught to keep thread alive')
    139             sys.exc_clear()
    140 
    141 
    142 def GetUndeadObjects():
    143     from carbon.common.script.net.moniker import allMonikers
    144     while 1:
    145         obs = []
    146         for each in allConnectedObjects:
    147             obs.append(each)
    148 
    149         break
    150 
    151     while 1:
    152         con = []
    153         for each in allObjectConnections:
    154             con.append(each)
    155 
    156         break
    157 
    158     while 1:
    159         ses = []
    160         for each in allSessionsBySID.itervalues():
    161             ses.append(each)
    162 
    163         break
    164 
    165     while 1:
    166         mon = []
    167         for each in allMonikers:
    168             mon.append(each)
    169 
    170         break
    171 
    172     zombies = []
    173     bombies = []
    174     for each in con:
    175         liveone = 0
    176         for other in ses:
    177             for yetanother in other.connectedObjects.itervalues():
    178                 if each is yetanother[0]:
    179                     liveone = 1
    180                     break
    181 
    182         if not liveone:
    183             for other in mon:
    184                 if other.boundObject is each:
    185                     liveone = 1
    186                     break
    187 
    188         if not liveone:
    189             zombies.append(each)
    190 
    191     for each in obs:
    192         liveone = 0
    193         for other in con:
    194             if other.__object__ is each:
    195                 liveone = 1
    196                 for yetanother in zombies:
    197                     if yetanother is other:
    198                         liveone = 2
    199 
    200                 if liveone:
    201                     break
    202 
    203         if not liveone:
    204             zombies.append(each)
    205         elif liveone == 2:
    206             bombies.append(each)
    207 
    208     return (zombies, bombies)
    209 
    210 
    211 def GetAllSessionInfo():
    212     return (allSessionsBySID, sessionsBySID, sessionsByAttribute)
    213 
    214 
    215 def GetSessionMaps():
    216     return (sessionsBySID, sessionsByAttribute)
    217 
    218 
    219 def CountSessions(attr, val):
    220     cnt = 0
    221     for v in val:
    222         try:
    223             r = []
    224             for sid in sessionsByAttribute[attr].get(v, {}).iterkeys():
    225                 if sid in sessionsBySID:
    226                     cnt += 1
    227                 else:
    228                     r.append(sid)
    229 
    230             for each in r:
    231                 del sessionsByAttribute[attr][v][each]
    232 
    233         except:
    234             srv = sm.services['sessionMgr']
    235             srv.LogError('Session map borked')
    236             srv.LogError('sessionsByAttribute=', sessionsByAttribute)
    237             srv.LogError('sessionsBySID=', sessionsBySID)
    238             log.LogTraceback()
    239             raise
    240 
    241     return cnt
    242 
    243 
    244 def FindClients(attr, val):
    245     ret = []
    246     for v in val:
    247         try:
    248             r = []
    249             for sid in sessionsByAttribute[attr].get(v, {}).iterkeys():
    250                 if sid in sessionsBySID:
    251                     s = sessionsBySID[sid]
    252                     if hasattr(s, 'clientID'):
    253                         ret.append(s.clientID)
    254                 else:
    255                     r.append(sid)
    256 
    257             for each in r:
    258                 del sessionsByAttribute[attr][v][each]
    259 
    260         except:
    261             srv = sm.services['sessionMgr']
    262             srv.LogError('Session map borked')
    263             srv.LogError('sessionsByAttribute=', sessionsByAttribute)
    264             srv.LogError('sessionsBySID=', sessionsBySID)
    265             log.LogTraceback()
    266             raise
    267 
    268     return ret
    269 
    270 
    271 def FindSessionsByAttributes(sessionValueBySessionAttribute):
    272     ret = set()
    273     for attribute, attributeValue in sessionValueBySessionAttribute.iteritems():
    274         if attribute == 'sid':
    275             sess = sessionsBySID.get(attributeValue, None)
    276             if sess:
    277                 ret.add(sess)
    278         elif attribute in sessionsByAttribute:
    279             for sid in sessionsByAttribute[attribute].get(attributeValue, {}).keys():
    280                 sess = sessionsBySID.get(sid, None)
    281                 if sess is not None and getattr(sess, attribute, None) == attributeValue:
    282                     ret.add(sess)
    283 
    284         else:
    285             for sess in sessionsBySID.itervalues():
    286                 if attribute in ('corprole', 'rolesAtAll', 'rolesAtHQ', 'rolesAtBase', 'rolesAtOther'):
    287                     if attributeValue & getattr(sess, attribute, 0):
    288                         ret.add(sess)
    289                 elif getattr(sess, attribute, None) == attributeValue:
    290                     ret.add(sess)
    291 
    292     return ret
    293 
    294 
    295 def GetSessions(sid = None):
    296     if sid is None:
    297         return sessionsBySID.values()
    298     else:
    299         return sessionsBySID.get(sid, None)
    300 
    301 
    302 def GetNumberOfCharacterSessions():
    303     return len(sessionsByAttribute.get('charid', []))
    304 
    305 
    306 class SessionMgr(service.Service):
    307     __guid__ = 'svc.sessionMgr'
    308     __displayname__ = 'Session manager'
    309     __exportedcalls__ = {'GetSessionStatistics': [ROLE_SERVICE],
    310      'CloseUserSessions': [ROLE_SERVICE],
    311      'GetProxyNodeFromID': [ROLE_SERVICE],
    312      'GetClientIDsFromID': [ROLE_SERVICE],
    313      'UpdateSessionAttributes': [ROLE_SERVICE],
    314      'ConnectToClientService': [ROLE_SERVICE],
    315      'PerformSessionChange': [ROLE_SERVICE],
    316      'GetLocalClientIDs': [ROLE_SERVICE],
    317      'EndAllGameSessions': [ROLE_ADMIN | ROLE_SERVICE],
    318      'PerformHorridSessionAttributeUpdate': [ROLE_SERVICE],
    319      'BatchedRemoteCall': [ROLE_SERVICE],
    320      'GetSessionDetails': [ROLE_SERVICE],
    321      'TerminateClientConnections': [ROLE_SERVICE | ROLE_ADMIN],
    322      'RemoveSessionsFromServer': [ROLE_SERVICE],
    323      'ResetClientConnection': [ROLE_SERVICE]}
    324     __dependencies__ = []
    325     __notifyevents__ = ['ProcessSessionChange',
    326      'DoSessionChanging',
    327      'DoSimClockRebase',
    328      'OnGlobalConfigChanged',
    329      'OnSessionChanged']
    330 
    331     def __init__(self):
    332         service.Service.__init__(self)
    333         if machobase.mode == 'server':
    334             self.__dependencies__ += ['authentication', 'DB2']
    335         self.sessionClientIDCache = {'userid': {},
    336          'charid': {}}
    337         self.proxies = {}
    338         self.clientIDsByCharIDCache = {}
    339         self.sessionChangeShortCircuitReasons = []
    340         self.sessionChangeSubscribers = {}
    341         self.additionalAttribsAllowedToUpdate = []
    342         self.additionalStatAttribs = []
    343         self.additionalSessionDetailsAttribs = []
    344         self.sessionStatistics = None
    345         self.timeSessionStatsComputed = None
    346         if machobase.mode == 'server':
    347             uthread.new(SessionKillah, sm.GetService('machoNet')).context = 'sessions::SessionKillah'
    348 
    349     def GetProxySessionManager(self, nodeID):
    350         if nodeID not in self.proxies:
    351             self.proxies[nodeID] = self.session.ConnectToProxyServerService('sessionMgr', nodeID)
    352         return self.proxies[nodeID]
    353 
    354     def GetLocalClientIDs(self):
    355         ret = []
    356         for each in GetSessions():
    357             if hasattr(each, 'clientID'):
    358                 ret.append(each.clientID)
    359 
    360         return ret
    361 
    362     def GetReason(self, oldReason, newReason, timeLeft):
    363         return localization.GetByLabel('/Carbon/UI/Sessions/SessionChangeInProgressBase')
    364 
    365     def __RaisePSCIP(self, oldReason, newReason, timeLeft = None):
    366         if oldReason is None:
    367             oldReason = ''
    368         if newReason is None:
    369             newReason = ''
    370         reason = self.GetReason(oldReason, newReason, timeLeft)
    371         self.LogInfo('raising a PerformSessionChangeInProgress user error with reason ', reason)
    372         raise UserError('PerformSessionChangeInProgress', {'reason': reason})
    373 
    374     def NotifySessionChangeSubscribers(self):
    375         for callback in self.sessionChangeSubscribers.values():
    376             callback()
    377 
    378     def RegisterNotifyOfSessionChange(self, subscriber, callback):
    379         if subscriber not in self.sessionChangeSubscribers:
    380             self.sessionChangeSubscribers[subscriber] = callback
    381 
    382     def UnregisterNotifyOfSessionChange(self, subscriber):
    383         if subscriber in self.sessionChangeSubscribers:
    384             del self.sessionChangeSubscribers[subscriber]
    385 
    386     def PerformSessionLockedOperation(self, *args, **keywords):
    387         return self.PerformSessionChange(*args, **keywords)
    388 
    389     def PerformSessionChange(self, sessionChangeReason, func, *args, **keywords):
    390         global sessionChangeDelay
    391         if 'hostileMutation' in keywords or 'violateSafetyTimer' in keywords or 'wait' in keywords:
    392             kw2 = copy.copy(keywords)
    393             hostile = keywords.get('hostileMutation', 0)
    394             wait = keywords.get('wait', 0)
    395             violateSafetyTimer = keywords.get('violateSafetyTimer', 0)
    396             if 'violateSafetyTimer' in kw2:
    397                 del kw2['violateSafetyTimer']
    398             if 'hostileMutation' in kw2:
    399                 del kw2['hostileMutation']
    400             if 'wait' in kw2:
    401                 del kw2['wait']
    402         else:
    403             hostile = 0
    404             violateSafetyTimer = 0
    405             kw2 = keywords
    406             wait = 0
    407         self.LogInfo('Performing a locked session changing operation at', blue.os.GetSimTime(), 'with next allowed at', session.nextSessionChange, '- reason=', sessionChangeReason)
    408         if machobase.mode == 'client':
    409             sess = session
    410             if not violateSafetyTimer and hostile in (0, 1) and sess.charid:
    411                 if sess.nextSessionChange is not None and sess.nextSessionChange > blue.os.GetSimTime():
    412                     if wait > 0:
    413                         t = 1000 * (2 + (session.nextSessionChange - blue.os.GetSimTime()) / const.SEC)
    414                         self.LogInfo('PerformSessionChange is sleeping for %s ms' % t)
    415                         blue.pyos.synchro.SleepWallclock(t)
    416                 if sess.nextSessionChange is not None and sess.nextSessionChange > blue.os.GetSimTime():
    417                     self.LogError("Too frequent session change attempts.  You'll just get yourself stuck doing this.  Ignoring.")
    418                     self.LogError('func=', func, ', args=', args, ', keywords=', keywords)
    419                     if sessionChangeReason in self.sessionChangeShortCircuitReasons:
    420                         return
    421                     self.__RaisePSCIP(sess.sessionChangeReason, sessionChangeReason, sess.nextSessionChange - blue.os.GetSimTime())
    422             else:
    423                 self.LogInfo('Passing session change stuck prevention speedbump.  hostile=', hostile)
    424         else:
    425             raise RuntimeError('Not Yet Implemented')
    426         if sess.mutating and hostile in (0, 1):
    427             if sessionChangeReason in self.sessionChangeShortCircuitReasons:
    428                 self.LogInfo('Ignoring session change attempt due to ' + sessionChangeReason + ' overzealousness')
    429                 return
    430             self.__RaisePSCIP(sess.sessionChangeReason, sessionChangeReason)
    431         self.NotifySessionChangeSubscribers()
    432         try:
    433             if hostile not in (2, 4):
    434                 self.LogInfo('Incrementing the session mutation flag')
    435                 sess.mutating += 1
    436             if sess.mutating == 1:
    437                 self.LogInfo('Chaining ProcessSessionMutating event')
    438                 sm.ChainEvent('ProcessSessionMutating', func, args, kw2)
    439                 sess.sessionChangeReason = sessionChangeReason
    440             if hostile == 0:
    441                 prev = sess.nextSessionChange
    442                 if not violateSafetyTimer:
    443                     sess.nextSessionChange = blue.os.GetSimTime() + sessionChangeDelay
    444                 localNextSessionChange = sess.nextSessionChange
    445                 self.LogInfo('Pre-op updating next legal session change to ', FmtDateEng(sess.nextSessionChange))
    446                 self.LogInfo('Executing the session modification method')
    447                 try:
    448                     return func(*args, **kw2)
    449                 except Exception as e:
    450                     if localNextSessionChange >= sess.nextSessionChange:
    451                         sess.nextSessionChange = prev
    452                         self.LogInfo('post-op exception handler reverting next legal session change to ', FmtDateEng(sess.nextSessionChange))
    453                     else:
    454                         self.LogInfo("post-op exception handler - Someone else has modified nextSessionChange, so DON'T revert it - modified value is ", FmtDateEng(sess.nextSessionChange))
    455                     if not isinstance(e, CancelSessionChange):
    456                         raise
    457 
    458             elif hostile in (1, 3):
    459                 self.LogInfo('Initiating Remote Mutation (local state change only), args=', args, ', keywords=', kw2)
    460             else:
    461                 self.LogInfo('Finalizing Remote Mutation (local state change only), args=', args, ', keywords=', kw2)
    462         finally:
    463             self.LogInfo('Post-op updating next legal session change to ', FmtDateEng(sess.nextSessionChange))
    464             if hostile not in (1, 3):
    465                 self.LogInfo('Decrementing the session mutation flag')
    466                 sess.mutating -= 1
    467                 if sess.mutating == 0:
    468                     self.LogInfo('Scattering OnSessionMutated event')
    469                     sm.ScatterEvent('OnSessionMutated', func, args, kw2)
    470 
    471     def PerformSelectiveSessionChange(self, reasonAllowedToViolate, reason, allowWait, func, *args, **keywords):
    472         violateSafetyTimer = 0
    473         if session.nextSessionChange is not None and session.nextSessionChange > blue.os.GetSimTime():
    474             if session.sessionChangeReason.startswith(reasonAllowedToViolate):
    475                 violateSafetyTimer = 1
    476         kw2 = copy.copy(keywords)
    477         kw2['violateSafetyTimer'] = violateSafetyTimer
    478         if allowWait:
    479             kw2['wait'] = 1
    480         return self.PerformSessionChange(reason, func, *args, **kw2)
    481 
    482     def GetProxyNodeFromID(self, idtype, theID, refresh = 0):
    483         if idtype != 'clientID':
    484             clientID = self.GetClientIDsFromID(idtype, theID, refresh)[0]
    485         else:
    486             clientID = theID
    487         return sm.services['machoNet'].GetProxyNodeIDFromClientID(clientID)
    488 
    489     def IsPlayerCharacter(self, charID):
    490         raise Exception('stub function not implemented')
    491 
    492     def GetClientIDsFromID(self, idtype, theID, refresh = 0):
    493         clientIDs = set()
    494         if theID in sessionsByAttribute[idtype]:
    495             sids = sessionsByAttribute[idtype][theID]
    496             for sid in sids:
    497                 if sid in sessionsBySID:
    498                     s = sessionsBySID[sid]
    499                     if getattr(s, 'clientID', 0):
    500                         if theID in self.sessionClientIDCache[idtype]:
    501                             del self.sessionClientIDCache[idtype][theID]
    502                         clientIDs.add(s.clientID)
    503 
    504         if not refresh and theID in self.sessionClientIDCache[idtype]:
    505             return self.sessionClientIDCache[idtype][theID]
    506         if not hasattr(self, 'dbzcluster'):
    507             self.dbzcluster = self.DB2.GetSchema('zcluster')
    508         clientID = None
    509         if idtype == 'charid':
    510             if self.IsPlayerCharacter(theID):
    511                 if theID in self.clientIDsByCharIDCache:
    512                     clientID, lastTime = self.clientIDsByCharIDCache[theID]
    513                     if blue.os.GetWallclockTime() - lastTime > const.SEC:
    514                         clientID = None
    515                     else:
    516                         clientIDs.add(clientID)
    517                 if clientID is None:
    518                     client = self.dbzcluster.Sessions_ByCharacterID(theID)
    519                     if len(client) and client[0].clientID:
    520                         clientID = client[0].clientID
    521                         clientIDs.add(clientID)
    522                 self.clientIDsByCharIDCache[theID] = (clientID, blue.os.GetWallclockTime())
    523             else:
    524                 log.LogTraceback('Thou shall only use GetClientIDsFromID for player characters', show_locals=1)
    525                 clientID = None
    526         elif idtype == 'userid':
    527             for row in self.dbzcluster.Sessions_ByUserID(theID):
    528                 if row.clientID:
    529                     clientIDs.add(row.clientID)
    530 
    531         else:
    532             raise RuntimeError('Can only currently characterID to locate a client through the DB')
    533         if not clientIDs:
    534             raise UnMachoDestination('The dude is not logged on')
    535         else:
    536             self.sessionClientIDCache[idtype][theID] = list(clientIDs)
    537             return self.sessionClientIDCache[idtype][theID]
    538 
    539     def DoSessionChanging(self, *args):
    540         pass
    541 
    542     def ProcessSessionChange(self, isRemote, sess, change):
    543         if 'userid' in change and change['userid'][0] in self.sessionClientIDCache['userid']:
    544             del self.sessionClientIDCache['userid'][change['userid'][0]]
    545         if 'charid' in change and change['charid'][0] in self.sessionClientIDCache['charid']:
    546             del self.sessionClientIDCache['charid'][change['charid'][0]]
    547         if machobase.mode == 'proxy':
    548             return -1
    549 
    550     def OnSessionChanged(self, isRemote, sess, change):
    551         if 'charid' in change:
    552             oldCharacterID, newCharacterID = change['charid']
    553             sm.ScatterEvent('OnCharacterSessionChanged', oldCharacterID, newCharacterID)
    554 
    555     def DoSimClockRebase(self, times):
    556         oldSimTime, newSimTime = times
    557         try:
    558             session.nextSessionChange += newSimTime - oldSimTime
    559         except:
    560             log.LogException('Exception while trying to rebase the session change timer')
    561 
    562     def OnGlobalConfigChanged(self, config):
    563         global sessionChangeDelay
    564         if 'sessionChangeDelay' in config:
    565             sessionChangeDelay = int(config['sessionChangeDelay']) * const.SEC
    566 
    567     def TypeAndNodeValidationHook(self, idType, id):
    568         pass
    569 
    570     def UpdateSessionAttributes(self, idtype, theID, dict):
    571         if idtype not in ['charid', 'userid'] + self.additionalAttribsAllowedToUpdate:
    572             raise RuntimeError("You shouldn't be calling this, as you obviously don't know what you're doing.  This is like one of the most sensitive things in the system, dude.")
    573         if machobase.mode == 'proxy' and theID not in sessionsByAttribute[idtype]:
    574             raise UnMachoDestination('Wrong proxy or client not connected')
    575         if machobase.mode == 'server' and idtype in ('userid', 'charid'):
    576             proxyNodeID = None
    577             try:
    578                 proxyNodeID = self.GetProxyNodeFromID(idtype, theID, 1)
    579             except UnMachoDestination:
    580                 sys.exc_clear()
    581 
    582             if proxyNodeID is not None:
    583                 return self.GetProxySessionManager(proxyNodeID).UpdateSessionAttributes(idtype, theID, dict)
    584         sessions = FindSessions(idtype, [theID])
    585         if idtype == 'charid' and 'flagRolesOnly' in dict and sessions and len(sessions) > 0:
    586             sessioncorpid = sessions[0].corpid
    587             rolecorpid = dict['corpid']
    588             if sessioncorpid != rolecorpid:
    589                 self.LogError('Character session is wrong!!! Character', theID, 'has session corp', sessioncorpid, 'but should be', rolecorpid, "I'll fix his session but please investigate why this occurred! Update dict:", dict, 'Session:', sessions[0])
    590         if 'flagRolesOnly' in dict:
    591             del dict['flagRolesOnly']
    592         self.TypeAndNodeValidationHook(idtype, theID)
    593         parallelCalls = []
    594         for each in sessions:
    595             if hasattr(each, 'clientID'):
    596                 parallelCalls.append((self.PerformHorridSessionAttributeUpdate, (each.clientID, dict)))
    597             else:
    598                 each.LogSessionHistory('Updating session information via sessionMgr::UpdateSessionAttributes')
    599                 each.SetAttributes(dict)
    600                 each.LogSessionHistory('Updated session information via sessionMgr::UpdateSessionAttributes')
    601 
    602         if len(parallelCalls) > 60:
    603             log.LogTraceback('Horrid session change going haywire.  Redesign the calling code!')
    604         uthread.parallel(parallelCalls)
    605 
    606     def PerformHorridSessionAttributeUpdate(self, clientID, dict):
    607         try:
    608             if machobase.mode == 'server':
    609                 proxyNodeID = self.GetProxyNodeFromID('clientID', clientID)
    610                 return self.GetProxySessionManager(proxyNodeID).PerformHorridSessionAttributeUpdate(clientID, dict)
    611             s = sm.services['machoNet'].GetSessionByClientID(clientID)
    612             if s:
    613                 s.LogSessionHistory('Updating session information via sessionMgr::UpdateSessionAttributes')
    614                 s.SetAttributes(dict)
    615                 s.LogSessionHistory('Updated session information via sessionMgr::UpdateSessionAttributes')
    616         except Exception:
    617             log.LogException()
    618             sys.exc_clear()
    619 
    620     def GetSessionFromParams(self, idtype, theID):
    621         if idtype == 'clientID':
    622             s = sm.services['machoNet'].GetSessionByClientID(theID)
    623             if s is None:
    624                 raise UnMachoDestination('Wrong proxy or client not connected, session not found by clientID=%s' % theID)
    625             return s
    626         if theID not in sessionsByAttribute[idtype]:
    627             raise UnMachoDestination('Wrong proxy or client not connected, session not found by %s=%s' % (idtype, theID))
    628         else:
    629             sids = sessionsByAttribute[idtype][theID].keys()
    630             if not len(sids) == 1:
    631                 raise UnMachoDestination('Ambiguous idtype/id pair (%s/%s).  There are %d sessions that match them.' % (idtype, theID, len(sids)))
    632             else:
    633                 sid = sids[0]
    634             if sid not in sessionsBySID:
    635                 raise UnMachoDestination("The client's session is in an invalid or terminating state")
    636             return sessionsBySID[sid]
    637 
    638     def ConnectToClientService(self, svc, idtype, theID):
    639         if machobase.mode == 'proxy':
    640             s = self.GetSessionFromParams(idtype, theID)
    641             return sm.services['machoNet'].ConnectToRemoteService(svc, MachoAddress(clientID=s.clientID, service=svc), s)
    642         proxyNodeID = self.GetProxyNodeFromID(idtype, theID)
    643         try:
    644             return self.GetProxySessionManager(proxyNodeID).ConnectToClientService(svc, idtype, theID)
    645         except UnMachoDestination:
    646             sys.exc_clear()
    647             if not refreshed:
    648                 return self.GetProxySessionManager(self.GetProxyNodeFromID(idtype, theID, 1)).ConnectToClientService(svc, idtype, theID)
    649 
    650     def GetSessionStatistics(self):
    651         now = blue.os.GetWallclockTime()
    652         statAttributes = ['userid', 'usertype'] + self.additionalStatAttribs
    653         if self.sessionStatistics is None or now - self.timeSessionStatsComputed > 5 * const.SEC:
    654             self.timeSessionStatsComputed = now
    655             self.sessionStatistics = {}
    656             for attribute, valuesOfAttribute in sessionsByAttribute.iteritems():
    657                 if attribute in statAttributes:
    658                     attrValueCounts = {}
    659                     for attrValue, valueSessions in valuesOfAttribute.iteritems():
    660                         attrValueCounts[attrValue] = len(valueSessions)
    661 
    662                     self.sessionStatistics[attribute] = [len(valuesOfAttribute), attrValueCounts]
    663 
    664             self._AddToSessionStatistics()
    665         return self.sessionStatistics
    666 
    667     def _AddToSessionStatistics(self):
    668         machoChar = machoUser = 0
    669         for sess in sessionsBySID.itervalues():
    670             if sess.sessionType == const.session.SESSION_TYPE_GAME:
    671                 if sess.charid:
    672                     machoChar += 1
    673                 else:
    674                     machoUser += 1
    675 
    676         self.sessionStatistics['CARBON:MachoChar'] = (machoChar, {None: machoChar})
    677         self.sessionStatistics['CARBON:MachoUser'] = (machoUser, {None: machoUser})
    678 
    679     def Run(self, memstream = None):
    680         service.Service.Run(self, memstream)
    681         self.AppRun(memstream)
    682 
    683     def AppRun(self, memstream = None):
    684         pass
    685 
    686     def BatchedRemoteCall(self, batchedCalls):
    687         retvals = []
    688         for callID, (service, method, args, keywords) in batchedCalls.iteritems():
    689             try:
    690                 c = '%s::%s (Batched\\Server)' % (service, method)
    691                 timer = PushMark(c)
    692                 try:
    693                     with CallTimer(c):
    694                         retvals.append((0, callID, getattr(sm.GetService(service), method)(*args, **keywords)))
    695                 finally:
    696                     PopMark(timer)
    697 
    698             except Exception as e:
    699                 if getattr(e, '__passbyvalue__', 0):
    700                     retvals.append((1, callID, strx(e)))
    701                 else:
    702                     retvals.append((1, callID, e))
    703                 sys.exc_clear()
    704 
    705         return retvals
    706 
    707     def GetSessionValuesFromRowset(self, si):
    708         return {}
    709 
    710     def GetInitialValuesFromCharID(self, charID):
    711         return {}
    712 
    713     def CloseUserSessions(self, userIDs, reason, clientID = None):
    714         if type(userIDs) not in (types.ListType, types.TupleType):
    715             userIDs = [userIDs]
    716         for each in FindSessions('userid', userIDs):
    717             if clientID is None or not hasattr(each, 'clientID') or each.clientID != clientID:
    718                 each.LogSessionHistory(reason)
    719                 CloseSession(each)
    720 
    721     def ResetClientConnection(self, clientID, sessionID = None):
    722         try:
    723             if machobase.mode != 'proxy':
    724                 raise RuntimeError('This cannot run on a %s' % machobase.mode)
    725             if clientID is None:
    726                 raise RuntimeError('Cannot reset client connection None')
    727             log.LogNotice('ResetClientConnection', clientID, sessionID)
    728             if sessionID is not None:
    729                 sessionToReset = sessionsBySID.get(sessionID, None)
    730                 if sessionToReset is None:
    731                     log.LogError('Failed to find client session with sessionID %s. Cannot reset client connection.' % sessionID)
    732                     return False
    733             else:
    734                 for sess in sessionsBySID.itervalues():
    735                     if getattr(sess, 'clientID', None) == clientID:
    736                         sessionToReset = sess
    737                         break
    738                 else:
    739                     log.LogError('Failed to find client session with clientID %s. Cannot reset client connection.' % clientID)
    740                     return False
    741 
    742             sm.ChainEvent('ProcessProxySessionReset', sessionToReset)
    743             log.LogNotice('ResetClientConnection: Instructing all sols to remove', sessionToReset)
    744             sessionToReset.LogSessionHistory('Removing sessions from sol nodes')
    745             self._RemoveSessionFromTransports(sessionToReset)
    746             sessionToReset.LogSessionHistory('Resetting session information via sessionMgr::ResetClientConnection')
    747             sessionToReset.ClearCharacterDependantAttributes()
    748             sessionToReset.LogSessionHistory('Session information reset sessionMgr::ResetClientConnection')
    749         except Exception:
    750             log.LogException()
    751             raise
    752 
    753         return True
    754 
    755     @staticmethod
    756     def _RemoveSessionFromTransports(clientSession):
    757         sassies = FindSessionsByAttributes({'userid': clientSession.userid,
    758          'charid': clientSession.charid,
    759          'shipid': clientSession.shipid})
    760         transports = {t for t in sm.GetService('machoNet').transportsByID.itervalues() if t.nodeID is not None}
    761         for transport in transports:
    762             for sess in sassies:
    763                 try:
    764                     transport.RemoveSessionFromServer(sess)
    765                 except Exception:
    766                     log.LogException('While removing irrelevant session from server')
    767 
    768     def TerminateClientConnections(self, reason, filter):
    769         if machobase.mode != 'proxy' or not isinstance(filter, types.DictType) or len(filter) == 0:
    770             raise RuntimeError('TerminateClientConnections should only be called on a proxy and with a non-empty filter dictionnary')
    771         numDisconnected = 0
    772         for clientSession in GetSessions():
    773             blue.pyos.BeNice()
    774             clientID = getattr(clientSession, 'clientID', None)
    775             if clientID is None:
    776                 continue
    777             if clientSession.role & ROLE_SERVICE:
    778                 continue
    779             for attr, value in filter.iteritems():
    780                 if not (hasattr(clientSession, attr) and getattr(clientSession, attr) == value):
    781                     break
    782             else:
    783                 numDisconnected += 1
    784                 clientSession.LogSessionHistory('Connection terminated by administrator %s, reason is: %s' % (str(session.userid), reason))
    785                 sm.GetService('machoNet').TerminateClient(reason, clientID)
    786 
    787         return numDisconnected
    788 
    789     def EndAllGameSessions(self, remote = 0):
    790         if remote:
    791             self.session.ConnectToAllProxyServerServices('sessionMgr').EndAllGameSessions()
    792         else:
    793             txt = ''
    794             for s in GetSessions():
    795                 blue.pyos.BeNice()
    796                 if hasattr(s, 'clientID') and not s.role & ROLE_SERVICE:
    797                     sid = s.sid
    798                     s.LogSessionHistory('Session closed by administrator %s' % str(session.userid))
    799                     CloseSession(s, True)
    800 
    801     def RemoveSessionsFromServer(self, nodeID, sessionIDs):
    802         if machobase.mode != 'proxy':
    803             raise RuntimeError('RemoveSessionsFromServer should only be called on a proxy')
    804         log.LogInfo('CTXSESS: RemoveSessionsFromServer(nodeID=', nodeID, '), with ', len(sessionIDs), ' session IDs')
    805         mn = sm.services['machoNet']
    806         serverTID = mn.transportIDbySolNodeID.get(nodeID, None)
    807         if serverTID is not None:
    808             serverTransport = mn.transportsByID[serverTID]
    809             for sid in sessionIDs:
    810                 sess = sessionsBySID.get(sid, None)
    811                 if sess is not None:
    812                     uthread.worker('SessionMgr::RemoveSesssionsFromServer', serverTransport.RemoveSessionFromServer, sess)
    813 
    814         else:
    815             log.LogWarning('RemoveSessionsFromServer() called with unknown or non-server nodeID ', nodeID)
    816 
    817     def GetSessionDetails(self, clientID, sid):
    818         from carbon.common.script.util import htmlwriter
    819         macho = sm.GetService('machoNet')
    820         if clientID:
    821             s = macho.GetSessionByClientID(clientID)
    822         else:
    823             s = GetSessions(sid)
    824         if s is None:
    825             return
    826         info = [['sid', s.sid],
    827          ['version', s.version],
    828          ['clientID', getattr(s, 'clientID', '')],
    829          ['userid', s.userid],
    830          ['userType', s.userType],
    831          ['role', s.role],
    832          ['charid', s.charid],
    833          ['lastRemoteCall', s.lastRemoteCall]]
    834         for each in self.additionalSessionDetailsAttribs:
    835             info.append([each, s.__dict__[each]])
    836 
    837         sessionsBySID, sessionsByAttribute = GetSessionMaps()
    838         for each in info:
    839             if each[0] == 'sid':
    840                 if each[1] not in sessionsBySID:
    841                     each[1] = str(each[1]) + ' <b>(Not in sessionsBySID)</b>'
    842             elif each[0] in sessionsByAttribute:
    843                 a = getattr(s, each[0])
    844                 if a:
    845                     if a not in sessionsByAttribute[each[0]]:
    846                         each[1] = str(each[1]) + " <b>(Not in sessionsByAttribute['%s'])</b>" % each[0]
    847                     elif s.sid not in sessionsByAttribute[each[0]].get(a, {}):
    848                         each[1] = str(each[1]) + " <b>(Not in sessionsByAttribute['%s']['%s'])</b>" % (each[0], a)
    849 
    850         info.append(['IP Address', getattr(s, 'address', '?')])
    851         connectedObjects = []
    852         hd = ['ObjectID', 'References', 'Object']
    853         for k, v in s.machoObjectsByID.iteritems():
    854             tmp = [k, '%s.%s' % (FmtDateEng(v[0]), v[0] % const.SEC), htmlwriter.Swing(str(v[1]))]
    855             if isinstance(v[1], ObjectConnection):
    856                 tmp[2] = str(tmp[2]) + ' (c2ooid=%s)' % str(v[1].__dict__['__c2ooid__'])
    857                 if v[1].__dict__['__c2ooid__'] not in s.connectedObjects:
    858                     tmp[2] = str(tmp[2]) + ' <b>(Not in s.connectedObjects)</b>'
    859                 else:
    860                     object = v[1].__dict__['__object__']
    861                     if s.sid not in object.sessionConnections:
    862                         tmp[2] = str(tmp[2]) + ' <b>(s.sid not in object.sessionConnections)</b>'
    863                     if s.sid not in object.objectConnections or v[1].__dict__['__c2ooid__'] not in object.objectConnections[s.sid]:
    864                         tmp[2] = str(tmp[2]) + ' <b>([s.sid][c2ooid]) not part of object.objectConnections)</b>'
    865             if k not in sessionsByAttribute['objectID']:
    866                 tmp[2] = str(tmp[2]) + " <b>(Not in sessionsByAttribute['objectID'])</b>"
    867             elif s.sid not in sessionsByAttribute['objectID'][k]:
    868                 tmp[2] = str(tmp[2]) + " <b>(Not in sessionsByAttribute['objectID']['%s'])</b>" % k
    869             connectedObjects.append(tmp)
    870 
    871         sessionHistory = []
    872         lastEntry = ''
    873         i = 0
    874         for each in s.sessionhist:
    875             tmp = each[2].replace('\n', '<br>')
    876             if tmp == lastEntry:
    877                 txt = '< same >'
    878             else:
    879                 txt = tmp
    880             lastEntry = tmp
    881             sessionHistory.append((each[0],
    882              i,
    883              each[1],
    884              htmlwriter.Swing(txt)))
    885             i += 1
    886 
    887         streamInfo = []
    888         try:
    889             streams = macho.transportsByID[macho.transportIDbySessionID[s.sid]].readers
    890             for stream in streams:
    891                 streamInfo.append((stream.streamID,
    892                  stream.request.remote_addr,
    893                  stream.sequence_number,
    894                  stream.reconnects,
    895                  stream.GetActiveSetSize()))
    896 
    897         except KeyError:
    898             pass
    899 
    900         return (info,
    901          connectedObjects,
    902          sessionHistory,
    903          s.calltimes,
    904          s.sessionVariables,
    905          streamInfo)
    906 
    907 
    908 def IsSessionChangeConnect(change):
    909     if 'charid' in change and change['charid'][0] is None and change['charid'][1] is not None:
    910         return change['charid'][1]
    911     return False
    912 
    913 
    914 def IsSessionChangeDisconnect(change, character = False):
    915     key = 'charid' if character else 'userid'
    916     if key in change and change[key][0] is not None and change[key][1] is None:
    917         return change[key][0]
    918     else:
    919         return False
    920 
    921 
    922 def IsSolarSystemChange(change):
    923     return 'solarsystemid2' in change
    924 
    925 
    926 class ClientContext(localstorage.UpdatedLocalStorage):
    927 
    928     def __init__(self):
    929         localstorage.UpdatedLocalStorage.__init__(self, {'base.ClientContext': True})
    930 
    931 
    932 class MethodCachingContext(localstorage.UpdatedLocalStorage):
    933 
    934     def __init__(self, methodCachingScope):
    935         localstorage.UpdatedLocalStorage.__init__(self, {'base.MethodCachingContext': methodCachingScope})
    936 
    937 
    938 def CachedMethodCalled(cacheKey, details):
    939     try:
    940         cacheScope = localstorage.GetLocalStorage()['base.MethodCachingContext']
    941         if details:
    942             clientTimes = details['versionCheck']
    943             if clientTimes:
    944                 clientTime = clientTimes[0]
    945                 cacheScope[cacheKey] = (sm.GetService('objectCaching').__versionchecktimes__[clientTime], 'sessionInfo' in details)
    946     except KeyError:
    947         pass
    948 
    949 
    950 sessionChangeDelay = SESSIONCHANGEDELAY