def __init__(self, terminal, sid, env, child_pipes, kind, addrport, matrix_args, matrix_kwargs): # pylint: disable=R0913 # Too many arguments """ Instantiate a Session. Only one session may be instantiated per process. :param blessed.Terminal terminal: interactive terminal associated with this session. :param str sid: session identification string :param dict env: transport-negotiated environment variables, should contain at least values for TERM and 'encoding'. :param tuple child_pipes: tuple of ``(writer, reader)``. :param str kind: transport description string (ssh, telnet) :param str addrport: transport ip address and port as string :param tuple matrix_args: When non-None, a tuple of positional arguments passed to the matrix script. :param dict matrix_kwargs: When non-None, a dictionary of keyword arguments passed to the matrix script. """ self.log = logging.getLogger(__name__) # pylint: disable=W0603 # Using the global statement # Manage `SESSION' per-process singleton. global SESSION assert SESSION is None, 'Only one Session per process allowed' SESSION = self # public attributes self.terminal = terminal self.sid = sid self.env = env self.writer, self.reader = child_pipes self.kind = kind self.addrport = addrport # initialize keyboard encoding terminal.set_keyboard_decoder(env['encoding']) # private attributes self._script_stack = [ # Initialize the "script stack" with the matrix script # using the default configuration value of `script' for all # connections, but preferring `script_{kind}', where `kind' # may be `telnet', `ssh', or any kind of supporting transport. Script( name=get_ini('matrix', 'script_{self.kind}'.format(self=self)) or get_ini('matrix', 'script'), args=matrix_args, kwargs=matrix_kwargs) ] self._connect_time = time.time() self._last_input_time = time.time() self._node = None # create event buffer self._buffer = dict()
def buffer_event(self, event, data=None): """ Buffer and handle IPC data keyed by ``event``. :param str event: event name. :param data: event data. :rtype: bool :returns: True if the event was internally handled, and the caller should take no further action. Methods internally handled by this method: - ``global``: events where the first index of ``data`` is ``AYT``. This is sent by other sessions using the ``broadcast`` event, to discover "who is online". - ``info-req``: Where the first data value is the remote session-id that requested it, expecting a return value event of ``info-ack`` whose data values is a dictionary describing a session. This is an extension of the "who is online" event described above. - ``gosub``: Allows one session to send another to a different script, this is used by the default board ``chat.py`` for a chat request. """ # exceptions aren't buffered; they are thrown! if event == 'exception': # pylint: disable=E0702 # Raising NoneType while only classes, (..) allowed raise data # these callback-responsive session events should be handled by # another method, or by a configurable 'event: callback' registration # system. # respond to global 'AYT' requests if event == 'global' and data[0] == 'AYT': reply_to = data[1] self.send_event('route', ( reply_to, 'ACK', self.sid, self.user.handle, )) return True # accept 'gosub' as a literal command to run a new script directly # from this buffer_event method. I'm sure it's fine ... if event == 'gosub': save_activity = self.activity self.log.info('event-driven gosub: {0}'.format(data)) try: self.runscript(Script(*data)) finally: self.activity = save_activity # RECURSIVE: we call buffer_event to push-in a duplicate # "resize" event, so the script that was interrupted has # an opportunity to adjust to the new terminal dimensions # (if any), or in the case of subpar clients such as netrunner # and syncterm that do not handle "alt screen" correctly, # it forces a refresh in the interrupted script, though there # aren't any guarantees. # Otherwise, it is fine to not require the calling function to # refresh -- so long as the target script makes sure(!) to # use the "with term.fullscreen()" context manager. data = ('resize', ( self.terminal.height, self.terminal.width, )) self.buffer_event('refresh', data) return True # respond to 'info-req' events by returning pickled session info if event == 'info-req': self.send_event( 'route', ( # data[0] is the session-id to return the reply to. data[0], 'info-ack', self.sid, self.to_dict())) return True if event not in self._buffer: # " Once a bounded length deque is full, when new items are added, # a corresponding number of items are discarded from the opposite # end." -- global events are meant to be disregarded, so a much # shorter queue length is used. only the foremost refresh event is # important in the case of screen resize. self._buffer[event] = collections.deque(maxlen={ 'global': 128, 'refresh': 1, }.get(event, 65534)) # buffer input if event == 'input': self.buffer_input(data) return False # buffer only 1 most recent 'refresh' event if event == 'refresh': if data[0] == 'resize': # inherit terminal dimensions values (self.terminal._columns, self.terminal._rows) = data[1] # buffer all else self._buffer[event].appendleft(data) return False
def gosub(script, *args, **kwargs): """ Call bbs script with optional arguments, Returns value. """ script = Script(name=script, args=args, kwargs=kwargs) return getsession().runscript(script)
def __init__(self, script, *args, **kwargs): self.value = Script(name=script, args=args, kwargs=kwargs)