1# Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
2#                    Nikos Kouremenos <kourem AT gmail.com>
3# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
4# Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
5# Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
6# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
7#                    Jonathan Schleifer <js-gajim AT webkeks.org>
8#
9# This file is part of Gajim.
10#
11# Gajim is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published
13# by the Free Software Foundation; version 3 only.
14#
15# Gajim is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
22
23import time
24
25from gajim.common import app
26
27
28class Event:
29    """
30    Information concerning each event
31    """
32
33    def __init__(self, time_=None, show_in_roster=False, show_in_systray=True):
34        """
35        type_ in chat, normal, file-request, file-error, file-completed,
36        file-request-error, file-send-error, file-stopped, gc_msg, pm,
37        printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
38        gc-invitation, subscription_request, unsubscribedm jingle-incoming
39
40        parameters is (per type_):
41                chat, normal, pm: [message, subject, kind, time, encrypted, resource,
42                msg_log_id]
43                        where kind in error, incoming
44                file-*: file_props
45                gc_msg: None
46                printed_chat: [message, subject, control, msg_log_id]
47                printed_*: None
48                        messages that are already printed in chat, but not read
49                gc-invitation: [room_jid, reason, password, jid_from]
50                subscription_request: [text, nick]
51                unsubscribed: contact
52                jingle-incoming: (fulljid, sessionid, content_types)
53        """
54        if time_:
55            self.time_ = time_
56        else:
57            self.time_ = time.time()
58        self.show_in_roster = show_in_roster
59        self.show_in_systray = show_in_systray
60        # Set when adding the event
61        self.jid = None
62        self.account = None
63
64class ChatEvent(Event):
65    type_ = 'chat'
66    def __init__(self, message, subject, kind, time_, resource,
67    msg_log_id, correct_id=None, message_id=None, session=None,
68    displaymarking=None, sent_forwarded=False, show_in_roster=False,
69    show_in_systray=True, additional_data=None):
70        Event.__init__(self, time_, show_in_roster=show_in_roster,
71            show_in_systray=show_in_systray)
72        self.message = message
73        self.subject = subject
74        self.kind = kind
75        self.time = time_
76        self.resource = resource
77        self.msg_log_id = msg_log_id
78        self.message_id = message_id
79        self.correct_id = correct_id
80        self.session = session
81        self.displaymarking = displaymarking
82        self.sent_forwarded = sent_forwarded
83        if additional_data is None:
84            from gajim.common.helpers import AdditionalDataDict
85            additional_data = AdditionalDataDict()
86        self.additional_data = additional_data
87
88class PmEvent(ChatEvent):
89    type_ = 'pm'
90
91class PrintedChatEvent(Event):
92    type_ = 'printed_chat'
93    def __init__(self, message, subject, control, msg_log_id, time_=None,
94                 message_id=None, stanza_id=None, show_in_roster=False,
95                 show_in_systray=True):
96        Event.__init__(self, time_, show_in_roster=show_in_roster,
97                       show_in_systray=show_in_systray)
98        self.message = message
99        self.subject = subject
100        self.control = control
101        self.msg_log_id = msg_log_id
102        self.message_id = message_id
103        self.stanza_id = stanza_id
104
105class PrintedGcMsgEvent(PrintedChatEvent):
106    type_ = 'printed_gc_msg'
107
108class PrintedMarkedGcMsgEvent(PrintedChatEvent):
109    type_ = 'printed_marked_gc_msg'
110
111class PrintedPmEvent(PrintedChatEvent):
112    type_ = 'printed_pm'
113
114class SubscriptionRequestEvent(Event):
115    type_ = 'subscription_request'
116    def __init__(self, text, nick, time_=None, show_in_roster=False,
117    show_in_systray=True):
118        Event.__init__(self, time_, show_in_roster=show_in_roster,
119            show_in_systray=show_in_systray)
120        self.text = text
121        self.nick = nick
122
123class UnsubscribedEvent(Event):
124    type_ = 'unsubscribed'
125    def __init__(self, contact, time_=None, show_in_roster=False,
126    show_in_systray=True):
127        Event.__init__(self, time_, show_in_roster=show_in_roster,
128            show_in_systray=show_in_systray)
129        self.contact = contact
130
131class GcInvitationtEvent(Event):
132    type_ = 'gc-invitation'
133    def __init__(self, event):
134        Event.__init__(self, None, show_in_roster=False, show_in_systray=True)
135        for key, value in vars(event).items():
136            setattr(self, key, value)
137
138    def get_inviter_name(self):
139        if self.from_.bare_match(self.muc):
140            return self.from_.resource
141
142        contact = app.contacts.get_first_contact_from_jid(
143            self.account, self.from_.bare)
144        if contact is None:
145            return str(self.from_)
146        return contact.get_shown_name()
147
148class FileRequestEvent(Event):
149    type_ = 'file-request'
150    def __init__(self, file_props, time_=None, show_in_roster=False, show_in_systray=True):
151        Event.__init__(self, time_, show_in_roster=show_in_roster,
152            show_in_systray=show_in_systray)
153        self.file_props = file_props
154
155class FileSendErrorEvent(FileRequestEvent):
156    type_ = 'file-send-error'
157
158class FileErrorEvent(FileRequestEvent):
159    type_ = 'file-error'
160
161class FileRequestErrorEvent(FileRequestEvent):
162    type_ = 'file-request-error'
163
164class FileCompletedEvent(FileRequestEvent):
165    type_ = 'file-completed'
166
167class FileStoppedEvent(FileRequestEvent):
168    type_ = 'file-stopped'
169
170class FileHashErrorEvent(FileRequestEvent):
171    type_ = 'file-hash-error'
172
173class JingleIncomingEvent(Event):
174    type_ = 'jingle-incoming'
175    def __init__(self, peerjid, sid, content_types, time_=None, show_in_roster=False, show_in_systray=True):
176        Event.__init__(self, time_, show_in_roster=show_in_roster,
177            show_in_systray=show_in_systray)
178        self.peerjid = peerjid
179        self.sid = sid
180        self.content_types = content_types
181
182class Events:
183    """
184    Information concerning all events
185    """
186
187    def __init__(self):
188        self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
189        self._event_added_listeners = []
190        self._event_removed_listeners = []
191
192    def event_added_subscribe(self, listener):
193        """
194        Add a listener when an event is added to the queue
195        """
196        if not listener in self._event_added_listeners:
197            self._event_added_listeners.append(listener)
198
199    def event_added_unsubscribe(self, listener):
200        """
201        Remove a listener when an event is added to the queue
202        """
203        if listener in self._event_added_listeners:
204            self._event_added_listeners.remove(listener)
205
206    def event_removed_subscribe(self, listener):
207        """
208        Add a listener when an event is removed from the queue
209        """
210        if not listener in self._event_removed_listeners:
211            self._event_removed_listeners.append(listener)
212
213    def event_removed_unsubscribe(self, listener):
214        """
215        Remove a listener when an event is removed from the queue
216        """
217        if listener in self._event_removed_listeners:
218            self._event_removed_listeners.remove(listener)
219
220    def fire_event_added(self, event):
221        for listener in self._event_added_listeners:
222            listener(event)
223
224    def fire_event_removed(self, event_list):
225        for listener in self._event_removed_listeners:
226            listener(event_list)
227
228    def add_account(self, account):
229        self._events[account] = {}
230
231    def get_accounts(self):
232        return self._events.keys()
233
234    def remove_account(self, account):
235        del self._events[account]
236
237    def add_event(self, account, jid, event):
238        # No such account before ?
239        if account not in self._events:
240            self._events[account] = {jid: [event]}
241        # no such jid before ?
242        elif jid not in self._events[account]:
243            self._events[account][jid] = [event]
244        else:
245            self._events[account][jid].append(event)
246        event.jid = jid
247        event.account = account
248        self.fire_event_added(event)
249
250    def remove_events(self, account, jid, event=None, types=None):
251        """
252        If event is not specified, remove all events from this jid, optionally
253        only from given type return True if no such event found
254        """
255        if types is None:
256            types = []
257        if account not in self._events:
258            return True
259        if jid not in self._events[account]:
260            return True
261        if event: # remove only one event
262            if event in self._events[account][jid]:
263                if len(self._events[account][jid]) == 1:
264                    del self._events[account][jid]
265                else:
266                    self._events[account][jid].remove(event)
267                self.fire_event_removed([event])
268                return
269            return True
270        if types:
271            new_list = [] # list of events to keep
272            removed_list = [] # list of removed events
273            for ev in self._events[account][jid]:
274                if ev.type_ not in types:
275                    new_list.append(ev)
276                else:
277                    removed_list.append(ev)
278            if len(new_list) == len(self._events[account][jid]):
279                return True
280            if new_list:
281                self._events[account][jid] = new_list
282            else:
283                del self._events[account][jid]
284            self.fire_event_removed(removed_list)
285            return
286        # No event nor type given, remove them all
287        removed_list = self._events[account][jid]
288        del self._events[account][jid]
289        self.fire_event_removed(removed_list)
290
291    def change_jid(self, account, old_jid, new_jid):
292        if account not in self._events:
293            return
294        if old_jid not in self._events[account]:
295            return
296        if new_jid in self._events[account]:
297            self._events[account][new_jid] += self._events[account][old_jid]
298        else:
299            self._events[account][new_jid] = self._events[account][old_jid]
300        del self._events[account][old_jid]
301
302    def get_nb_events(self, types=None, account=None):
303        if types is None:
304            types = []
305        return self._get_nb_events(types=types, account=account)
306
307    def get_events(self, account, jid=None, types=None):
308        """
309        Return all events from the given account of the form {jid1: [], jid2:
310        []}. If jid is given, returns all events from the given jid in a list: []
311        optionally only from given type
312        """
313        if types is None:
314            types = []
315        if account not in self._events:
316            return []
317        if not jid:
318            events_list = {} # list of events
319            for jid_ in self._events[account]:
320                events = []
321                for ev in self._events[account][jid_]:
322                    if not types or ev.type_ in types:
323                        events.append(ev)
324                if events:
325                    events_list[jid_] = events
326            return events_list
327        if jid not in self._events[account]:
328            return []
329        events_list = [] # list of events
330        for ev in self._events[account][jid]:
331            if not types or ev.type_ in types:
332                events_list.append(ev)
333        return events_list
334
335    def get_all_events(self, types=None):
336        accounts = self._events.keys()
337        events = []
338        for account in accounts:
339            for jid in self._events[account]:
340                for event in self._events[account][jid]:
341                    if types is None or event.type_ in types:
342                        events.append(event)
343        return events
344
345    def get_first_event(self, account=None, jid=None, type_=None):
346        """
347        Return the first event of type type_ if given
348        """
349        if not account:
350            return self._get_first_event_with_attribute(self._events)
351        events_list = self.get_events(account, jid, type_)
352        # be sure it's bigger than latest event
353        first_event_time = time.time() + 1
354        first_event = None
355        for event in events_list:
356            if event.time_ < first_event_time:
357                first_event_time = event.time_
358                first_event = event
359        return first_event
360
361    def _get_nb_events(self, account=None, jid=None, attribute=None, types=None):
362        """
363        Return the number of pending events
364        """
365        if types is None:
366            types = []
367        nb = 0
368        if account:
369            accounts = [account]
370        else:
371            accounts = self._events.keys()
372        for acct in accounts:
373            if acct not in self._events:
374                continue
375            if jid:
376                jids = [jid]
377            else:
378                jids = self._events[acct].keys()
379            for j in jids:
380                if j not in self._events[acct]:
381                    continue
382                for event in self._events[acct][j]:
383                    if types and event.type_ not in types:
384                        continue
385                    if not attribute or \
386                    attribute == 'systray' and event.show_in_systray or \
387                    attribute == 'roster' and event.show_in_roster:
388                        nb += 1
389        return nb
390
391    def _get_some_events(self, attribute):
392        """
393        Attribute in systray, roster
394        """
395        events = {}
396        for account in self._events:
397            events[account] = {}
398            for jid in self._events[account]:
399                events[account][jid] = []
400                for event in self._events[account][jid]:
401                    if attribute == 'systray' and event.show_in_systray or \
402                    attribute == 'roster' and event.show_in_roster:
403                        events[account][jid].append(event)
404                if not events[account][jid]:
405                    del events[account][jid]
406            if not events[account]:
407                del events[account]
408        return events
409
410    def _get_first_event_with_attribute(self, events):
411        """
412        Get the first event
413
414        events is in the form {account1: {jid1: [ev1, ev2], },. }
415        """
416        # be sure it's bigger than latest event
417        first_event_time = time.time() + 1
418        first_account = None
419        first_jid = None
420        first_event = None
421        for account in events:
422            for jid in events[account]:
423                for event in events[account][jid]:
424                    if event.time_ < first_event_time:
425                        first_event_time = event.time_
426                        first_account = account
427                        first_jid = jid
428                        first_event = event
429        return first_account, first_jid, first_event
430
431    def get_nb_systray_events(self, types=None):
432        """
433        Return the number of events displayed in roster
434        """
435        if types is None:
436            types = []
437        return self._get_nb_events(attribute='systray', types=types)
438
439    def get_systray_events(self):
440        """
441        Return all events that must be displayed in systray:
442                {account1: {jid1: [ev1, ev2], },. }
443        """
444        return self._get_some_events('systray')
445
446    def get_first_systray_event(self):
447        events = self.get_systray_events()
448        return self._get_first_event_with_attribute(events)
449
450    def get_nb_roster_events(self, account=None, jid=None, types=None):
451        """
452        Return the number of events displayed in roster
453        """
454        if types is None:
455            types = []
456        return self._get_nb_events(attribute='roster', account=account,
457                jid=jid, types=types)
458
459    def get_roster_events(self):
460        """
461        Return all events that must be displayed in roster:
462                {account1: {jid1: [ev1, ev2], },. }
463        """
464        return self._get_some_events('roster')
465