don't traceback when we get disconnected wile we parse stream features. Fixes #5574
[gajim.git] / src / notify.py
blob3d9eb605117819c683cb09fb6b0fc77608c18dec
1 # -*- coding:utf-8 -*-
2 ## src/notify.py
3 ##
4 ## Copyright (C) 2005 Sebastian Estienne
5 ## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com>
6 ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org>
8 ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
9 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
10 ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
11 ## Stephan Erb <steve-e AT h3c.de>
12 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
13 ## Jonathan Schleifer <js-gajim AT webkeks.org>
15 ## This file is part of Gajim.
17 ## Gajim is free software; you can redistribute it and/or modify
18 ## it under the terms of the GNU General Public License as published
19 ## by the Free Software Foundation; version 3 only.
21 ## Gajim is distributed in the hope that it will be useful,
22 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ## GNU General Public License for more details.
26 ## You should have received a copy of the GNU General Public License
27 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
30 import os
31 import time
32 import dialogs
33 import gobject
34 import gtkgui_helpers
35 import gtk
37 from common import gajim
38 from common import helpers
40 from common import dbus_support
41 if dbus_support.supported:
42 import dbus
43 import dbus.glib
46 USER_HAS_PYNOTIFY = True # user has pynotify module
47 try:
48 import pynotify
49 pynotify.init('Gajim Notification')
50 except ImportError:
51 USER_HAS_PYNOTIFY = False
53 if gajim.HAVE_INDICATOR:
54 import indicate
56 def setup_indicator_server():
57 server = indicate.indicate_server_ref_default()
58 server.set_type('message.im')
59 server.set_desktop_file('/usr/share/applications/gajim.desktop')
60 server.connect('server-display', server_display)
61 server.show()
63 def display(indicator, account, jid, msg_type):
64 gajim.interface.handle_event(account, jid, msg_type)
65 indicator.hide()
67 def server_display(server):
68 win = gajim.interface.roster.window
69 win.present()
71 def get_show_in_roster(event, account, contact, session=None):
72 '''Return True if this event must be shown in roster, else False'''
73 if event == 'gc_message_received':
74 return True
75 num = get_advanced_notification(event, account, contact)
76 if num is not None:
77 if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
78 return True
79 if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
80 return False
81 if event == 'message_received':
82 if session and session.control:
83 return False
84 return True
86 def get_show_in_systray(event, account, contact, type_=None):
87 '''Return True if this event must be shown in systray, else False'''
88 num = get_advanced_notification(event, account, contact)
89 if num is not None:
90 if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
91 return True
92 if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
93 return False
94 if type_ == 'printed_gc_msg' and not gajim.config.get(
95 'notify_on_all_muc_messages'):
96 # it's not an highlighted message, don't show in systray
97 return False
98 return gajim.config.get('trayicon_notification_on_events')
100 def get_advanced_notification(event, account, contact):
101 '''Returns the number of the first (top most)
102 advanced notification else None'''
103 num = 0
104 notif = gajim.config.get_per('notifications', str(num))
105 while notif:
106 recipient_ok = False
107 status_ok = False
108 tab_opened_ok = False
109 # test event
110 if gajim.config.get_per('notifications', str(num), 'event') == event:
111 # test recipient
112 recipient_type = gajim.config.get_per('notifications', str(num),
113 'recipient_type')
114 recipients = gajim.config.get_per('notifications', str(num),
115 'recipients').split()
116 if recipient_type == 'all':
117 recipient_ok = True
118 elif recipient_type == 'contact' and contact.jid in recipients:
119 recipient_ok = True
120 elif recipient_type == 'group':
121 for group in contact.groups:
122 if group in contact.groups:
123 recipient_ok = True
124 break
125 if recipient_ok:
126 # test status
127 our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
128 status = gajim.config.get_per('notifications', str(num), 'status')
129 if status == 'all' or our_status in status.split():
130 status_ok = True
131 if status_ok:
132 # test window_opened
133 tab_opened = gajim.config.get_per('notifications', str(num),
134 'tab_opened')
135 if tab_opened == 'both':
136 tab_opened_ok = True
137 else:
138 chat_control = helpers.get_chat_control(account, contact)
139 if (chat_control and tab_opened == 'yes') or (not chat_control and \
140 tab_opened == 'no'):
141 tab_opened_ok = True
142 if tab_opened_ok:
143 return num
145 num += 1
146 notif = gajim.config.get_per('notifications', str(num))
148 def notify(event, jid, account, parameters, advanced_notif_num=None):
149 '''Check what type of notifications we want, depending on basic
150 and the advanced configuration of notifications and do these notifications;
151 advanced_notif_num holds the number of the first (top most) advanced
152 notification'''
153 # First, find what notifications we want
154 do_popup = False
155 do_sound = False
156 do_cmd = False
157 if event == 'status_change':
158 new_show = parameters[0]
159 status_message = parameters[1]
160 # Default: No popup for status change
161 elif event == 'contact_connected':
162 status_message = parameters
163 j = gajim.get_jid_without_resource(jid)
164 server = gajim.get_server_from_jid(j)
165 account_server = account + '/' + server
166 block_transport = False
167 if account_server in gajim.block_signed_in_notifications and \
168 gajim.block_signed_in_notifications[account_server]:
169 block_transport = True
170 if helpers.allow_showing_notification(account, 'notify_on_signin') and \
171 not gajim.block_signed_in_notifications[account] and not block_transport:
172 do_popup = True
173 if gajim.config.get_per('soundevents', 'contact_connected',
174 'enabled') and not gajim.block_signed_in_notifications[account] and \
175 not block_transport:
176 do_sound = True
177 elif event == 'contact_disconnected':
178 status_message = parameters
179 if helpers.allow_showing_notification(account, 'notify_on_signout'):
180 do_popup = True
181 if gajim.config.get_per('soundevents', 'contact_disconnected',
182 'enabled'):
183 do_sound = True
184 elif event == 'new_message':
185 message_type = parameters[0]
186 is_first_message = parameters[1]
187 nickname = parameters[2]
188 if gajim.config.get('notification_preview_message'):
189 message = parameters[3]
190 if message.startswith('/me ') or message.startswith('/me\n'):
191 message = '* ' + nickname + message[3:]
192 else:
193 # We don't want message preview, do_preview = False
194 message = ''
195 focused = parameters[4]
196 if helpers.allow_showing_notification(account, 'notify_on_new_message',
197 advanced_notif_num, is_first_message):
198 do_popup = True
199 if is_first_message and helpers.allow_sound_notification(account,
200 'first_message_received', advanced_notif_num):
201 do_sound = True
202 elif not is_first_message and focused and \
203 helpers.allow_sound_notification(account, 'next_message_received_focused',
204 advanced_notif_num):
205 do_sound = True
206 elif not is_first_message and not focused and \
207 helpers.allow_sound_notification(account,
208 'next_message_received_unfocused', advanced_notif_num):
209 do_sound = True
210 else:
211 print '*Event not implemeted yet*'
213 if advanced_notif_num is not None and gajim.config.get_per('notifications',
214 str(advanced_notif_num), 'run_command'):
215 do_cmd = True
217 # Do the wanted notifications
218 if do_popup:
219 if event in ('contact_connected', 'contact_disconnected',
220 'status_change'): # Common code for popup for these three events
221 if event == 'contact_disconnected':
222 show_image = 'offline.png'
223 suffix = '_notif_size_bw'
224 else: #Status Change or Connected
225 # FIXME: for status change,
226 # we don't always 'online.png', but we
227 # first need 48x48 for all status
228 show_image = 'online.png'
229 suffix = '_notif_size_colored'
230 transport_name = gajim.get_transport_name_from_jid(jid)
231 img = None
232 if transport_name:
233 img = os.path.join(helpers.get_transport_path(transport_name),
234 '48x48', show_image)
235 if not img or not os.path.isfile(img):
236 iconset = gajim.config.get('iconset')
237 img = os.path.join(helpers.get_iconset_path(iconset), '48x48',
238 show_image)
239 path = gtkgui_helpers.get_path_to_generic_or_avatar(img,
240 jid = jid, suffix = suffix)
241 if event == 'status_change':
242 title = _('%(nick)s Changed Status') % \
243 {'nick': gajim.get_name_from_jid(account, jid)}
244 text = _('%(nick)s is now %(status)s') % \
245 {'nick': gajim.get_name_from_jid(account, jid),\
246 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])}
247 if status_message:
248 text = text + " : " + status_message
249 popup(_('Contact Changed Status'), jid, account,
250 path_to_image=path, title=title, text=text)
251 elif event == 'contact_connected':
252 title = _('%(nickname)s Signed In') % \
253 {'nickname': gajim.get_name_from_jid(account, jid)}
254 text = ''
255 if status_message:
256 text = status_message
257 popup(_('Contact Signed In'), jid, account,
258 path_to_image=path, title=title, text=text)
259 elif event == 'contact_disconnected':
260 title = _('%(nickname)s Signed Out') % \
261 {'nickname': gajim.get_name_from_jid(account, jid)}
262 text = ''
263 if status_message:
264 text = status_message
265 popup(_('Contact Signed Out'), jid, account,
266 path_to_image=path, title=title, text=text)
267 elif event == 'new_message':
268 if message_type == 'normal': # single message
269 event_type = _('New Single Message')
270 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
271 'single_msg_recv.png')
272 title = _('New Single Message from %(nickname)s') % \
273 {'nickname': nickname}
274 text = message
275 elif message_type == 'pm': # private message
276 event_type = _('New Private Message')
277 room_name = gajim.get_nick_from_jid(jid)
278 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
279 'priv_msg_recv.png')
280 title = _('New Private Message from group chat %s') % room_name
281 if message:
282 text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
283 'message': message}
284 else:
285 text = _('Messaged by %(nickname)s') % {'nickname': nickname}
287 else: # chat message
288 event_type = _('New Message')
289 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
290 'chat_msg_recv.png')
291 title = _('New Message from %(nickname)s') % \
292 {'nickname': nickname}
293 text = message
294 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
295 popup(event_type, jid, account, message_type,
296 path_to_image=path, title=title, text=text)
298 if do_sound:
299 snd_file = None
300 snd_event = None # If not snd_file, play the event
301 if event == 'new_message':
302 if advanced_notif_num is not None and gajim.config.get_per(
303 'notifications', str(advanced_notif_num), 'sound') == 'yes':
304 snd_file = gajim.config.get_per('notifications',
305 str(advanced_notif_num), 'sound_file')
306 elif advanced_notif_num is not None and gajim.config.get_per(
307 'notifications', str(advanced_notif_num), 'sound') == 'no':
308 pass # do not set snd_event
309 elif is_first_message:
310 snd_event = 'first_message_received'
311 elif focused:
312 snd_event = 'next_message_received_focused'
313 else:
314 snd_event = 'next_message_received_unfocused'
315 elif event in ('contact_connected', 'contact_disconnected'):
316 snd_event = event
317 if snd_file:
318 helpers.play_sound_file(snd_file)
319 if snd_event:
320 helpers.play_sound(snd_event)
322 if do_cmd:
323 command = gajim.config.get_per('notifications', str(advanced_notif_num),
324 'command')
325 try:
326 helpers.exec_command(command)
327 except Exception:
328 pass
330 def popup(event_type, jid, account, msg_type='', path_to_image=None,
331 title=None, text=None):
332 '''Notifies a user of an event. It first tries to a valid implementation of
333 the Desktop Notification Specification. If that fails, then we fall back to
334 the older style PopupNotificationWindow method.'''
336 # default image
337 if not path_to_image:
338 path_to_image = os.path.abspath(
339 os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
340 'chat_msg_recv.png')) # img to display
342 if gajim.HAVE_INDICATOR and event_type in (_('New Message'),
343 _('New Single Message'), _('New Private Message')):
344 indicator = indicate.Indicator()
345 indicator.set_property('subtype', 'im')
346 indicator.set_property('sender', jid)
347 indicator.set_property('body', text)
348 indicator.set_property_time('time', time.time())
349 pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image)
350 indicator.set_property_icon('icon', pixbuf)
351 indicator.connect('user-display', display, account, jid, msg_type)
352 indicator.show()
354 # Try to show our popup via D-Bus and notification daemon
355 if gajim.config.get('use_notif_daemon') and dbus_support.supported:
356 try:
357 DesktopNotification(event_type, jid, account, msg_type,
358 path_to_image, title, gobject.markup_escape_text(text))
359 return # sucessfully did D-Bus Notification procedure!
360 except dbus.DBusException, e:
361 # Connection to D-Bus failed
362 gajim.log.debug(str(e))
363 except TypeError, e:
364 # This means that we sent the message incorrectly
365 gajim.log.debug(str(e))
367 # Ok, that failed. Let's try pynotify, which also uses notification daemon
368 if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY:
369 if not text and event_type == 'new_message':
370 # empty text for new_message means do_preview = False
371 # -> default value for text
372 _text = gobject.markup_escape_text(
373 gajim.get_name_from_jid(account, jid))
374 else:
375 _text = gobject.markup_escape_text(text)
377 if not title:
378 _title = ''
379 else:
380 _title = title
382 notification = pynotify.Notification(_title, _text)
383 timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
384 notification.set_timeout(timeout)
386 notification.set_category(event_type)
387 notification.set_data('event_type', event_type)
388 notification.set_data('jid', jid)
389 notification.set_data('account', account)
390 notification.set_data('msg_type', msg_type)
391 notification.set_property('icon-name', path_to_image)
392 if 'actions' in pynotify.get_server_caps():
393 notification.add_action('default', 'Default Action',
394 on_pynotify_notification_clicked)
396 try:
397 notification.show()
398 return
399 except gobject.GError, e:
400 # Connection to notification-daemon failed, see #2893
401 gajim.log.debug(str(e))
403 # Either nothing succeeded or the user wants old-style notifications
404 instance = dialogs.PopupNotificationWindow(event_type, jid, account,
405 msg_type, path_to_image, title, text)
406 gajim.interface.roster.popup_notification_windows.append(instance)
408 def on_pynotify_notification_clicked(notification, action):
409 jid = notification.get_data('jid')
410 account = notification.get_data('account')
411 msg_type = notification.get_data('msg_type')
413 notification.close()
414 gajim.interface.handle_event(account, jid, msg_type)
416 class NotificationResponseManager:
417 '''Collects references to pending DesktopNotifications and manages there
418 signalling. This is necessary due to a bug in DBus where you can't remove
419 a signal from an interface once it's connected.'''
420 def __init__(self):
421 self.pending = {}
422 self.received = []
423 self.interface = None
425 def attach_to_interface(self):
426 if self.interface is not None:
427 return
428 self.interface = dbus_support.get_notifications_interface()
429 self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked)
430 self.interface.connect_to_signal('NotificationClosed', self.on_closed)
432 def on_action_invoked(self, id_, reason):
433 self.received.append((id_, time.time(), reason))
434 if id_ in self.pending:
435 notification = self.pending[id_]
436 notification.on_action_invoked(id_, reason)
437 del self.pending[id_]
438 if len(self.received) > 20:
439 curt = time.time()
440 for rec in self.received:
441 diff = curt - rec[1]
442 if diff > 10:
443 self.received.remove(rec)
445 def on_closed(self, id_, reason=None):
446 if id_ in self.pending:
447 del self.pending[id_]
449 def add_pending(self, id_, object_):
450 # Check to make sure that we handle an event immediately if we're adding
451 # an id that's already been triggered
452 for rec in self.received:
453 if rec[0] == id_:
454 object_.on_action_invoked(id_, rec[2])
455 self.received.remove(rec)
456 return
457 if id_ not in self.pending:
458 # Add it
459 self.pending[id_] = object_
460 else:
461 # We've triggered an event that has a duplicate ID!
462 gajim.log.debug('Duplicate ID of notification. Can\'t handle this.')
464 notification_response_manager = NotificationResponseManager()
466 class DesktopNotification:
467 '''A DesktopNotification that interfaces with D-Bus via the Desktop
468 Notification specification'''
469 def __init__(self, event_type, jid, account, msg_type='',
470 path_to_image=None, title=None, text=None):
471 self.path_to_image = path_to_image
472 self.event_type = event_type
473 self.title = title
474 self.text = text
475 # 0.3.1 is the only version of notification daemon that has no way
476 # to determine which version it is. If no method exists, it means
477 # they're using that one.
478 self.default_version = [0, 3, 1]
479 self.account = account
480 self.jid = jid
481 self.msg_type = msg_type
483 # default value of text
484 if not text and event_type == 'new_message':
485 # empty text for new_message means do_preview = False
486 self.text = gajim.get_name_from_jid(account, jid)
488 if not title:
489 self.title = event_type # default value
491 if event_type == _('Contact Signed In'):
492 ntype = 'presence.online'
493 elif event_type == _('Contact Signed Out'):
494 ntype = 'presence.offline'
495 elif event_type in (_('New Message'), _('New Single Message'),
496 _('New Private Message')):
497 ntype = 'im.received'
498 elif event_type == _('File Transfer Request'):
499 ntype = 'transfer'
500 elif event_type == _('File Transfer Error'):
501 ntype = 'transfer.error'
502 elif event_type in (_('File Transfer Completed'),
503 _('File Transfer Stopped')):
504 ntype = 'transfer.complete'
505 elif event_type == _('New E-mail'):
506 ntype = 'email.arrived'
507 elif event_type == _('Groupchat Invitation'):
508 ntype = 'im.invitation'
509 elif event_type == _('Contact Changed Status'):
510 ntype = 'presence.status'
511 elif event_type == _('Connection Failed'):
512 ntype = 'connection.failed'
513 elif event_type == _('Subscription request'):
514 ntype = 'subscription.request'
515 elif event_type == _('Unsubscribed'):
516 ntype = 'unsubscribed'
517 else:
518 # default failsafe values
519 self.path_to_image = os.path.abspath(
520 os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
521 'chat_msg_recv.png')) # img to display
522 ntype = 'im' # Notification Type
524 self.notif = dbus_support.get_notifications_interface(self)
525 if self.notif is None:
526 raise dbus.DBusException('unable to get notifications interface')
527 self.ntype = ntype
529 if self.kde_notifications:
530 self.attempt_notify()
531 else:
532 self.capabilities = self.notif.GetCapabilities()
533 if self.capabilities is None:
534 self.capabilities = ['actions']
535 self.get_version()
537 def attempt_notify(self):
538 timeout = gajim.config.get('notification_timeout') # in seconds
539 ntype = self.ntype
540 if self.kde_notifications:
541 notification_text = ('<html><img src="%(image)s" align=left />' \
542 '%(title)s<br/>%(text)s</html>') % {'title': self.title,
543 'text': self.text, 'image': self.path_to_image}
544 gajim_icon = os.path.abspath(os.path.join(gajim.DATA_DIR, 'pixmaps',
545 'gajim.png'))
546 self.notif.Notify(
547 dbus.String(_('Gajim')), # app_name (string)
548 dbus.UInt32(0), # replaces_id (uint)
549 ntype, # event_id (string)
550 dbus.String(gajim_icon), # app_icon (string)
551 dbus.String(''), # summary (string)
552 dbus.String(notification_text), # body (string)
553 # actions (stringlist)
554 (dbus.String('default'), dbus.String(self.event_type),
555 dbus.String('ignore'), dbus.String(_('Ignore'))),
556 [], # hints (not used in KDE yet)
557 dbus.UInt32(timeout*1000), # timeout (int), in ms
558 reply_handler=self.attach_by_id,
559 error_handler=self.notify_another_way)
560 return
561 version = self.version
562 if version[:2] == [0, 2]:
563 actions = {}
564 if 'actions' in self.capabilities:
565 actions = {'default': 0}
566 try:
567 self.notif.Notify(
568 dbus.String(_('Gajim')),
569 dbus.String(self.path_to_image),
570 dbus.UInt32(0),
571 ntype,
572 dbus.Byte(0),
573 dbus.String(self.title),
574 dbus.String(self.text),
575 [dbus.String(self.path_to_image)],
576 actions,
577 [''],
578 True,
579 dbus.UInt32(timeout),
580 reply_handler=self.attach_by_id,
581 error_handler=self.notify_another_way)
582 except AttributeError:
583 version = [0, 3, 1] # we're actually dealing with the newer version
584 if version > [0, 3]:
585 if gajim.interface.systray_enabled and \
586 gajim.config.get('attach_notifications_to_systray'):
587 x, y = gajim.interface.systray.img_tray.window.get_origin()
588 width, height, = \
589 gajim.interface.systray.img_tray.window.get_geometry()[2:4]
590 pos_x = x + (width / 2)
591 pos_y = y + (height / 2)
592 hints = {'x': pos_x, 'y': pos_y}
593 else:
594 hints = {}
595 if version >= [0, 3, 2]:
596 hints['urgency'] = dbus.Byte(0) # Low Urgency
597 hints['category'] = dbus.String(ntype)
598 # it seems notification-daemon doesn't like empty text
599 if self.text:
600 text = self.text
601 else:
602 text = ' '
603 actions = ()
604 if 'actions' in self.capabilities:
605 actions = (dbus.String('default'), dbus.String(self.event_type))
606 self.notif.Notify(
607 dbus.String(_('Gajim')),
608 dbus.UInt32(0), # this notification does not replace other
609 dbus.String(self.path_to_image),
610 dbus.String(self.title),
611 dbus.String(text),
612 actions,
613 hints,
614 dbus.UInt32(timeout*1000),
615 reply_handler=self.attach_by_id,
616 error_handler=self.notify_another_way)
617 else:
618 self.notif.Notify(
619 dbus.String(_('Gajim')),
620 dbus.String(self.path_to_image),
621 dbus.UInt32(0),
622 dbus.String(self.title),
623 dbus.String(self.text),
624 dbus.String(''),
625 hints,
626 dbus.UInt32(timeout*1000),
627 reply_handler=self.attach_by_id,
628 error_handler=self.notify_another_way)
630 def attach_by_id(self, id_):
631 self.id = id_
632 notification_response_manager.attach_to_interface()
633 notification_response_manager.add_pending(self.id, self)
635 def notify_another_way(self,e):
636 gajim.log.debug(str(e))
637 gajim.log.debug('Need to implement a new way of falling back')
639 def on_action_invoked(self, id_, reason):
640 if self.notif is None:
641 return
642 self.notif.CloseNotification(dbus.UInt32(id_))
643 self.notif = None
645 if reason == 'ignore':
646 return
648 gajim.interface.handle_event(self.account, self.jid, self.msg_type)
650 def version_reply_handler(self, name, vendor, version, spec_version=None):
651 if spec_version:
652 version = spec_version
653 elif vendor == 'Xfce' and version.startswith('0.1.0'):
654 version = '0.9'
655 version_list = version.split('.')
656 self.version = []
657 try:
658 while len(version_list):
659 self.version.append(int(version_list.pop(0)))
660 except ValueError:
661 self.version_error_handler_3_x_try(None)
662 self.attempt_notify()
664 def get_version(self):
665 self.notif.GetServerInfo(
666 reply_handler=self.version_reply_handler,
667 error_handler=self.version_error_handler_2_x_try)
669 def version_error_handler_2_x_try(self, e):
670 self.notif.GetServerInformation(reply_handler=self.version_reply_handler,
671 error_handler=self.version_error_handler_3_x_try)
673 def version_error_handler_3_x_try(self, e):
674 self.version = self.default_version
675 self.attempt_notify()
677 # vim: se ts=3: