session.py 12.7 KB
Newer Older
Philipp Hörist's avatar
Philipp Hörist committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
#                    Jonathan Schleifer <js-gajim AT webkeks.org>
#                    Stephan Erb <steve-e AT h3c.de>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
roidelapluie's avatar
roidelapluie committed
19

20 21 22 23
import string
import random
import itertools

André's avatar
André committed
24 25
from gajim.common import helpers
from gajim.common import events
26
from gajim.common import app
André's avatar
André committed
27 28
from gajim.common import contacts
from gajim.common import ged
29
from gajim.common.helpers import AdditionalDataDict
Philipp Hörist's avatar
Philipp Hörist committed
30
from gajim.common.const import KindConstant
Philipp Hörist's avatar
Philipp Hörist committed
31 32
from gajim.gtk.util import get_show_in_roster
from gajim.gtk.util import get_show_in_systray
33

34

35
class ChatControlSession:
36 37 38 39 40
    def __init__(self, conn, jid, thread_id, type_='chat'):
        self.conn = conn
        self.jid = jid
        self.type_ = type_
        self.resource = jid.getResource()
41 42
        self.control = None

43 44 45 46 47 48 49 50 51 52 53
        if thread_id:
            self.received_thread_id = True
            self.thread_id = thread_id
        else:
            self.received_thread_id = False
            if type_ == 'normal':
                self.thread_id = None
            else:
                self.thread_id = self.generate_thread_id()

        self.loggable = True
54

55 56
        self.last_send = 0
        self.last_receive = 0
57

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
        app.ged.register_event_handler('decrypted-message-received',
                                       ged.PREGUI,
                                       self._nec_decrypted_message_received)

    def generate_thread_id(self):
        return ''.join(
            [f(string.ascii_letters) for f in itertools.repeat(
                random.choice, 32)]
        )

    def is_loggable(self):
        return app.config.should_log(self.conn.name,
                                     self.jid.getStripped())

    def get_to(self):
Philipp Hörist's avatar
Philipp Hörist committed
73 74 75 76
        bare_jid = self.jid.getBare()
        if not self.resource:
            return bare_jid
        return bare_jid + '/' + self.resource
77

78
    def _nec_decrypted_message_received(self, obj):
79 80 81
        """
        Dispatch a received <message> stanza
        """
82 83
        if obj.session != self:
            return
84

85
        if obj.properties.is_muc_pm:
86 87 88 89 90
            contact = app.contacts.get_gc_contact(
                self.conn.name, obj.jid, obj.resource)
        else:
            contact = app.contacts.get_contact(
                self.conn.name, obj.jid, obj.resource)
91 92
        if self.resource != obj.resource:
            self.resource = obj.resource
93
            if self.control:
94 95 96 97 98
                if isinstance(contact, contacts.GC_Contact):
                    self.control.gc_contact = contact
                    self.control.contact = contact.as_contact()
                else:
                    self.control.contact = contact
99 100
                if self.control.resource:
                    self.control.change_resource(self.resource)
101

102 103
        if not obj.msgtxt:
            return
104

105
        log_type = KindConstant.CHAT_MSG_RECV
106
        if obj.properties.is_sent_carbon:
107
            log_type = KindConstant.CHAT_MSG_SENT
108

109 110
        if self.is_loggable() and obj.msgtxt:
            jid = obj.fjid
111
            if not obj.properties.is_muc_pm:
112 113
                jid = obj.jid

114
            obj.msg_log_id = app.logger.insert_into_logs(
115 116 117 118
                self.conn.name,
                jid,
                obj.properties.timestamp,
                log_type,
Philipp Hörist's avatar
Philipp Hörist committed
119
                message=obj.msgtxt,
120
                subject=obj.properties.subject,
121
                additional_data=obj.additional_data,
Philipp Hörist's avatar
Philipp Hörist committed
122
                stanza_id=obj.unique_id,
123
                message_id=obj.properties.id)
124

125
        if obj.properties.is_muc_pm and not obj.gc_control:
