""" This defines a generic session class. All protocols should implement this class and its hook methods. The process of first connect: - The custom connection-handler for the respective protocol should be called by the transport connection itself. - The connect-handler handles whatever internal settings are needed - The connection-handler calls session_connect() - session_connect() setups sessions then calls session.at_connect() Disconnecting is a bit more complex in order to avoid circular calls depending on if the disconnect happens automatically or manually from a command. The process at automatic disconnect: - The custom disconnect-handler for the respective protocol should be called by the transport connection itself. This handler should be defined with a keyword argument 'step' defaulting to 1. - since step=1, the disconnect-handler calls session_disconnect() - session_disconnect() removes session, then calls session.at_disconnect() - session.at_disconnect() calls the custom disconnect-handler with step=2 as argument - since step=2, the disconnect-handler closes the connection and performs all needed protocol cleanup. The process of manual disconnect: - The command/outside function calls session.session_disconnect(). - from here the process proceeds as the automatic disconnect above. """ import time from datetime import datetime #from django.contrib.auth.models import User from django.conf import settings #from src.objects.models import ObjectDB from src.comms.models import Channel from src.utils import logger, reloads from src.commands import cmdhandler from src.server.sessionhandler import SESSIONS IDLE_TIMEOUT = settings.IDLE_TIMEOUT IDLE_COMMAND = settings.IDLE_COMMAND class IOdata(object): """ A simple storage object that allows for storing new attributes on it at creation. """ def __init__(self, **kwargs): "Give keyword arguments to store as new arguments on the object." self.__dict__.update(**kwargs) def _login(session, player): """ For logging a player in. Removed this from CmdConnect because ssh wanted to call it for autologin. """ # We are logging in, get/setup the player object controlled by player # Check if this is the first time the # *player* connects (should be set by the if player.db.FIRST_LOGIN: player.at_first_login() del player.db.FIRST_LOGIN player.at_pre_login() character = player.character if character: # this player has a character. Check if it's the # first time *this character* logs in (this should be # set by the initial create command) if character.db.FIRST_LOGIN: character.at_first_login() del character.db.FIRST_LOGIN # run character login hook character.at_pre_login() # actually do the login session.session_login(player) # post-login hooks player.at_post_login() if character: character.at_post_login() character.execute_cmd('look') else: player.execute_cmd('look') #------------------------------------------------------------ # SessionBase class #------------------------------------------------------------ class SessionBase(object): """ This class represents a player's session and is a template for individual protocols to communicate with Evennia. Each player gets a session assigned to them whenever they connect to the game server. All communication between game and player goes through their session. """ # use this to uniquely identify the protocol name, e.g. "telnet" or "comet" protocol_key = "BaseProtocol" def session_connect(self, address, suid=None): """ The setup of the session. An address (usually an IP address) on any form is required. This should be called by the protocol at connection time. suid = this is a session id. Needed by some transport protocols. """ self.address = address # user setup self.name = None self.uid = None self.suid = suid self.logged_in = False self.encoding = "utf-8" current_time = time.time() # The time the user last issued a command. self.cmd_last = current_time # Player-visible idle time, excluding the IDLE command. self.cmd_last_visible = current_time # The time when the user connected. self.conn_time = current_time # Total number of commands issued. self.cmd_total = 0 #self.channels_subscribed = {} SESSIONS.add_unloggedin_session(self) # calling hook self.at_connect() def session_login(self, player): """ Startup mechanisms that need to run at login player - the connected player """ # Check if this is the first time the *player* logs in if player.db.FIRST_LOGIN: player.at_first_login() del player.db.FIRST_LOGIN player.at_pre_login() character = player.character if character: # this player has a character. Check if it's the # first time *this character* logs in if character.db.FIRST_LOGIN: character.at_first_login() del character.db.FIRST_LOGIN # run character login hook character.at_pre_login() # actually do the login by assigning session data self.player = player self.user = player.user self.uid = self.user.id self.name = self.user.username self.logged_in = True self.conn_time = time.time() # Update account's last login time. self.user.last_login = datetime.now() self.user.save() self.log('Logged in: %s' % self) # start (persistent) scripts on this object reloads.reload_scripts(obj=self.player.character, init_mode=True) #add session to connected list SESSIONS.add_loggedin_session(self) #call login hook self.at_login(player) # post-login hooks player.at_post_login() if character: character.at_post_login() def session_disconnect(self): """ Clean up the session, removing it from the game and doing some accounting. This method is used also for non-loggedin accounts. Note that this methods does not close the connection - this is protocol-dependent and have to be done right after this function! """ if self.logged_in: player = self.get_player() uaccount = player.user uaccount.last_login = datetime.now() uaccount.save() self.at_disconnect() self.logged_in = False SESSIONS.remove_session(self) def session_validate(self): """ Validate the session to make sure they have not been idle for too long """ if IDLE_TIMEOUT > 0 and (time.time() - self.cmd_last) > IDLE_TIMEOUT: self.msg("Idle timeout exceeded, disconnecting.") self.session_disconnect() def get_player(self): """ Get the player associated with this session """ if self.logged_in: return self.player else: return None # if self.logged_in: # character = ObjectDB.objects.get_object_with_user(self.uid) # if not character: # string = "No player match for session uid: %s" % self.uid # logger.log_errmsg(string) # return None # return character.player # return None def get_character(self): """ Returns the in-game character associated with a session. This returns the typeclass of the object. """ player = self.get_player() if player: return player.character return None def log(self, message, channel=True): """ Emits session info to the appropriate outputs and info channels. """ if channel: try: cchan = settings.CHANNEL_CONNECTINFO cchan = Channel.objects.get_channel(cchan[0]) cchan.msg("[%s]: %s" % (cchan.key, message)) except Exception: pass logger.log_infomsg(message) def update_session_counters(self, idle=False): """ Hit this when the user enters a command in order to update idle timers and command counters. """ # Store the timestamp of the user's last command. self.cmd_last = time.time() if not idle: # Increment the user's command counter. self.cmd_total += 1 # Player-visible idle time, not used in idle timeout calcs. self.cmd_last_visible = time.time() def execute_cmd(self, command_string): """ Execute a command string. """ # handle the 'idle' command if str(command_string).strip() == IDLE_COMMAND: self.update_session_counters(idle=True) return # all other inputs, including empty inputs character = self.get_character() if character: # normal operation. character.execute_cmd(command_string) #import cProfile #cProfile.runctx("character.execute_cmd(command_string)", # {"command_string":command_string,"character":character}, {}, "execute_cmd.profile") else: if self.logged_in: # there is no character, but we are logged in. Use player instead. self.get_player().execute_cmd(command_string) else: # we are not logged in. Use special unlogged-in call. cmdhandler.cmdhandler(self, command_string, unloggedin=True) self.update_session_counters() def get_data_obj(self, **kwargs): """ Create a data object, storing keyword arguments on itself as arguments. """ return IOdata(**kwargs) def __eq__(self, other): return self.address == other.address def __str__(self): """ String representation of the user session class. We use this a lot in the server logs. """ if self.logged_in: symbol = '#' else: symbol = '?' return "<%s> %s@%s" % (symbol, self.name, self.address,) def __unicode__(self): """ Unicode representation """ return u"%s" % str(self) #------------------------------------------------------------ # Session class - inherit from this #------------------------------------------------------------ class Session(SessionBase): """ The main class to inherit from. Overload the methods here. """ # exchange this for a unique name you can use to identify the # protocol type this session uses protocol_key = "TemplateProtocol" # # Hook methods # def at_connect(self): """ This method is called by the connection mechanic after connection has been made. The session is added to the sessionhandler and basic accounting has been made at this point. This is the place to put e.g. welcome screens specific to the protocol. """ pass def at_login(self, player): """ This method is called by the login mechanic whenever the user has finished authenticating. The user has been moved to the right sessionhandler list and basic book keeping has been done at this point (so logged_in=True). """ pass def at_disconnect(self): """ This method is called just before cleaning up the session (so still logged_in=True at this point). This method should not be called from commands, instead it is called automatically by session_disconnect() as part of the cleanup. This method MUST call the protocol-dependant disconnect-handler with step=2 to finalize the closing of the connection! """ # self.my-disconnect-handler(step=2) pass def at_data_in(self, string="", data=None): """ Player -> Evennia """ pass def at_data_out(self, string="", data=None): """ Evennia -> Player string - an string of any form to send to the player data - a data structure of any form """ pass # easy-access functions def login(self, player): "alias for at_login" self.at_login(player) def disconnect(self): "alias for session_disconnect" self.session_disconnect() def msg(self, string='', data=None): "alias for at_data_out" self.at_data_out(string, data=data)