1# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
2# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
3#                    Jonathan Schleifer <js-gajim AT webkeks.org>
4#                    Stephan Erb <steve-e AT h3c.de>
5#
6# This file is part of Gajim.
7#
8# Gajim is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published
10# by the Free Software Foundation; version 3 only.
11#
12# Gajim is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
19
20import string
21import random
22import itertools
23
24from gajim.common import helpers
25from gajim.common import events
26from gajim.common import app
27from gajim.common import contacts
28from gajim.common import ged
29from gajim.common.helpers import AdditionalDataDict
30from gajim.common.const import KindConstant
31from gajim.gui.util import get_show_in_roster
32from gajim.gui.util import get_show_in_systray
33
34
35class ChatControlSession:
36    def __init__(self, conn, jid, thread_id, type_='chat'):
37        self.conn = conn
38        self.jid = jid
39        self.type_ = type_
40        self.resource = jid.resource
41        self.control = None
42
43        if thread_id:
44            self.received_thread_id = True
45            self.thread_id = thread_id
46        else:
47            self.received_thread_id = False
48            if type_ == 'normal':
49                self.thread_id = None
50            else:
51                self.thread_id = self.generate_thread_id()
52
53        self.loggable = True
54
55        self.last_send = 0
56        self.last_receive = 0
57
58        app.ged.register_event_handler('decrypted-message-received',
59                                       ged.PREGUI,
60                                       self._nec_decrypted_message_received)
61
62    def generate_thread_id(self):
63        return ''.join(
64            [f(string.ascii_letters) for f in itertools.repeat(
65                random.choice, 32)]
66        )
67
68    def is_loggable(self):
69        return helpers.should_log(self.conn.name, self.jid.bare)
70
71    def get_to(self):
72        bare_jid = self.jid.bare
73        if not self.resource:
74            return bare_jid
75        return bare_jid + '/' + self.resource
76
77    def _nec_decrypted_message_received(self, obj):
78        """
79        Dispatch a received <message> stanza
80        """
81        if obj.session != self:
82            return
83
84        if obj.properties.is_muc_pm:
85            contact = app.contacts.get_gc_contact(
86                self.conn.name, obj.jid, obj.resource)
87        else:
88            contact = app.contacts.get_contact(
89                self.conn.name, obj.jid, obj.resource)
90        if self.resource != obj.resource:
91            self.resource = obj.resource
92            if self.control:
93                if isinstance(contact, contacts.GC_Contact):
94                    self.control.gc_contact = contact
95                    self.control.contact = contact.as_contact()
96                else:
97                    self.control.contact = contact
98                if self.control.resource:
99                    self.control.change_resource(self.resource)
100
101        if not obj.msgtxt:
102            return
103
104        log_type = KindConstant.CHAT_MSG_RECV
105        if obj.properties.is_sent_carbon:
106            log_type = KindConstant.CHAT_MSG_SENT
107
108        if self.is_loggable() and obj.msgtxt:
109            jid = obj.fjid
110            if not obj.properties.is_muc_pm:
111                jid = obj.jid
112
113            obj.msg_log_id = app.storage.archive.insert_into_logs(
114                self.conn.name,
115                jid,
116                obj.properties.timestamp,
117                log_type,
118                message=obj.msgtxt,
119                subject=obj.properties.subject,
120                additional_data=obj.additional_data,
121                stanza_id=obj.unique_id,
122                message_id=obj.properties.id)
123
124        if obj.properties.is_muc_pm and not obj.gc_control:
125            # This is a carbon of a PM from a MUC we are not currently
126            # joined. We log it silently without notification.
127            return True
128
129        if not obj.msgtxt: # empty message text
130            return True
131
132        if not self.control:
133            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
134                obj.conn.name, obj.resource)
135            if ctrl:
136                self.control = ctrl
137                self.control.set_session(self)
138                if isinstance(contact, contacts.GC_Contact):
139                    self.control.gc_contact = contact
140                    self.control.contact = contact.as_contact()
141                else:
142                    self.control.contact = contact
143
144        if not obj.properties.is_muc_pm:
145            self.roster_message2(obj)
146
147    def roster_message2(self, obj):
148        """
149        Display the message or show notification in the roster
150        """
151        contact = None
152        jid = obj.jid
153        resource = obj.resource
154
155        fjid = jid
156
157        # Try to catch the contact with correct resource
158        if resource:
159            fjid = jid + '/' + resource
160            contact = app.contacts.get_contact(obj.conn.name, jid, resource)
161
162        highest_contact = app.contacts.get_contact_with_highest_priority(
163            obj.conn.name, jid)
164        if not contact:
165            # If there is another resource, it may be a message from an
166            # invisible resource
167            lcontact = app.contacts.get_contacts(obj.conn.name, jid)
168            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
169            lcontact[0].show != 'offline')) and jid.find('@') > 0:
170                contact = app.contacts.copy_contact(highest_contact)
171                contact.resource = resource
172                contact.priority = 0
173                contact.show = 'offline'
174                contact.status = ''
175                app.contacts.add_contact(obj.conn.name, contact)
176
177            else:
178                # Default to highest prio
179                fjid = jid
180                contact = highest_contact
181
182        if not contact:
183            # contact is not in roster
184            contact = app.interface.roster.add_to_not_in_the_roster(
185                obj.conn.name, jid, obj.properties.nickname)
186
187        if not self.control:
188            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
189                obj.conn.name, obj.resource)
190            if ctrl:
191                self.control = ctrl
192                self.control.set_session(self)
193            else:
194                fjid = jid
195
196        obj.popup = helpers.allow_popup_window(self.conn.name)
197
198        event_t = events.ChatEvent
199        event_type = 'message_received'
200
201        if self.control:
202            # We have a ChatControl open
203            obj.show_in_roster = False
204            obj.show_in_systray = False
205            do_event = False
206        elif obj.properties.is_sent_carbon:
207            # Its a Carbon Copied Message we sent
208            obj.show_in_roster = False
209            obj.show_in_systray = False
210            unread_events = app.events.get_events(
211                self.conn.name, fjid, types=['chat'])
212            read_ids = []
213            for msg in unread_events:
214                read_ids.append(msg.msg_log_id)
215            app.storage.archive.set_read_messages(read_ids)
216            app.events.remove_events(self.conn.name, fjid, types=['chat'])
217            do_event = False
218        else:
219            # Everything else
220            obj.show_in_roster = get_show_in_roster(event_type, self)
221            obj.show_in_systray = get_show_in_systray(event_type,
222                                                      obj.conn.name,
223                                                      contact.jid)
224            do_event = True
225        if do_event:
226            kind = obj.properties.type.value
227            event = event_t(
228                obj.msgtxt,
229                obj.properties.subject,
230                kind,
231                obj.properties.timestamp,
232                obj.resource,
233                obj.msg_log_id,
234                correct_id=obj.correct_id,
235                message_id=obj.properties.id,
236                session=self,
237                displaymarking=obj.displaymarking,
238                sent_forwarded=obj.properties.is_sent_carbon,
239                show_in_roster=obj.show_in_roster,
240                show_in_systray=obj.show_in_systray,
241                additional_data=obj.additional_data)
242
243            app.events.add_event(self.conn.name, fjid, event)
244
245    def roster_message(self, jid, msg, tim, msg_type='',
246    subject=None, resource='', msg_log_id=None, user_nick='',
247    displaymarking=None, additional_data=None):
248        """
249        Display the message or show notification in the roster
250        """
251        contact = None
252        fjid = jid
253
254        if additional_data is None:
255            additional_data = AdditionalDataDict()
256
257        # Try to catch the contact with correct resource
258        if resource:
259            fjid = jid + '/' + resource
260            contact = app.contacts.get_contact(self.conn.name, jid, resource)
261
262        highest_contact = app.contacts.get_contact_with_highest_priority(
263                self.conn.name, jid)
264        if not contact:
265            # If there is another resource, it may be a message from an invisible
266            # resource
267            lcontact = app.contacts.get_contacts(self.conn.name, jid)
268            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
269            lcontact[0].show != 'offline')) and jid.find('@') > 0:
270                contact = app.contacts.copy_contact(highest_contact)
271                contact.resource = resource
272                if resource:
273                    fjid = jid + '/' + resource
274                contact.priority = 0
275                contact.show = 'offline'
276                contact.status = ''
277                app.contacts.add_contact(self.conn.name, contact)
278
279            else:
280                # Default to highest prio
281                fjid = jid
282                contact = highest_contact
283
284        if not contact:
285            # contact is not in roster
286            contact = app.interface.roster.add_to_not_in_the_roster(
287                    self.conn.name, jid, user_nick)
288
289        if not self.control:
290            ctrl = app.interface.msg_win_mgr.get_control(fjid, self.conn.name)
291            if ctrl:
292                self.control = ctrl
293                self.control.set_session(self)
294            else:
295                fjid = jid
296
297        # Do we have a queue?
298        no_queue = len(app.events.get_events(self.conn.name, fjid)) == 0
299
300        popup = helpers.allow_popup_window(self.conn.name)
301
302        # We print if window is opened and it's not a single message
303        if self.control:
304            typ = ''
305
306            if msg_type == 'error':
307                typ = 'error'
308
309            self.control.add_message(msg,
310                                     typ,
311                                     tim=tim,
312                                     subject=subject,
313                                     displaymarking=displaymarking,
314                                     additional_data=additional_data)
315
316            if msg_log_id:
317                app.storage.archive.set_read_messages([msg_log_id])
318
319            return
320
321        # We save it in a queue
322        event_t = events.ChatEvent
323        event_type = 'message_received'
324
325        show_in_roster = get_show_in_roster(event_type, self)
326        show_in_systray = get_show_in_systray(event_type,
327                                              self.conn.name,
328                                              contact.jid)
329
330        event = event_t(msg, subject, msg_type, tim, resource,
331            msg_log_id, session=self,
332            displaymarking=displaymarking, sent_forwarded=False,
333            show_in_roster=show_in_roster, show_in_systray=show_in_systray,
334            additional_data=additional_data)
335
336        app.events.add_event(self.conn.name, fjid, event)
337
338        if popup:
339            if not self.control:
340                self.control = app.interface.new_chat(contact,
341                    self.conn.name, session=self)
342
343                if app.events.get_events(self.conn.name, fjid):
344                    self.control.read_queue()
345        else:
346            if no_queue: # We didn't have a queue: we change icons
347                app.interface.roster.draw_contact(jid, self.conn.name)
348
349            app.interface.roster.show_title() # we show the * or [n]
350        # Select the big brother contact in roster, it's visible because it has
351        # events.
352        family = app.contacts.get_metacontacts_family(self.conn.name, jid)
353        if family:
354            _nearby_family, bb_jid, bb_account = \
355                    app.contacts.get_nearby_family_and_big_brother(family,
356                    self.conn.name)
357        else:
358            bb_jid, bb_account = jid, self.conn.name
359        app.interface.roster.select_contact(bb_jid, bb_account)
360