126 127 128 129
            # This is a carbon of a PM from a MUC we are not currently
            # joined. We log it silently without notification.
            return True

130
        if not obj.msgtxt: # empty message text
131
            return True
132 133

        if not self.control:
134
            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
135
                obj.conn.name, obj.resource)
136 137 138
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
139 140 141 142 143
                if isinstance(contact, contacts.GC_Contact):
                    self.control.gc_contact = contact
                    self.control.contact = contact.as_contact()
                else:
                    self.control.contact = contact
144

145
        if not obj.properties.is_muc_pm:
146
            self.roster_message2(obj)
147

148 149 150 151 152 153 154 155 156 157 158 159 160
    def roster_message2(self, obj):
        """
        Display the message or show notification in the roster
        """
        contact = None
        jid = obj.jid
        resource = obj.resource

        fjid = jid

        # Try to catch the contact with correct resource
        if resource:
            fjid = jid + '/' + resource
161
            contact = app.contacts.get_contact(obj.conn.name, jid, resource)
162

163
        highest_contact = app.contacts.get_contact_with_highest_priority(
164 165 166 167
            obj.conn.name, jid)
        if not contact:
            # If there is another resource, it may be a message from an
            # invisible resource
168
            lcontact = app.contacts.get_contacts(obj.conn.name, jid)
169 170
            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
            lcontact[0].show != 'offline')) and jid.find('@') > 0:
171
                contact = app.contacts.copy_contact(highest_contact)
172 173 174 175
                contact.resource = resource
                contact.priority = 0
                contact.show = 'offline'
                contact.status = ''
176
                app.contacts.add_contact(obj.conn.name, contact)
177 178 179 180 181 182 183 184

            else:
                # Default to highest prio
                fjid = jid
                contact = highest_contact

        if not contact:
            # contact is not in roster
185
            contact = app.interface.roster.add_to_not_in_the_roster(
186
                obj.conn.name, jid, obj.properties.nickname)
187 188

        if not self.control:
189
            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
190
                obj.conn.name, obj.resource)
191 192 193 194
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
            else:
195
                fjid = jid
196 197 198

        obj.popup = helpers.allow_popup_window(self.conn.name)

199
        event_t = events.ChatEvent
200 201
        event_type = 'message_received'

202
        if self.control:
203 204 205
            # We have a ChatControl open
            obj.show_in_roster = False
            obj.show_in_systray = False
206
            do_event = False
207
        elif obj.properties.is_sent_carbon:
208
            # Its a Carbon Copied Message we sent
209 210
            obj.show_in_roster = False
            obj.show_in_systray = False
211 212 213 214 215 216
            unread_events = app.events.get_events(
                self.conn.name, fjid, types=['chat'])
            read_ids = []
            for msg in unread_events:
                read_ids.append(msg.msg_log_id)
            app.logger.set_read_messages(read_ids)
217
            app.events.remove_events(self.conn.name, fjid, types=['chat'])
218
            do_event = False
219
        else:
220
            # Everything else
Philipp Hörist's avatar
Philipp Hörist committed
221 222
            obj.show_in_roster = get_show_in_roster(event_type, self)
            obj.show_in_systray = get_show_in_systray(event_type, contact.jid)
223
            do_event = True
224
        if do_event:
225
            kind = obj.properties.type.value
226 227 228
            event = event_t(
                obj.msgtxt,
                obj.properties.subject,
229 230
                kind,
                obj.properties.timestamp,
231 232
                obj.resource,
                obj.msg_log_id,
233
                correct_id=obj.correct_id,
234
                message_id=obj.properties.id,
235
                session=self,
236
                displaymarking=obj.displaymarking,
237
                sent_forwarded=obj.properties.is_sent_carbon,
238
                show_in_roster=obj.show_in_roster,
239 240
                show_in_systray=obj.show_in_systray,
                additional_data=obj.additional_data)
241

242
            app.events.add_event(self.conn.name, fjid, event)
243

244
    def roster_message(self, jid, msg, tim, msg_type='',
Philipp Hörist's avatar
Philipp Hörist committed
245
    subject=None, resource='', msg_log_id=None, user_nick='',
246
    displaymarking=None, additional_data=None):
