| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding:utf-8 -*-
2 ## src/common/helpers.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
6 ## Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2006 Alex Mauer <hawke AT hawkesnest.net>
8 ## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
9 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
10 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
11 ## James Newton <redshodan AT gmail.com>
12 ## Julien Pivotto <roidelapluie AT gmail.com>
13 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
14 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
15 ## Jonathan Schleifer <js-gajim AT webkeks.org>
16 ##
17 ## This file is part of Gajim.
18 ##
19 ## Gajim is free software; you can redistribute it and/or modify
20 ## it under the terms of the GNU General Public License as published
21 ## by the Free Software Foundation; version 3 only.
22 ##
23 ## Gajim is distributed in the hope that it will be useful,
24 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
25 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 ## GNU General Public License for more details.
27 ##
28 ## You should have received a copy of the GNU General Public License
29 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
30 ##
31
32 import sys
33 import re
34 import locale
35 import os
36 import subprocess
37 import urllib
38 import errno
39 import select
40 import base64
41 import hashlib
42 import caps_cache
43
44 from encodings.punycode import punycode_encode
45 from string import Template
46
47 from i18n import Q_
48 from i18n import ngettext
49
50 try:
51 import winsound # windows-only built-in module for playing wav
52 import win32api
53 import win32con
54 except Exception:
55 pass
56
57 special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
58
61
63 user = None
64 server = None
65 resource = None
66
67 # Search for delimiters
68 user_sep = jidstring.find('@')
69 res_sep = jidstring.find('/')
70
71 if user_sep == -1:
72 if res_sep == -1:
73 # host
74 server = jidstring
75 else:
76 # host/resource
77 server = jidstring[0:res_sep]
78 resource = jidstring[res_sep + 1:]
79 else:
80 if res_sep == -1:
81 # user@host
82 user = jidstring[0:user_sep]
83 server = jidstring[user_sep + 1:]
84 else:
85 if user_sep < res_sep:
86 # user@host/resource
87 user = jidstring[0:user_sep]
88 server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
89 resource = jidstring[res_sep + 1:]
90 else:
91 # server/resource (with an @ in resource)
92 server = jidstring[0:res_sep]
93 resource = jidstring[res_sep + 1:]
94 return user, server, resource
95
97 """
98 Perform stringprep on all JID fragments from a string and return the full
99 jid
100 """
101 # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
102
103 return prep(*decompose_jid(jidstring))
104
106 """
107 Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
108 encoding)
109 """
110 from encodings import idna
111 labels = idna.dots.split(host)
112 converted_labels = []
113 for label in labels:
114 converted_labels.append(idna.ToASCII(label))
115 return ".".join(converted_labels)
116
118 """
119 Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
120 Names)
121 """
122 from encodings import idna
123 labels = idna.dots.split(host)
124 converted_labels = []
125 for label in labels:
126 converted_labels.append(idna.ToUnicode(label))
127 return ".".join(converted_labels)
128
130 """
131 Perform stringprep on resource and return it
132 """
133 if resource:
134 try:
135 from xmpp.stringprepare import resourceprep
136 return resourceprep.prepare(unicode(resource))
137 except UnicodeError:
138 raise InvalidFormat, 'Invalid character in resource.'
139
141 """
142 Perform stringprep on all JID fragments and return the full jid
143 """
144 # This function comes from
145 #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
146 if user is not None:
147 if len(user) < 1 or len(user) > 1023:
148 raise InvalidFormat, _('Username must be between 1 and 1023 chars')
149 try:
150 from xmpp.stringprepare import nodeprep
151 user = nodeprep.prepare(unicode(user))
152 except UnicodeError:
153 raise InvalidFormat, _('Invalid character in username.')
154 else:
155 user = None
156
157 if server is not None:
158 if len(server) < 1 or len(server) > 1023:
159 raise InvalidFormat, _('Server must be between 1 and 1023 chars')
160 try:
161 from xmpp.stringprepare import nameprep
162 server = nameprep.prepare(unicode(server))
163 except UnicodeError:
164 raise InvalidFormat, _('Invalid character in hostname.')
165 else:
166 raise InvalidFormat, _('Server address required.')
167
168 if resource is not None:
169 if len(resource) < 1 or len(resource) > 1023:
170 raise InvalidFormat, _('Resource must be between 1 and 1023 chars')
171 try:
172 from xmpp.stringprepare import resourceprep
173 resource = resourceprep.prepare(unicode(resource))
174 except UnicodeError:
175 raise InvalidFormat, _('Invalid character in resource.')
176 else:
177 resource = None
178
179 if user:
180 if resource:
181 return '%s@%s/%s' % (user, server, resource)
182 else:
183 return '%s@%s' % (user, server)
184 else:
185 if resource:
186 return '%s/%s' % (server, resource)
187 else:
188 return server
189
194
196 while True:
197 try:
198 return func(*args, **kwargs)
199 except (os.error, IOError, select.error), ex:
200 if ex.errno == errno.EINTR:
201 continue
202 else:
203 raise
204
206 """
207 Return a userfriendly string for dnd/xa/chat and make all strings
208 translatable
209
210 If use_mnemonic is True, it adds _ so GUI should call with True for
211 accessibility issues
212 """
213 if show == 'dnd':
214 if use_mnemonic:
215 uf_show = _('_Busy')
216 else:
217 uf_show = _('Busy')
218 elif show == 'xa':
219 if use_mnemonic:
220 uf_show = _('_Not Available')
221 else:
222 uf_show = _('Not Available')
223 elif show == 'chat':
224 if use_mnemonic:
225 uf_show = _('_Free for Chat')
226 else:
227 uf_show = _('Free for Chat')
228 elif show == 'online':
229 if use_mnemonic:
230 uf_show = Q_('?user status:_Available')
231 else:
232 uf_show = Q_('?user status:Available')
233 elif show == 'connecting':
234 uf_show = _('Connecting')
235 elif show == 'away':
236 if use_mnemonic:
237 uf_show = _('A_way')
238 else:
239 uf_show = _('Away')
240 elif show == 'offline':
241 if use_mnemonic:
242 uf_show = _('_Offline')
243 else:
244 uf_show = _('Offline')
245 elif show == 'invisible':
246 if use_mnemonic:
247 uf_show = _('_Invisible')
248 else:
249 uf_show = _('Invisible')
250 elif show == 'not in roster':
251 uf_show = _('Not in Roster')
252 elif show == 'requested':
253 uf_show = Q_('?contact has status:Unknown')
254 else:
255 uf_show = Q_('?contact has status:Has errors')
256 return unicode(uf_show)
257
259 if sub == 'none':
260 uf_sub = Q_('?Subscription we already have:None')
261 elif sub == 'to':
262 uf_sub = _('To')
263 elif sub == 'from':
264 uf_sub = _('From')
265 elif sub == 'both':
266 uf_sub = _('Both')
267 else:
268 uf_sub = sub
269
270 return unicode(uf_sub)
271
273 if ask is None:
274 uf_ask = Q_('?Ask (for Subscription):None')
275 elif ask == 'subscribe':
276 uf_ask = _('Subscribe')
277 else:
278 uf_ask = ask
279
280 return unicode(uf_ask)
281
283 ''' plural determines if you get Moderators or Moderator'''
284 if role == 'none':
285 role_name = Q_('?Group Chat Contact Role:None')
286 elif role == 'moderator':
287 if plural:
288 role_name = _('Moderators')
289 else:
290 role_name = _('Moderator')
291 elif role == 'participant':
292 if plural:
293 role_name = _('Participants')
294 else:
295 role_name = _('Participant')
296 elif role == 'visitor':
297 if plural:
298 role_name = _('Visitors')
299 else:
300 role_name = _('Visitor')
301 return role_name
302
304 '''Get a nice and translated affilition for muc'''
305 if affiliation == 'none':
306 affiliation_name = Q_('?Group Chat Contact Affiliation:None')
307 elif affiliation == 'owner':
308 affiliation_name = _('Owner')
309 elif affiliation == 'admin':
310 affiliation_name = _('Administrator')
311 elif affiliation == 'member':
312 affiliation_name = _('Member')
313 else: # Argl ! An unknown affiliation !
314 affiliation_name = affiliation.capitalize()
315 return affiliation_name
316
320
322 msg = msg.replace('\\', '\\\\')
323 msg = msg.replace('\n', '\\n')
324 # s1 = 'test\ntest\\ntest'
325 # s11 = s1.replace('\\', '\\\\')
326 # s12 = s11.replace('\n', '\\n')
327 # s12
328 # 'test\\ntest\\\\ntest'
329 return msg
330
332 # (?<!\\) is a lookbehind assertion which asks anything but '\'
333 # to match the regexp that follows it
334
335 # So here match '\\n' but not if you have a '\' before that
336 expr = re.compile(r'(?<!\\)\\n')
337 msg = expr.sub('\n', msg)
338 msg = msg.replace('\\\\', '\\')
339 # s12 = 'test\\ntest\\\\ntest'
340 # s13 = re.sub('\n', s12)
341 # s14 s13.replace('\\\\', '\\')
342 # s14
343 # 'test\ntest\\ntest'
344 return msg
345
347 """
348 Remove chatstate jargon and returns user friendly messages
349 """
350 if chatstate == 'active':
351 return _('is paying attention to the conversation')
352 elif chatstate == 'inactive':
353 return _('is doing something else')
354 elif chatstate == 'composing':
355 return _('is composing a message...')
356 elif chatstate == 'paused':
357 #paused means he or she was composing but has stopped for a while
358 return _('paused composing a message')
359 elif chatstate == 'gone':
360 return _('has closed the chat window or tab')
361 return ''
362
364 """
365 Return True if 'command' is found in one of the directories in the user's
366 path. If 'return_abs_path' is True, return the absolute path of the first
367 found command instead. Return False otherwise and on errors
368 """
369 for directory in os.getenv('PATH').split(os.pathsep):
370 try:
371 if command in os.listdir(directory):
372 if return_abs_path:
373 return os.path.join(directory, command)
374 else:
375 return True
376 except OSError:
377 # If the user has non directories in his path
378 pass
379 return False
380
383
385 # we add to the parameter (can hold path with spaces)
386 # "" so we have good parsing from shell
387 parameter = parameter.replace('"', '\\"') # but first escape "
388 command = '%s "%s"' % (executable, parameter)
389 return command
390
392 path = urllib.unquote(uri) # escape special chars
393 path = path.strip('\r\n\x00') # remove \r\n and NULL
394 # get the path to file
395 if re.match('^file:///[a-zA-Z]:/', path): # windows
396 path = path[8:] # 8 is len('file:///')
397 elif path.startswith('file://'): # nautilus, rox
398 path = path[7:] # 7 is len('file://')
399 elif path.startswith('file:'): # xffm
400 path = path[5:] # 5 is len('file:')
401 return path
402
404 # this is xs:boolean so 'true', 'false', '1', '0'
405 # convert those to True/False (python booleans)
406 if value in ('1', 'true'):
407 val = True
408 else: # '0', 'false' or anything else
409 val = False
410
411 return val
412
417
419 try:
420 child_stdin, child_stdout = os.popen2(command)
421 except ValueError:
422 return None
423
424 output = child_stdout.readlines()
425 child_stdout.close()
426 child_stdin.close()
427
428 return output
429
431 """
432 Try to decode (to make it Unicode instance) given string
433 """
434 if isinstance(string, unicode):
435 return string
436 # by the time we go to iso15 it better be the one else we show bad characters
437 encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
438 for encoding in encodings:
439 try:
440 string = string.decode(encoding)
441 except UnicodeError:
442 continue
443 break
444
445 return string
446
448 """
449 Make sure string is in UTF-8
450 """
451 try:
452 string = decode_string(string).encode('utf-8')
453 except Exception:
454 pass
455 return string
456
458 """
459 Ask for paths commonly used but not exposed as ENVs in english Windows 2003
460 those are:
461 'AppData' = %USERPROFILE%\Application Data (also an ENV)
462 'Desktop' = %USERPROFILE%\Desktop
463 'Favorites' = %USERPROFILE%\Favorites
464 'NetHood' = %USERPROFILE%\NetHood
465 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
466 'PrintHood' = %USERPROFILE%\PrintHood
467 'Programs' = %USERPROFILE%\Start Menu\Programs
468 'Recent' = %USERPROFILE%\Recent
469 'SendTo' = %USERPROFILE%\SendTo
470 'Start Menu' = %USERPROFILE%\Start Menu
471 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
472 'Templates' = %USERPROFILE%\Templates
473 'My Pictures' = D:\My Documents\My Pictures
474 'Local Settings' = %USERPROFILE%\Local Settings
475 'Local AppData' = %USERPROFILE%\Local Settings\Application Data
476 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
477 'Cookies' = %USERPROFILE%\Cookies
478 'History' = %USERPROFILE%\Local Settings\History
479 """
480 if os.name != 'nt':
481 return ''
482
483 val = default
484 try:
485 rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,
486 r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders')
487 try:
488 val = str(win32api.RegQueryValueEx(rkey, varname)[0])
489 val = win32api.ExpandEnvironmentStrings(val) # expand using environ
490 except Exception:
491 pass
492 finally:
493 win32api.RegCloseKey(rkey)
494 return val
495
501
503 if os.name == 'nt':
504 path = get_windows_reg_env('Desktop')
505 else:
506 path = os.path.join(os.path.expanduser('~'), 'Desktop')
507 return path
508
510 if os.name == 'nt':
511 path = get_windows_reg_env('Personal')
512 else:
513 path = os.path.expanduser('~')
514 return path
515
517 """
518 Make sure the filename we will write does contain only acceptable and latin
519 characters, and is not too long (in that case hash it)
520 """
521 # 48 is the limit
522 if len(filename) > 48:
523 hash = hashlib.md5(filename)
524 filename = base64.b64encode(hash.digest())
525
526 filename = punycode_encode(filename) # make it latin chars only
527 filename = filename.replace('/', '_')
528 if os.name == 'nt':
529 filename = filename.replace('?', '_').replace(':', '_')\
530 .replace('\\', '_').replace('"', "'").replace('|', '_')\
531 .replace('*', '_').replace('<', '_').replace('>', '_')
532
533 return filename
534
536 """
537 Cut the chars after 'max_chars' on each line and show only the first
538 'max_lines'
539
540 If any of the params is not present (None or 0) the action on it is not
541 performed
542 """
543 def _cut_if_long(string):
544 if len(string) > max_chars:
545 string = string[:max_chars - 3] + '...'
546 return string
547
548 if isinstance(text, str):
549 text = text.decode('utf-8')
550
551 if max_lines == 0:
552 lines = text.split('\n')
553 else:
554 lines = text.split('\n', max_lines)[:max_lines]
555 if max_chars > 0:
556 if lines:
557 lines = [_cut_if_long(e) for e in lines]
558 if lines:
559 reduced_text = '\n'.join(lines)
560 if reduced_text != text:
561 reduced_text += '...'
562 else:
563 reduced_text = ''
564 return reduced_text
565
569
571 """
572 Return the filename of the avatar, distinguishes between user- and contact-
573 provided one. Return None if no avatar was found at all. prefix is the path
574 to the requested avatar just before the ".png" or ".jpeg"
575 """
576 # First, scan for a local, user-set avatar
577 for type_ in ('jpeg', 'png'):
578 file_ = prefix + '_local.' + type_
579 if os.path.exists(file_):
580 return file_
581 # If none available, scan for a contact-provided avatar
582 for type_ in ('jpeg', 'png'):
583 file_ = prefix + '.' + type_
584 if os.path.exists(file_):
585 return file_
586 return None
587
589 """
590 Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
591
592 Because of various datetime formats are used the following exceptions
593 are handled:
594 - Optional milliseconds appened to the string are removed
595 - Optional Z (that means UTC) appened to the string are removed
596 - XEP-082 datetime strings have all '-' cahrs removed to meet
597 the above format.
598 """
599 timestamp = timestamp.split('.')[0]
600 timestamp = timestamp.replace('-', '')
601 timestamp = timestamp.replace('z', '')
602 timestamp = timestamp.replace('Z', '')
603 from time import strptime
604 return strptime(timestamp, '%Y%m%dT%H:%M:%S')
605
606 # import gajim only when needed (after decode_string is defined) see #4764
607
608 import gajim
609
611 suffix = ''
612 # IEC standard says KiB = 1024 bytes KB = 1000 bytes
613 # but do we use the standard?
614 use_kib_mib = gajim.config.get('use_kib_mib')
615 align = 1024.
616 bytes = float(string)
617 if bytes >= align:
618 bytes = round(bytes/align, 1)
619 if bytes >= align:
620 bytes = round(bytes/align, 1)
621 if bytes >= align:
622 bytes = round(bytes/align, 1)
623 if use_kib_mib:
624 #GiB means gibibyte
625 suffix = _('%s GiB')
626 else:
627 #GB means gigabyte
628 suffix = _('%s GB')
629 else:
630 if use_kib_mib:
631 #MiB means mibibyte
632 suffix = _('%s MiB')
633 else:
634 #MB means megabyte
635 suffix = _('%s MB')
636 else:
637 if use_kib_mib:
638 #KiB means kibibyte
639 suffix = _('%s KiB')
640 else:
641 #KB means kilo bytes
642 suffix = _('%s KB')
643 else:
644 #B means bytes
645 suffix = _('%s B')
646 return suffix % unicode(bytes)
647
649 """
650 Create a dict of jid, nick -> contact with all contacts of account.
651
652 Can be used for completion lists
653 """
654 contacts_dict = {}
655 for jid in gajim.contacts.get_jid_list(account):
656 contact = gajim.contacts.get_contact_with_highest_priority(account,
657 jid)
658 contacts_dict[jid] = contact
659 name = contact.name
660 if name in contacts_dict:
661 contact1 = contacts_dict[name]
662 del contacts_dict[name]
663 contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
664 contacts_dict['%s (%s)' % (name, jid)] = contact
665 else:
666 if contact.name == gajim.get_nick_from_jid(jid):
667 del contacts_dict[jid]
668 contacts_dict[name] = contact
669 return contacts_dict
670
672 #kind = 'url' or 'mail'
673 if os.name == 'nt':
674 try:
675 os.startfile(uri) # if pywin32 is installed we open
676 except Exception:
677 pass
678
679 else:
680 if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'):
681 uri = 'mailto:' + uri
682
683 if kind == 'url' and uri.startswith('www.'):
684 uri = 'http://' + uri
685
686 if gajim.config.get('openwith') == 'gnome-open':
687 command = 'gnome-open'
688 elif gajim.config.get('openwith') == 'kfmclient exec':
689 command = 'kfmclient exec'
690 elif gajim.config.get('openwith') == 'exo-open':
691 command = 'exo-open'
692 elif gajim.config.get('openwith') == 'custom':
693 if kind == 'url':
694 command = gajim.config.get('custombrowser')
695 elif kind in ('mail', 'sth_at_sth'):
696 command = gajim.config.get('custommailapp')
697 if command == '': # if no app is configured
698 return
699
700 command = build_command(command, uri)
701 try:
702 exec_command(command)
703 except Exception:
704 pass
705
707 if os.name == 'nt':
708 try:
709 os.startfile(path_to_open) # if pywin32 is installed we open
710 except Exception:
711 pass
712 else:
713 if gajim.config.get('openwith') == 'gnome-open':
714 command = 'gnome-open'
715 elif gajim.config.get('openwith') == 'kfmclient exec':
716 command = 'kfmclient exec'
717 elif gajim.config.get('openwith') == 'exo-open':
718 command = 'exo-open'
719 elif gajim.config.get('openwith') == 'custom':
720 command = gajim.config.get('custom_file_manager')
721 if command == '': # if no app is configured
722 return
723 command = build_command(command, path_to_open)
724 try:
725 exec_command(command)
726 except Exception:
727 pass
728
730 if not gajim.config.get('sounds_on'):
731 return
732 path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
733 play_sound_file(path_to_soundfile)
734
737 """
738 Check if the sound file exists
739
740 :param file: the file to check, absolute or relative to 'dirs' path
741 :param dirs: list of knows paths to fallback if the file doesn't exists
742 (eg: ~/.gajim/sounds/, DATADIR/sounds...).
743 :return the path to file or None if it doesn't exists.
744 """
745 if not file:
746 return None
747 elif os.path.exists(file):
748 return file
749
750 for d in dirs:
751 d = os.path.join(d, 'sounds', file)
752 if os.path.exists(d):
753 return d
754 return None
755
756 -def strip_soundfile_path(file, dirs=(gajim.gajimpaths.data_root,
757 gajim.DATA_DIR), abs=True):
758 """
759 Remove knowns paths from a sound file
760
761 Filechooser returns absolute path. If path is a known fallback path, we remove it.
762 So config have no hardcoded path to DATA_DIR and text in textfield is shorther.
763 param: file: the filename to strip.
764 param: dirs: list of knowns paths from which the filename should be stripped.
765 param: abs: force absolute path on dirs
766 """
767 if not file:
768 return None
769
770 name = os.path.basename(file)
771 for d in dirs:
772 d = os.path.join(d, 'sounds', name)
773 if abs:
774 d = os.path.abspath(d)
775 if file == d:
776 return name
777 return file
778
780 if path_to_soundfile == 'beep':
781 exec_command('beep')
782 return
783 path_to_soundfile = check_soundfile_path(path_to_soundfile)
784 if path_to_soundfile is None:
785 return
786 elif os.name == 'nt':
787 try:
788 winsound.PlaySound(path_to_soundfile,
789 winsound.SND_FILENAME|winsound.SND_ASYNC)
790 except Exception:
791 pass
792 elif os.name == 'posix':
793 if gajim.config.get('soundplayer') == '':
794 return
795 player = gajim.config.get('soundplayer')
796 command = build_command(player, path_to_soundfile)
797 exec_command(command)
798
800 maxi = 0
801 for account in gajim.connections:
802 if not gajim.config.get_per('accounts', account,
803 'sync_with_global_status'):
804 continue
805 connected = gajim.connections[account].connected
806 if connected > maxi:
807 maxi = connected
808 return gajim.SHOW_LIST[maxi]
809
811 maxi = 0
812 for account in gajim.connections:
813 if not gajim.config.get_per('accounts', account,
814 'sync_with_global_status'):
815 continue
816 connected = gajim.connections[account].connected
817 if connected > maxi:
818 maxi = connected
819 status = gajim.connections[account].status
820 return status
821
822
824 """
825 Test if all statuses are the same
826 """
827 reference = None
828 for account in gajim.connections:
829 if not gajim.config.get_per('accounts', account,
830 'sync_with_global_status'):
831 continue
832 if reference is None:
833 reference = gajim.connections[account].connected
834 elif reference != gajim.connections[account].connected:
835 return False
836 return True
837
839 """
840 Get the icon name to show in online, away, requested, etc
841 """
842 if account and gajim.events.get_nb_roster_events(account, contact.jid):
843 return 'event'
844 if account and gajim.events.get_nb_roster_events(account,
845 contact.get_full_jid()):
846 return 'event'
847 if account and account in gajim.interface.minimized_controls and \
848 contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\
849 minimized_controls[account][contact.jid].get_nb_unread_pm() > 0:
850 return 'event'
851 if account and contact.jid in gajim.gc_connected[account]:
852 if gajim.gc_connected[account][contact.jid]:
853 return 'muc_active'
854 else:
855 return 'muc_inactive'
856 if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
857 return contact.show
858 if contact.sub in ('both', 'to'):
859 return contact.show
860 if contact.ask == 'subscribe':
861 return 'requested'
862 transport = gajim.get_transport_name_from_jid(contact.jid)
863 if transport:
864 return contact.show
865 if contact.show in gajim.SHOW_LIST:
866 return contact.show
867 return 'not in roster'
868
870 """
871 Return the full jid (with resource) from an iq as unicode
872 """
873 return parse_jid(str(iq_obj.getFrom()))
874
876 """
877 Return the jid (without resource) from an iq as unicode
878 """
879 jid = get_full_jid_from_iq(iq_obj)
880 return gajim.get_jid_without_resource(jid)
881
883 """
884 Return sha of sid + initiator + target used for proxy auth
885 """
886 return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest()
887
889 if string:
890 string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
891 return string
892
893 distro_info = {
894 'Arch Linux': '/etc/arch-release',
895 'Aurox Linux': '/etc/aurox-release',
896 'Conectiva Linux': '/etc/conectiva-release',
897 'CRUX': '/usr/bin/crux',
898 'Debian GNU/Linux': '/etc/debian_release',
899 'Debian GNU/Linux': '/etc/debian_version',
900 'Fedora Linux': '/etc/fedora-release',
901 'Gentoo Linux': '/etc/gentoo-release',
902 'Linux from Scratch': '/etc/lfs-release',
903 'Mandrake Linux': '/etc/mandrake-release',
904 'Slackware Linux': '/etc/slackware-release',
905 'Slackware Linux': '/etc/slackware-version',
906 'Solaris/Sparc': '/etc/release',
907 'Source Mage': '/etc/sourcemage_version',
908 'SUSE Linux': '/etc/SuSE-release',
909 'Sun JDS': '/etc/sun-release',
910 'PLD Linux': '/etc/pld-release',
911 'Yellow Dog Linux': '/etc/yellowdog-release',
912 # many distros use the /etc/redhat-release for compatibility
913 # so Redhat is the last
914 'Redhat Linux': '/etc/redhat-release'
915 }
916
918 """
919 Create random string of length 16
920 """
921 rng = range(65, 90)
922 rng.extend(range(48, 57))
923 char_sequence = [chr(e) for e in rng]
924 from random import sample
925 return ''.join(sample(char_sequence, 16))
926
928 if gajim.os_info:
929 return gajim.os_info
930 if os.name == 'nt':
931 # platform.release() seems to return the name of the windows
932 ver = sys.getwindowsversion()
933 ver_format = ver[3], ver[0], ver[1]
934 win_version = {
935 (1, 4, 0): '95',
936 (1, 4, 10): '98',
937 (1, 4, 90): 'ME',
938 (2, 4, 0): 'NT',
939 (2, 5, 0): '2000',
940 (2, 5, 1): 'XP',
941 (2, 5, 2): '2003',
942 (2, 6, 0): 'Vista',
943 (2, 6, 1): '7',
944 }
945 if ver_format in win_version:
946 os_info = 'Windows' + ' ' + win_version[ver_format]
947 else:
948 os_info = 'Windows'
949 gajim.os_info = os_info
950 return os_info
951 elif os.name == 'posix':
952 executable = 'lsb_release'
953 params = ' --description --codename --release --short'
954 full_path_to_executable = is_in_path(executable, return_abs_path = True)
955 if full_path_to_executable:
956 command = executable + params
957 p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE,
958 stdout=subprocess.PIPE, close_fds=True)
959 p.wait()
960 output = temp_failure_retry(p.stdout.readline).strip()
961 # some distros put n/a in places, so remove those
962 output = output.replace('n/a', '').replace('N/A', '')
963 gajim.os_info = output
964 return output
965
966 # lsb_release executable not available, so parse files
967 for distro_name in distro_info:
968 path_to_file = distro_info[distro_name]
969 if os.path.exists(path_to_file):
970 if os.access(path_to_file, os.X_OK):
971 # the file is executable (f.e. CRUX)
972 # yes, then run it and get the first line of output.
973 text = get_output_of_command(path_to_file)[0]
974 else:
975 fd = open(path_to_file)
976 text = fd.readline().strip() # get only first line
977 fd.close()
978 if path_to_file.endswith('version'):
979 # sourcemage_version and slackware-version files
980 # have all the info we need (name and version of distro)
981 if not os.path.basename(path_to_file).startswith(
982 'sourcemage') or not\
983 os.path.basename(path_to_file).startswith('slackware'):
984 text = distro_name + ' ' + text
985 elif path_to_file.endswith('aurox-release') or \
986 path_to_file.endswith('arch-release'):
987 # file doesn't have version
988 text = distro_name
989 elif path_to_file.endswith('lfs-release'): # file just has version
990 text = distro_name + ' ' + text
991 os_info = text.replace('\n', '')
992 gajim.os_info = os_info
993 return os_info
994
995 # our last chance, ask uname and strip it
996 uname_output = get_output_of_command('uname -sr')
997 if uname_output is not None:
998 os_info = uname_output[0] # only first line
999 gajim.os_info = os_info
1000 return os_info
1001 os_info = 'N/A'
1002 gajim.os_info = os_info
1003 return os_info
1004
1005
1006 -def allow_showing_notification(account, type_ = 'notify_on_new_message',
1007 advanced_notif_num = None, is_first_message = True):
1008 """
1009 Is it allowed to show nofication?
1010
1011 Check OUR status and if we allow notifications for that status type is the
1012 option that need to be True e.g.: notify_on_signing is_first_message: set it
1013 to false when it's not the first message
1014 """
1015 if advanced_notif_num is not None:
1016 popup = gajim.config.get_per('notifications', str(advanced_notif_num),
1017 'popup')
1018 if popup == 'yes':
1019 return True
1020 if popup == 'no':
1021 return False
1022 if type_ and (not gajim.config.get(type_) or not is_first_message):
1023 return False
1024 if gajim.config.get('autopopupaway'): # always show notification
1025 return True
1026 if gajim.connections[account].connected in (2, 3): # we're online or chat
1027 return True
1028 return False
1029
1031 """
1032 Is it allowed to popup windows?
1033 """
1034 if advanced_notif_num is not None:
1035 popup = gajim.config.get_per('notifications', str(advanced_notif_num),
1036 'auto_open')
1037 if popup == 'yes':
1038 return True
1039 if popup == 'no':
1040 return False
1041 autopopup = gajim.config.get('autopopup')
1042 autopopupaway = gajim.config.get('autopopupaway')
1043 if autopopup and (autopopupaway or \
1044 gajim.connections[account].connected in (2, 3)): # we're online or chat
1045 return True
1046 return False
1047
1049 if advanced_notif_num is not None:
1050 sound = gajim.config.get_per('notifications', str(advanced_notif_num),
1051 'sound')
1052 if sound == 'yes':
1053 return True
1054 if sound == 'no':
1055 return False
1056 if gajim.config.get('sounddnd') or gajim.connections[account].connected != \
1057 gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents',
1058 sound_event, 'enabled'):
1059 return True
1060 return False
1061
1063 full_jid_with_resource = contact.jid
1064 if contact.resource:
1065 full_jid_with_resource += '/' + contact.resource
1066 highest_contact = gajim.contacts.get_contact_with_highest_priority(
1067 account, contact.jid)
1068
1069 # Look for a chat control that has the given resource, or default to
1070 # one without resource
1071 ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
1072 account)
1073
1074 if ctrl:
1075 return ctrl
1076 elif highest_contact and highest_contact.resource and \
1077 contact.resource != highest_contact.resource:
1078 return None
1079 else:
1080 # unknown contact or offline message
1081 return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
1082
1084 """
1085 Return a dict of the form {acct: {'show': show, 'message': message,
1086 'event_lines': [list of text lines to show in tooltip]}
1087 """
1088 # How many events must there be before they're shown summarized, not per-user
1089 max_ungrouped_events = 10
1090
1091 accounts = get_accounts_info()
1092
1093 # Gather events. (With accounts, when there are more.)
1094 for account in accounts:
1095 account_name = account['name']
1096 account['event_lines'] = []
1097 # Gather events per-account
1098 pending_events = gajim.events.get_events(account = account_name)
1099 messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0
1100 for jid in pending_events:
1101 for event in pending_events[jid]:
1102 if event.type_.count('file') > 0:
1103 # This is a non-messagee event.
1104 messages[jid] = non_messages.get(jid, 0) + 1
1105 total_non_messages = total_non_messages + 1
1106 else:
1107 # This is a message.
1108 messages[jid] = messages.get(jid, 0) + 1
1109 total_messages = total_messages + 1
1110 # Display unread messages numbers, if any
1111 if total_messages > 0:
1112 if total_messages > max_ungrouped_events:
1113 text = ngettext(
1114 '%d message pending',
1115 '%d messages pending',
1116 total_messages, total_messages, total_messages)
1117 account['event_lines'].append(text)
1118 else:
1119 for jid in messages.keys():
1120 text = ngettext(
1121 '%d message pending',
1122 '%d messages pending',
1123 messages[jid], messages[jid], messages[jid])
1124 contact = gajim.contacts.get_first_contact_from_jid(
1125 account['name'], jid)
1126 if jid in gajim.gc_connected[account['name']]:
1127 text += _(' from room %s') % (jid)
1128 elif contact:
1129 name = contact.get_shown_name()
1130 text += _(' from user %s') % (name)
1131 else:
1132 text += _(' from %s') % (jid)
1133 account['event_lines'].append(text)
1134
1135 # Display unseen events numbers, if any
1136 if total_non_messages > 0:
1137 if total_non_messages > max_ungrouped_events:
1138 text = ngettext(
1139 '%d event pending',
1140 '%d events pending',
1141 total_non_messages, total_non_messages, total_non_messages)
1142 account['event_lines'].append(text)
1143 else:
1144 for jid in non_messages.keys():
1145 text = ngettext(
1146 '%d event pending',
1147 '%d events pending',
1148 non_messages[jid], non_messages[jid], non_messages[jid])
1149 text += _(' from user %s') % (jid)
1150 account[account]['event_lines'].append(text)
1151
1152 return accounts
1153
1155 text = None
1156 # How many events must there be before they're shown summarized, not per-user
1157 # max_ungrouped_events = 10
1158 # Character which should be used to indent in the tooltip.
1159 indent_with = ' '
1160
1161 accounts = get_notification_icon_tooltip_dict()
1162
1163 if len(accounts) == 0:
1164 # No configured account
1165 return _('Gajim')
1166
1167 # at least one account present
1168
1169 # Is there more that one account?
1170 if len(accounts) == 1:
1171 show_more_accounts = False
1172 else:
1173 show_more_accounts = True
1174
1175 # If there is only one account, its status is shown on the first line.
1176 if show_more_accounts:
1177 text = _('Gajim')
1178 else:
1179 text = _('Gajim - %s') % (get_account_status(accounts[0]))
1180
1181 # Gather and display events. (With accounts, when there are more.)
1182 for account in accounts:
1183 account_name = account['name']
1184 # Set account status, if not set above
1185 if (show_more_accounts):
1186 message = '\n' + indent_with + ' %s - %s'
1187 text += message % (account_name, get_account_status(account))
1188 # Account list shown, messages need to be indented more
1189 indent_how = 2
1190 else:
1191 # If no account list is shown, messages could have default indenting.
1192 indent_how = 1
1193 for line in account['event_lines']:
1194 text += '\n' + indent_with * indent_how + ' '
1195 text += line
1196 return text
1197
1199 """
1200 Helper for notification icon tooltip
1201 """
1202 accounts = []
1203 accounts_list = sorted(gajim.contacts.get_accounts())
1204 for account in accounts_list:
1205 status_idx = gajim.connections[account].connected
1206 # uncomment the following to hide offline accounts
1207 # if status_idx == 0: continue
1208 status = gajim.SHOW_LIST[status_idx]
1209 message = gajim.connections[account].status
1210 single_line = get_uf_show(status)
1211 if message is None:
1212 message = ''
1213 else:
1214 message = message.strip()
1215 if message != '':
1216 single_line += ': ' + message
1217 accounts.append({'name': account, 'status_line': single_line,
1218 'show': status, 'message': message})
1219 return accounts
1220
1221
1223 if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
1224 return os.path.join(gajim.DATA_DIR, 'iconsets', iconset)
1225 elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)):
1226 return os.path.join(gajim.MY_ICONSETS_PATH, iconset)
1227
1229 if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)):
1230 return os.path.join(gajim.DATA_DIR, 'moods', iconset)
1231 elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)):
1232 return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)
1233
1235 if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)):
1236 return os.path.join(gajim.DATA_DIR, 'activities', iconset)
1237 elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH,
1238 iconset)):
1239 return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset)
1240
1242 if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
1243 transport)):
1244 return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport)
1245 elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
1246 transport)):
1247 return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
1248 # No transport folder found, use default jabber one
1249 return get_iconset_path(gajim.config.get('iconset'))
1250
1252 """
1253 Return an eight char long keyID that can be used with for GPG encryption
1254 with this contact
1255
1256 If the given keyID is None, return UNKNOWN; if the key does not match the
1257 assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet
1258 assigned, assign it.
1259 """
1260 if gajim.connections[account].USE_GPG:
1261 if keyID and len(keyID) == 16:
1262 keyID = keyID[8:]
1263
1264 attached_keys = gajim.config.get_per('accounts', account,
1265 'attached_gpg_keys').split()
1266
1267 if jid in attached_keys and keyID:
1268 attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
1269 if attachedkeyID != keyID:
1270 # Mismatch! Another gpg key was expected
1271 keyID += 'MISMATCH'
1272 elif jid in attached_keys:
1273 # An unsigned presence, just use the assigned key
1274 keyID = attached_keys[attached_keys.index(jid) + 1]
1275 elif keyID:
1276 public_keys = gajim.connections[account].ask_gpg_keys()
1277 # Assign the corresponding key, if we have it in our keyring
1278 if keyID in public_keys:
1279 for u in gajim.contacts.get_contacts(account, jid):
1280 u.keyID = keyID
1281 keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys')
1282 keys_str += jid + ' ' + keyID + ' '
1283 gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str)
1284 elif keyID is None:
1285 keyID = 'UNKNOWN'
1286 return keyID
1287
1289 import xmpp
1290 if account:
1291 accounts = [account]
1292 else:
1293 accounts = [a for a in gajim.connections]
1294 for a in accounts:
1295 gajim.gajim_optional_features[a] = []
1296 if gajim.config.get_per('accounts', a, 'subscribe_mood'):
1297 gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify')
1298 if gajim.config.get_per('accounts', a, 'subscribe_activity'):
1299 gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify')
1300 if gajim.config.get_per('accounts', a, 'publish_tune'):
1301 gajim.gajim_optional_features[a].append(xmpp.NS_TUNE)
1302 if gajim.config.get_per('accounts', a, 'publish_location'):
1303 gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION)
1304 if gajim.config.get_per('accounts', a, 'subscribe_tune'):
1305 gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify')
1306 if gajim.config.get_per('accounts', a, 'subscribe_nick'):
1307 gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify')
1308 if gajim.config.get_per('accounts', a, 'subscribe_location'):
1309 gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify')
1310 if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
1311 gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES)
1312 if not gajim.config.get('ignore_incoming_xhtml'):
1313 gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM)
1314 if gajim.HAVE_PYCRYPTO \
1315 and gajim.config.get_per('accounts', a, 'enable_esessions'):
1316 gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION)
1317 if gajim.config.get_per('accounts', a, 'answer_receipts'):
1318 gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS)
1319 if gajim.HAVE_FARSIGHT:
1320 gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE)
1321 gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP)
1322 gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
1323 gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
1324 gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
1325 gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
1326 gajim.gajim_common_features + gajim.gajim_optional_features[a])
1327 # re-send presence with new hash
1328 connected = gajim.connections[a].connected
1329 if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
1330 gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
1331 gajim.connections[a].status)
1332
1334 return ((jid in gajim.connections[account].blocked_contacts) or \
1335 gajim.connections[account].blocked_all)
1336
1338 return ((group in gajim.connections[account].blocked_groups) or \
1339 gajim.connections[account].blocked_all)
1340
1342 s = gajim.config.get_per('accounts', account, 'subscription_request_msg')
1343 if s:
1344 return s
1345 s = _('I would like to add you to my contact list.')
1346 if account:
1347 s = _('Hello, I am $name.') + ' ' + s
1348 our_jid = gajim.get_jid_from_account(account)
1349 vcard = gajim.connections[account].get_cached_vcard(our_jid)
1350 name = ''
1351 if vcard:
1352 if 'N' in vcard:
1353 if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
1354 name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
1355 if not name and 'FN' in vcard:
1356 name = vcard['FN']
1357 nick = gajim.nicks[account]
1358 if name and nick:
1359 name += ' (%s)' % nick
1360 elif nick:
1361 name = nick
1362 s = Template(s).safe_substitute({'name': name})
1363 return s
1364
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Aug 12 02:07:35 2010 | http://epydoc.sourceforge.net |