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