text_buffer.py 6.87 KB
Newer Older
1 2
"""
Define the TextBuffer class
3 4 5 6 7 8

A text buffer contains a list of intermediate representations of messages
(not xml stanzas, but neither the Lines used in windows.py.

Each text buffer can be linked to multiple windows, that will be rendered
independantly by their TextWins.
9 10
"""

11 12 13
import logging
log = logging.getLogger(__name__)

14 15
import collections

16
from datetime import datetime
17
from config import config
18
from theming import get_theme, dump_tuple
19

20 21
message_fields = ('txt nick_color time str_time nickname user identifier'
                  ' highlight me old_message revisions jid')
22 23
Message = collections.namedtuple('Message', message_fields)

mathieui's avatar
mathieui committed
24 25
class CorrectionError(Exception):
    pass
mathieui's avatar
mathieui committed
26

27
def other_elems(self):
28
    "Helper for the repr_message function"
29 30 31 32
    acc = ['Message(']
    fields = message_fields.split()
    fields.remove('old_message')
    for field in fields:
33
        acc.append('%s=%s' % (field, repr(getattr(self, field))))
mathieui's avatar
mathieui committed
34
    return ', '.join(acc) + ', old_message='
35 36

def repr_message(self):
37 38 39 40 41
    """
    repr() for the Message class, for debug purposes, since the default
    repr() is recursive, so it can stack overflow given too many revisions
    of a message
    """
42
    init = other_elems(self)
mathieui's avatar
mathieui committed
43
    acc = [init]
mathieui's avatar
mathieui committed
44
    next_message = self.old_message
mathieui's avatar
mathieui committed
45
    rev = 1
mathieui's avatar
mathieui committed
46 47 48
    while next_message:
        acc.append(other_elems(next_message))
        next_message = next_message.old_message
49 50 51 52 53 54 55 56 57
        rev += 1
    acc.append('None')
    while rev:
        acc.append(')')
        rev -= 1
    return ''.join(acc)

Message.__repr__ = repr_message
Message.__str__ = repr_message
58

59 60 61 62 63
class TextBuffer(object):
    """
    This class just keep trace of messages, in a list with various
    informations and attributes.
    """
64 65 66 67
    def __init__(self, messages_nb_limit=None):

        if messages_nb_limit is None:
            messages_nb_limit = config.get('max_messages_in_memory', 2048)
68
        self.messages_nb_limit = messages_nb_limit
69 70 71
        # Message objects
        self.messages = []
        # we keep track of one or more windows
72
        # so we can pass the new messages to them, as they are added, so
73
        # they (the windows) can build the lines from the new message
74
        self.windows = []
75 76 77

    def add_window(self, win):
        self.windows.append(win)
78

79 80 81 82 83
    @property
    def last_message(self):
        return self.messages[-1] if self.messages else None


mathieui's avatar
mathieui committed
84
    @staticmethod
85 86 87 88 89 90 91
    def make_message(txt, time, nickname, nick_color, history, user,
                     identifier, str_time=None, highlight=False,
                     old_message=None, revisions=0, jid=None):
        """
        Create a new Message object with parameters, check for /me messages,
        and delayed messages
        """
louiz’'s avatar
louiz’ committed
92
        time = time or datetime.now()
louiz’'s avatar
louiz’ committed
93
        if txt.startswith('/me '):
94
            me = True
95 96 97 98
            txt = '\x19%s}%s' % (dump_tuple(get_theme().COLOR_ME_MESSAGE),
                                 txt[4:])
        else:
            me = False
99
        if history:
100 101 102 103 104 105 106 107 108
            txt = txt.replace('\x19o', '\x19o\x19%s}' %
                                dump_tuple(get_theme().COLOR_LOG_MSG))
            str_time = time.strftime("%Y-%m-%d %H:%M:%S")
        else:
            if str_time is None:
                str_time = time.strftime("%H:%M:%S")
            else:
                str_time = ''

109 110 111 112
        msg = Message(
                txt='%s\x19o'%(txt.replace('\t', '    '),),
                nick_color=nick_color,
                time=time,
113
                str_time=str_time,
114 115 116 117 118
                nickname=nickname,
                user=user,
                identifier=identifier,
                highlight=highlight,
                me=me,
119
                old_message=old_message,
120 121
                revisions=revisions,
                jid=jid)
122
        log.debug('Set message %s with %s.', identifier, msg)
123 124
        return msg

125 126 127 128 129 130 131 132 133
    def add_message(self, txt, time=None, nickname=None,
                    nick_color=None, history=None, user=None, highlight=False,
                    identifier=None, str_time=None, jid=None):
        """
        Create a message and add it to the text buffer
        """
        msg = self.make_message(txt, time, nickname, nick_color, history,
                                user, identifier, str_time=str_time,
                                highlight=highlight, jid=jid)
134
        self.messages.append(msg)
135

136
        while len(self.messages) > self.messages_nb_limit:
137
            self.messages.pop(0)
138

139
        ret_val = None
140
        show_timestamps = config.get('show_timestamps', True)
141
        for window in self.windows: # make the associated windows
142 143 144 145
                                    # build the lines from the new message
            nb = window.build_new_message(msg, history=history,
                                          highlight=highlight,
                                          timestamp=show_timestamps)
146 147
            if ret_val is None:
                ret_val = nb
148 149
            if window.pos != 0:
                window.scroll_up(nb)
150

151
        return ret_val or 1
152

153 154 155 156 157 158
    def modify_message(self, txt, old_id, new_id, highlight=False,
                       time=None, user=None, jid=None):
        """
        Correct a message in a text buffer.
        """

159 160
        for i in range(len(self.messages) -1, -1, -1):
            msg = self.messages[i]
161

162
            if msg.identifier == old_id:
mathieui's avatar
mathieui committed
163
                if msg.user and msg.user is not user:
mathieui's avatar
mathieui committed
164
                    raise CorrectionError("Different users")
mathieui's avatar
mathieui committed
165
                elif len(msg.str_time) > 8: # ugly
mathieui's avatar
mathieui committed
166
                    raise CorrectionError("Delayed message")
167
                elif not msg.user and (msg.jid is None or jid is None):
168 169
                    raise CorrectionError('Could not check the '
                                          'identity of the sender')
170
                elif not msg.user and msg.jid != jid:
171 172 173 174 175 176 177 178 179 180 181 182
                    raise CorrectionError('Messages %s and %s have not been '
                                          'sent by the same fullJID' %
                                              (old_id, new_id))

                if not time:
                    time = msg.time
                message = self.make_message(txt, time, msg.nickname,
                                            msg.nick_color, None, msg.user,
                                            new_id, highlight=highlight,
                                            old_message=msg,
                                            revisions=msg.revisions + 1,
                                            jid=jid)
183
                self.messages[i] = message
184
                log.debug('Replacing message %s with %s.', old_id, new_id)
185
                return message
186 187
        log.debug('Message %s not found in text_buffer, abort replacement.',
                  old_id)
mathieui's avatar
mathieui committed
188
        raise CorrectionError("nothing to replace")
189

190 191 192 193
    def del_window(self, win):
        self.windows.remove(win)

    def __del__(self):
194 195
        size = len(self.messages)
        log.debug('** Deleting %s messages from textbuffer', size)