Beispiel #1
0
    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()
Beispiel #2
0
    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
Beispiel #3
0
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)
Beispiel #4
0
 def __init__(self, script, *args, **kwargs):
     self.value = Script(name=script, args=args, kwargs=kwargs)