247 248 249 250 251 252
        """
        Display the message or show notification in the roster
        """
        contact = None
        fjid = jid

253
        if additional_data is None:
254
            additional_data = AdditionalDataDict()
255

256 257 258
        # Try to catch the contact with correct resource
        if resource:
            fjid = jid + '/' + resource
259
            contact = app.contacts.get_contact(self.conn.name, jid, resource)
260

261
        highest_contact = app.contacts.get_contact_with_highest_priority(
262 263 264 265
                self.conn.name, jid)
        if not contact:
            # If there is another resource, it may be a message from an invisible
            # resource
266
            lcontact = app.contacts.get_contacts(self.conn.name, jid)
267 268
            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
            lcontact[0].show != 'offline')) and jid.find('@') > 0:
269
                contact = app.contacts.copy_contact(highest_contact)
270 271 272 273 274 275
                contact.resource = resource
                if resource:
                    fjid = jid + '/' + resource
                contact.priority = 0
                contact.show = 'offline'
                contact.status = ''
276
                app.contacts.add_contact(self.conn.name, contact)
277 278 279 280 281 282 283 284

            else:
                # Default to highest prio
                fjid = jid
                contact = highest_contact

        if not contact:
            # contact is not in roster
285
            contact = app.interface.roster.add_to_not_in_the_roster(
286 287 288
                    self.conn.name, jid, user_nick)

        if not self.control:
289
            ctrl = app.interface.msg_win_mgr.get_control(fjid, self.conn.name)
290 291 292 293
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
            else:
294
                fjid = jid
295 296

        # Do we have a queue?
297
        no_queue = len(app.events.get_events(self.conn.name, fjid)) == 0
298

299
        popup = helpers.allow_popup_window(self.conn.name)
300 301

        # We print if window is opened and it's not a single message
302
        if self.control:
303 304 305 306 307
            typ = ''

            if msg_type == 'error':
                typ = 'error'

Philipp Hörist's avatar
Philipp Hörist committed
308 309 310 311 312 313
            self.control.add_message(msg,
                                     typ,
                                     tim=tim,
                                     subject=subject,
                                     displaymarking=displaymarking,
                                     additional_data=additional_data)
314

315
            if msg_log_id:
316
                app.logger.set_read_messages([msg_log_id])
317 318 319 320

            return

        # We save it in a queue
321
        event_t = events.ChatEvent
322 323
        event_type = 'message_received'

Philipp Hörist's avatar
Philipp Hörist committed
324 325
        show_in_roster = get_show_in_roster(event_type, self)
        show_in_systray = get_show_in_systray(event_type, contact.jid)
326

327
        event = event_t(msg, subject, msg_type, tim, resource,
Philipp Hörist's avatar
Philipp Hörist committed
328
            msg_log_id, session=self,
329
            displaymarking=displaymarking, sent_forwarded=False,
330 331
            show_in_roster=show_in_roster, show_in_systray=show_in_systray,
            additional_data=additional_data)
332

333
        app.events.add_event(self.conn.name, fjid, event)
334 335 336

        if popup:
            if not self.control:
337
                self.control = app.interface.new_chat(contact,
338
                    self.conn.name, session=self)
339

340
                if app.events.get_events(self.conn.name, fjid):
341 342 343
                    self.control.read_queue()
        else:
            if no_queue: # We didn't have a queue: we change icons
344
                app.interface.roster.draw_contact(jid, self.conn.name)
345

346
            app.interface.roster.show_title() # we show the * or [n]
347 348
        # Select the big brother contact in roster, it's visible because it has
        # events.
349
        family = app.contacts.get_metacontacts_family(self.conn.name, jid)
350
        if family:
351
            _nearby_family, bb_jid, bb_account = \
352
                    app.contacts.get_nearby_family_and_big_brother(family,
353 354 355
                    self.conn.name)
        else:
            bb_jid, bb_account = jid, self.conn.name
356
        app.interface.roster.select_contact(bb_jid, bb_account)