reorder functions in common/helpers.py. Fixes #4764. Thanks Vardo!
This commit is contained in:
parent
baa208d0b6
commit
e49c64a452
|
@ -42,16 +42,10 @@ import hashlib
|
||||||
|
|
||||||
from encodings.punycode import punycode_encode
|
from encodings.punycode import punycode_encode
|
||||||
|
|
||||||
import gajim
|
|
||||||
from i18n import Q_
|
from i18n import Q_
|
||||||
from i18n import ngettext
|
from i18n import ngettext
|
||||||
import xmpp
|
import xmpp
|
||||||
|
|
||||||
try:
|
|
||||||
from osx import nsapp
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import winsound # windows-only built-in module for playing wav
|
import winsound # windows-only built-in module for playing wav
|
||||||
import win32api
|
import win32api
|
||||||
|
@ -187,65 +181,6 @@ def temp_failure_retry(func, *args, **kwargs):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def convert_bytes(string):
|
|
||||||
suffix = ''
|
|
||||||
# IEC standard says KiB = 1024 bytes KB = 1000 bytes
|
|
||||||
# but do we use the standard?
|
|
||||||
use_kib_mib = gajim.config.get('use_kib_mib')
|
|
||||||
align = 1024.
|
|
||||||
bytes = float(string)
|
|
||||||
if bytes >= align:
|
|
||||||
bytes = round(bytes/align, 1)
|
|
||||||
if bytes >= align:
|
|
||||||
bytes = round(bytes/align, 1)
|
|
||||||
if bytes >= align:
|
|
||||||
bytes = round(bytes/align, 1)
|
|
||||||
if use_kib_mib:
|
|
||||||
#GiB means gibibyte
|
|
||||||
suffix = _('%s GiB')
|
|
||||||
else:
|
|
||||||
#GB means gigabyte
|
|
||||||
suffix = _('%s GB')
|
|
||||||
else:
|
|
||||||
if use_kib_mib:
|
|
||||||
#MiB means mibibyte
|
|
||||||
suffix = _('%s MiB')
|
|
||||||
else:
|
|
||||||
#MB means megabyte
|
|
||||||
suffix = _('%s MB')
|
|
||||||
else:
|
|
||||||
if use_kib_mib:
|
|
||||||
#KiB means kibibyte
|
|
||||||
suffix = _('%s KiB')
|
|
||||||
else:
|
|
||||||
#KB means kilo bytes
|
|
||||||
suffix = _('%s KB')
|
|
||||||
else:
|
|
||||||
#B means bytes
|
|
||||||
suffix = _('%s B')
|
|
||||||
return suffix % unicode(bytes)
|
|
||||||
|
|
||||||
|
|
||||||
def get_contact_dict_for_account(account):
|
|
||||||
''' create a dict of jid, nick -> contact with all contacts of account.
|
|
||||||
Can be used for completion lists'''
|
|
||||||
contacts_dict = {}
|
|
||||||
for jid in gajim.contacts.get_jid_list(account):
|
|
||||||
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
|
||||||
jid)
|
|
||||||
contacts_dict[jid] = contact
|
|
||||||
name = contact.name
|
|
||||||
if name in contacts_dict:
|
|
||||||
contact1 = contacts_dict[name]
|
|
||||||
del contacts_dict[name]
|
|
||||||
contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
|
|
||||||
contacts_dict['%s (%s)' % (name, jid)] = contact
|
|
||||||
else:
|
|
||||||
if contact.name == gajim.get_nick_from_jid(jid):
|
|
||||||
del contacts_dict[jid]
|
|
||||||
contacts_dict[name] = contact
|
|
||||||
return contacts_dict
|
|
||||||
|
|
||||||
def get_uf_show(show, use_mnemonic = False):
|
def get_uf_show(show, use_mnemonic = False):
|
||||||
'''returns a userfriendly string for dnd/xa/chat
|
'''returns a userfriendly string for dnd/xa/chat
|
||||||
and makes all strings translatable
|
and makes all strings translatable
|
||||||
|
@ -426,6 +361,347 @@ def build_command(executable, parameter):
|
||||||
command = '%s "%s"' % (executable, parameter)
|
command = '%s "%s"' % (executable, parameter)
|
||||||
return command
|
return command
|
||||||
|
|
||||||
|
def get_file_path_from_dnd_dropped_uri(uri):
|
||||||
|
path = urllib.unquote(uri) # escape special chars
|
||||||
|
path = path.strip('\r\n\x00') # remove \r\n and NULL
|
||||||
|
# get the path to file
|
||||||
|
if re.match('^file:///[a-zA-Z]:/', path): # windows
|
||||||
|
path = path[8:] # 8 is len('file:///')
|
||||||
|
elif path.startswith('file://'): # nautilus, rox
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
# OS/X includes hostname in file:// URI
|
||||||
|
path = re.sub('file://[^/]*', '', path)
|
||||||
|
else:
|
||||||
|
path = path[7:] # 7 is len('file://')
|
||||||
|
elif path.startswith('file:'): # xffm
|
||||||
|
path = path[5:] # 5 is len('file:')
|
||||||
|
return path
|
||||||
|
|
||||||
|
def from_xs_boolean_to_python_boolean(value):
|
||||||
|
# this is xs:boolean so 'true', 'false', '1', '0'
|
||||||
|
# convert those to True/False (python booleans)
|
||||||
|
if value in ('1', 'true'):
|
||||||
|
val = True
|
||||||
|
else: # '0', 'false' or anything else
|
||||||
|
val = False
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
def get_xmpp_show(show):
|
||||||
|
if show in ('online', 'offline'):
|
||||||
|
return None
|
||||||
|
return show
|
||||||
|
|
||||||
|
def get_output_of_command(command):
|
||||||
|
try:
|
||||||
|
child_stdin, child_stdout = os.popen2(command)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
output = child_stdout.readlines()
|
||||||
|
child_stdout.close()
|
||||||
|
child_stdin.close()
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def decode_string(string):
|
||||||
|
'''try to decode (to make it Unicode instance) given string'''
|
||||||
|
if isinstance(string, unicode):
|
||||||
|
return string
|
||||||
|
# by the time we go to iso15 it better be the one else we show bad characters
|
||||||
|
encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
|
||||||
|
for encoding in encodings:
|
||||||
|
try:
|
||||||
|
string = string.decode(encoding)
|
||||||
|
except UnicodeError:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
|
def ensure_utf8_string(string):
|
||||||
|
'''make sure string is in UTF-8'''
|
||||||
|
try:
|
||||||
|
string = decode_string(string).encode('utf-8')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return string
|
||||||
|
|
||||||
|
def get_windows_reg_env(varname, default=''):
|
||||||
|
'''asks for paths commonly used but not exposed as ENVs
|
||||||
|
in english Windows 2003 those are:
|
||||||
|
'AppData' = %USERPROFILE%\Application Data (also an ENV)
|
||||||
|
'Desktop' = %USERPROFILE%\Desktop
|
||||||
|
'Favorites' = %USERPROFILE%\Favorites
|
||||||
|
'NetHood' = %USERPROFILE%\NetHood
|
||||||
|
'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
|
||||||
|
'PrintHood' = %USERPROFILE%\PrintHood
|
||||||
|
'Programs' = %USERPROFILE%\Start Menu\Programs
|
||||||
|
'Recent' = %USERPROFILE%\Recent
|
||||||
|
'SendTo' = %USERPROFILE%\SendTo
|
||||||
|
'Start Menu' = %USERPROFILE%\Start Menu
|
||||||
|
'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
|
||||||
|
'Templates' = %USERPROFILE%\Templates
|
||||||
|
'My Pictures' = D:\My Documents\My Pictures
|
||||||
|
'Local Settings' = %USERPROFILE%\Local Settings
|
||||||
|
'Local AppData' = %USERPROFILE%\Local Settings\Application Data
|
||||||
|
'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
|
||||||
|
'Cookies' = %USERPROFILE%\Cookies
|
||||||
|
'History' = %USERPROFILE%\Local Settings\History
|
||||||
|
'''
|
||||||
|
|
||||||
|
if os.name != 'nt':
|
||||||
|
return ''
|
||||||
|
|
||||||
|
val = default
|
||||||
|
try:
|
||||||
|
rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,
|
||||||
|
r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders')
|
||||||
|
try:
|
||||||
|
val = str(win32api.RegQueryValueEx(rkey, varname)[0])
|
||||||
|
val = win32api.ExpandEnvironmentStrings(val) # expand using environ
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
win32api.RegCloseKey(rkey)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def get_my_pictures_path():
|
||||||
|
'''windows-only atm. [Unix lives in the past]'''
|
||||||
|
return get_windows_reg_env('My Pictures')
|
||||||
|
|
||||||
|
def get_desktop_path():
|
||||||
|
if os.name == 'nt':
|
||||||
|
path = get_windows_reg_env('Desktop')
|
||||||
|
else:
|
||||||
|
path = os.path.join(os.path.expanduser('~'), 'Desktop')
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_documents_path():
|
||||||
|
if os.name == 'nt':
|
||||||
|
path = get_windows_reg_env('Personal')
|
||||||
|
else:
|
||||||
|
path = os.path.expanduser('~')
|
||||||
|
return path
|
||||||
|
|
||||||
|
def sanitize_filename(filename):
|
||||||
|
'''makes sure the filename we will write does contain only acceptable and
|
||||||
|
latin characters, and is not too long (in that case hash it)'''
|
||||||
|
# 48 is the limit
|
||||||
|
if len(filename) > 48:
|
||||||
|
hash = hashlib.md5(filename)
|
||||||
|
filename = base64.b64encode(hash.digest())
|
||||||
|
|
||||||
|
filename = punycode_encode(filename) # make it latin chars only
|
||||||
|
filename = filename.replace('/', '_')
|
||||||
|
if os.name == 'nt':
|
||||||
|
filename = filename.replace('?', '_').replace(':', '_')\
|
||||||
|
.replace('\\', '_').replace('"', "'").replace('|', '_')\
|
||||||
|
.replace('*', '_').replace('<', '_').replace('>', '_')
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
|
||||||
|
'''Cut the chars after 'max_chars' on each line
|
||||||
|
and show only the first 'max_lines'.
|
||||||
|
If any of the params is not present (None or 0) the action
|
||||||
|
on it is not performed'''
|
||||||
|
|
||||||
|
def _cut_if_long(string):
|
||||||
|
if len(string) > max_chars:
|
||||||
|
string = string[:max_chars - 3] + '...'
|
||||||
|
return string
|
||||||
|
|
||||||
|
if isinstance(text, str):
|
||||||
|
text = text.decode('utf-8')
|
||||||
|
|
||||||
|
if max_lines == 0:
|
||||||
|
lines = text.split('\n')
|
||||||
|
else:
|
||||||
|
lines = text.split('\n', max_lines)[:max_lines]
|
||||||
|
if max_chars > 0:
|
||||||
|
if lines:
|
||||||
|
lines = [_cut_if_long(e) for e in lines]
|
||||||
|
if lines:
|
||||||
|
reduced_text = '\n'.join(lines)
|
||||||
|
if reduced_text != text:
|
||||||
|
reduced_text += '...'
|
||||||
|
else:
|
||||||
|
reduced_text = ''
|
||||||
|
return reduced_text
|
||||||
|
|
||||||
|
def get_account_status(account):
|
||||||
|
status = reduce_chars_newlines(account['status_line'], 100, 1)
|
||||||
|
return status
|
||||||
|
|
||||||
|
def get_avatar_path(prefix):
|
||||||
|
'''Returns the filename of the avatar, distinguishes between user- and
|
||||||
|
contact-provided one. Returns None if no avatar was found at all.
|
||||||
|
prefix is the path to the requested avatar just before the ".png" or
|
||||||
|
".jpeg".'''
|
||||||
|
# First, scan for a local, user-set avatar
|
||||||
|
for type_ in ('jpeg', 'png'):
|
||||||
|
file_ = prefix + '_local.' + type_
|
||||||
|
if os.path.exists(file_):
|
||||||
|
return file_
|
||||||
|
# If none available, scan for a contact-provided avatar
|
||||||
|
for type_ in ('jpeg', 'png'):
|
||||||
|
file_ = prefix + '.' + type_
|
||||||
|
if os.path.exists(file_):
|
||||||
|
return file_
|
||||||
|
return None
|
||||||
|
|
||||||
|
def datetime_tuple(timestamp):
|
||||||
|
'''Converts timestamp using strptime and the format: %Y%m%dT%H:%M:%S
|
||||||
|
Because of various datetime formats are used the following exceptions
|
||||||
|
are handled:
|
||||||
|
- Optional milliseconds appened to the string are removed
|
||||||
|
- Optional Z (that means UTC) appened to the string are removed
|
||||||
|
- XEP-082 datetime strings have all '-' cahrs removed to meet
|
||||||
|
the above format.'''
|
||||||
|
timestamp = timestamp.split('.')[0]
|
||||||
|
timestamp = timestamp.replace('-', '')
|
||||||
|
timestamp = timestamp.replace('z', '')
|
||||||
|
timestamp = timestamp.replace('Z', '')
|
||||||
|
from time import strptime
|
||||||
|
return strptime(timestamp, '%Y%m%dT%H:%M:%S')
|
||||||
|
|
||||||
|
def sort_identities_func(i1, i2):
|
||||||
|
cat1 = i1['category']
|
||||||
|
cat2 = i2['category']
|
||||||
|
if cat1 < cat2:
|
||||||
|
return -1
|
||||||
|
if cat1 > cat2:
|
||||||
|
return 1
|
||||||
|
type1 = i1.get('type', '')
|
||||||
|
type2 = i2.get('type', '')
|
||||||
|
if type1 < type2:
|
||||||
|
return -1
|
||||||
|
if type1 > type2:
|
||||||
|
return 1
|
||||||
|
lang1 = i1.get('xml:lang', '')
|
||||||
|
lang2 = i2.get('xml:lang', '')
|
||||||
|
if lang1 < lang2:
|
||||||
|
return -1
|
||||||
|
if lang1 > lang2:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def sort_dataforms_func(d1, d2):
|
||||||
|
f1 = d1.getField('FORM_TYPE')
|
||||||
|
f2 = d2.getField('FORM_TYPE')
|
||||||
|
if f1 and f2 and (f1.getValue() < f2.getValue()):
|
||||||
|
return -1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
|
||||||
|
'''Compute caps hash according to XEP-0115, V1.5
|
||||||
|
|
||||||
|
dataforms are xmpp.DataForms objects as common.dataforms don't allow several
|
||||||
|
values without a field type list-multi'''
|
||||||
|
S = ''
|
||||||
|
identities.sort(cmp=sort_identities_func)
|
||||||
|
for i in identities:
|
||||||
|
c = i['category']
|
||||||
|
type_ = i.get('type', '')
|
||||||
|
lang = i.get('xml:lang', '')
|
||||||
|
name = i.get('name', '')
|
||||||
|
S += '%s/%s/%s/%s<' % (c, type_, lang, name)
|
||||||
|
features.sort()
|
||||||
|
for f in features:
|
||||||
|
S += '%s<' % f
|
||||||
|
dataforms.sort(cmp=sort_dataforms_func)
|
||||||
|
for dataform in dataforms:
|
||||||
|
# fields indexed by var
|
||||||
|
fields = {}
|
||||||
|
for f in dataform.getChildren():
|
||||||
|
fields[f.getVar()] = f
|
||||||
|
form_type = fields.get('FORM_TYPE')
|
||||||
|
if form_type:
|
||||||
|
S += form_type.getValue() + '<'
|
||||||
|
del fields['FORM_TYPE']
|
||||||
|
for var in sorted(fields.keys()):
|
||||||
|
S += '%s<' % var
|
||||||
|
values = sorted(fields[var].getValues())
|
||||||
|
for value in values:
|
||||||
|
S += '%s<' % value
|
||||||
|
|
||||||
|
if hash_method == 'sha-1':
|
||||||
|
hash_ = hashlib.sha1(S)
|
||||||
|
elif hash_method == 'md5':
|
||||||
|
hash_ = hashlib.md5(S)
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
return base64.b64encode(hash_.digest())
|
||||||
|
|
||||||
|
# import gajim only when needed (after decode_string is defined) see #4764
|
||||||
|
|
||||||
|
import gajim
|
||||||
|
|
||||||
|
try:
|
||||||
|
from osx import nsapp
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def convert_bytes(string):
|
||||||
|
suffix = ''
|
||||||
|
# IEC standard says KiB = 1024 bytes KB = 1000 bytes
|
||||||
|
# but do we use the standard?
|
||||||
|
use_kib_mib = gajim.config.get('use_kib_mib')
|
||||||
|
align = 1024.
|
||||||
|
bytes = float(string)
|
||||||
|
if bytes >= align:
|
||||||
|
bytes = round(bytes/align, 1)
|
||||||
|
if bytes >= align:
|
||||||
|
bytes = round(bytes/align, 1)
|
||||||
|
if bytes >= align:
|
||||||
|
bytes = round(bytes/align, 1)
|
||||||
|
if use_kib_mib:
|
||||||
|
#GiB means gibibyte
|
||||||
|
suffix = _('%s GiB')
|
||||||
|
else:
|
||||||
|
#GB means gigabyte
|
||||||
|
suffix = _('%s GB')
|
||||||
|
else:
|
||||||
|
if use_kib_mib:
|
||||||
|
#MiB means mibibyte
|
||||||
|
suffix = _('%s MiB')
|
||||||
|
else:
|
||||||
|
#MB means megabyte
|
||||||
|
suffix = _('%s MB')
|
||||||
|
else:
|
||||||
|
if use_kib_mib:
|
||||||
|
#KiB means kibibyte
|
||||||
|
suffix = _('%s KiB')
|
||||||
|
else:
|
||||||
|
#KB means kilo bytes
|
||||||
|
suffix = _('%s KB')
|
||||||
|
else:
|
||||||
|
#B means bytes
|
||||||
|
suffix = _('%s B')
|
||||||
|
return suffix % unicode(bytes)
|
||||||
|
|
||||||
|
def get_contact_dict_for_account(account):
|
||||||
|
''' create a dict of jid, nick -> contact with all contacts of account.
|
||||||
|
Can be used for completion lists'''
|
||||||
|
contacts_dict = {}
|
||||||
|
for jid in gajim.contacts.get_jid_list(account):
|
||||||
|
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||||
|
jid)
|
||||||
|
contacts_dict[jid] = contact
|
||||||
|
name = contact.name
|
||||||
|
if name in contacts_dict:
|
||||||
|
contact1 = contacts_dict[name]
|
||||||
|
del contacts_dict[name]
|
||||||
|
contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
|
||||||
|
contacts_dict['%s (%s)' % (name, jid)] = contact
|
||||||
|
else:
|
||||||
|
if contact.name == gajim.get_nick_from_jid(jid):
|
||||||
|
del contacts_dict[jid]
|
||||||
|
contacts_dict[name] = contact
|
||||||
|
return contacts_dict
|
||||||
|
|
||||||
def launch_browser_mailer(kind, uri):
|
def launch_browser_mailer(kind, uri):
|
||||||
#kind = 'url' or 'mail'
|
#kind = 'url' or 'mail'
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
|
@ -517,49 +793,6 @@ def play_sound_file(path_to_soundfile):
|
||||||
command = build_command(player, path_to_soundfile)
|
command = build_command(player, path_to_soundfile)
|
||||||
exec_command(command)
|
exec_command(command)
|
||||||
|
|
||||||
def get_file_path_from_dnd_dropped_uri(uri):
|
|
||||||
path = urllib.unquote(uri) # escape special chars
|
|
||||||
path = path.strip('\r\n\x00') # remove \r\n and NULL
|
|
||||||
# get the path to file
|
|
||||||
if re.match('^file:///[a-zA-Z]:/', path): # windows
|
|
||||||
path = path[8:] # 8 is len('file:///')
|
|
||||||
elif path.startswith('file://'): # nautilus, rox
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
# OS/X includes hostname in file:// URI
|
|
||||||
path = re.sub('file://[^/]*', '', path)
|
|
||||||
else:
|
|
||||||
path = path[7:] # 7 is len('file://')
|
|
||||||
elif path.startswith('file:'): # xffm
|
|
||||||
path = path[5:] # 5 is len('file:')
|
|
||||||
return path
|
|
||||||
|
|
||||||
def from_xs_boolean_to_python_boolean(value):
|
|
||||||
# this is xs:boolean so 'true', 'false', '1', '0'
|
|
||||||
# convert those to True/False (python booleans)
|
|
||||||
if value in ('1', 'true'):
|
|
||||||
val = True
|
|
||||||
else: # '0', 'false' or anything else
|
|
||||||
val = False
|
|
||||||
|
|
||||||
return val
|
|
||||||
|
|
||||||
def get_xmpp_show(show):
|
|
||||||
if show in ('online', 'offline'):
|
|
||||||
return None
|
|
||||||
return show
|
|
||||||
|
|
||||||
def get_output_of_command(command):
|
|
||||||
try:
|
|
||||||
child_stdin, child_stdout = os.popen2(command)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
output = child_stdout.readlines()
|
|
||||||
child_stdout.close()
|
|
||||||
child_stdin.close()
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def get_global_show():
|
def get_global_show():
|
||||||
maxi = 0
|
maxi = 0
|
||||||
for account in gajim.connections:
|
for account in gajim.connections:
|
||||||
|
@ -625,91 +858,6 @@ def get_icon_name_to_show(contact, account = None):
|
||||||
return contact.show
|
return contact.show
|
||||||
return 'not in roster'
|
return 'not in roster'
|
||||||
|
|
||||||
def decode_string(string):
|
|
||||||
'''try to decode (to make it Unicode instance) given string'''
|
|
||||||
if isinstance(string, unicode):
|
|
||||||
return string
|
|
||||||
# by the time we go to iso15 it better be the one else we show bad characters
|
|
||||||
encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
|
|
||||||
for encoding in encodings:
|
|
||||||
try:
|
|
||||||
string = string.decode(encoding)
|
|
||||||
except UnicodeError:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
return string
|
|
||||||
|
|
||||||
def ensure_utf8_string(string):
|
|
||||||
'''make sure string is in UTF-8'''
|
|
||||||
try:
|
|
||||||
string = decode_string(string).encode('utf-8')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return string
|
|
||||||
|
|
||||||
def remove_invalid_xml_chars(string):
|
|
||||||
if string:
|
|
||||||
string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
|
|
||||||
return string
|
|
||||||
|
|
||||||
def get_windows_reg_env(varname, default=''):
|
|
||||||
'''asks for paths commonly used but not exposed as ENVs
|
|
||||||
in english Windows 2003 those are:
|
|
||||||
'AppData' = %USERPROFILE%\Application Data (also an ENV)
|
|
||||||
'Desktop' = %USERPROFILE%\Desktop
|
|
||||||
'Favorites' = %USERPROFILE%\Favorites
|
|
||||||
'NetHood' = %USERPROFILE%\NetHood
|
|
||||||
'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
|
|
||||||
'PrintHood' = %USERPROFILE%\PrintHood
|
|
||||||
'Programs' = %USERPROFILE%\Start Menu\Programs
|
|
||||||
'Recent' = %USERPROFILE%\Recent
|
|
||||||
'SendTo' = %USERPROFILE%\SendTo
|
|
||||||
'Start Menu' = %USERPROFILE%\Start Menu
|
|
||||||
'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
|
|
||||||
'Templates' = %USERPROFILE%\Templates
|
|
||||||
'My Pictures' = D:\My Documents\My Pictures
|
|
||||||
'Local Settings' = %USERPROFILE%\Local Settings
|
|
||||||
'Local AppData' = %USERPROFILE%\Local Settings\Application Data
|
|
||||||
'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
|
|
||||||
'Cookies' = %USERPROFILE%\Cookies
|
|
||||||
'History' = %USERPROFILE%\Local Settings\History
|
|
||||||
'''
|
|
||||||
|
|
||||||
if os.name != 'nt':
|
|
||||||
return ''
|
|
||||||
|
|
||||||
val = default
|
|
||||||
try:
|
|
||||||
rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,
|
|
||||||
r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders')
|
|
||||||
try:
|
|
||||||
val = str(win32api.RegQueryValueEx(rkey, varname)[0])
|
|
||||||
val = win32api.ExpandEnvironmentStrings(val) # expand using environ
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
win32api.RegCloseKey(rkey)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def get_my_pictures_path():
|
|
||||||
'''windows-only atm. [Unix lives in the past]'''
|
|
||||||
return get_windows_reg_env('My Pictures')
|
|
||||||
|
|
||||||
def get_desktop_path():
|
|
||||||
if os.name == 'nt':
|
|
||||||
path = get_windows_reg_env('Desktop')
|
|
||||||
else:
|
|
||||||
path = os.path.join(os.path.expanduser('~'), 'Desktop')
|
|
||||||
return path
|
|
||||||
|
|
||||||
def get_documents_path():
|
|
||||||
if os.name == 'nt':
|
|
||||||
path = get_windows_reg_env('Personal')
|
|
||||||
else:
|
|
||||||
path = os.path.expanduser('~')
|
|
||||||
return path
|
|
||||||
|
|
||||||
def get_full_jid_from_iq(iq_obj):
|
def get_full_jid_from_iq(iq_obj):
|
||||||
'''return the full jid (with resource) from an iq as unicode'''
|
'''return the full jid (with resource) from an iq as unicode'''
|
||||||
return parse_jid(str(iq_obj.getFrom()))
|
return parse_jid(str(iq_obj.getFrom()))
|
||||||
|
@ -723,6 +871,10 @@ def get_auth_sha(sid, initiator, target):
|
||||||
''' return sha of sid + initiator + target used for proxy auth'''
|
''' return sha of sid + initiator + target used for proxy auth'''
|
||||||
return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest()
|
return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest()
|
||||||
|
|
||||||
|
def remove_invalid_xml_chars(string):
|
||||||
|
if string:
|
||||||
|
string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
|
||||||
|
return string
|
||||||
|
|
||||||
distro_info = {
|
distro_info = {
|
||||||
'Arch Linux': '/etc/arch-release',
|
'Arch Linux': '/etc/arch-release',
|
||||||
|
@ -832,22 +984,6 @@ def get_os_info():
|
||||||
gajim.os_info = os_info
|
gajim.os_info = os_info
|
||||||
return os_info
|
return os_info
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
|
||||||
'''makes sure the filename we will write does contain only acceptable and
|
|
||||||
latin characters, and is not too long (in that case hash it)'''
|
|
||||||
# 48 is the limit
|
|
||||||
if len(filename) > 48:
|
|
||||||
hash = hashlib.md5(filename)
|
|
||||||
filename = base64.b64encode(hash.digest())
|
|
||||||
|
|
||||||
filename = punycode_encode(filename) # make it latin chars only
|
|
||||||
filename = filename.replace('/', '_')
|
|
||||||
if os.name == 'nt':
|
|
||||||
filename = filename.replace('?', '_').replace(':', '_')\
|
|
||||||
.replace('\\', '_').replace('"', "'").replace('|', '_')\
|
|
||||||
.replace('*', '_').replace('<', '_').replace('>', '_')
|
|
||||||
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def allow_showing_notification(account, type_ = 'notify_on_new_message',
|
def allow_showing_notification(account, type_ = 'notify_on_new_message',
|
||||||
advanced_notif_num = None, is_first_message = True):
|
advanced_notif_num = None, is_first_message = True):
|
||||||
|
@ -919,39 +1055,6 @@ def get_chat_control(account, contact):
|
||||||
# unknown contact or offline message
|
# unknown contact or offline message
|
||||||
return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
|
return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
|
||||||
|
|
||||||
def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
|
|
||||||
'''Cut the chars after 'max_chars' on each line
|
|
||||||
and show only the first 'max_lines'.
|
|
||||||
If any of the params is not present (None or 0) the action
|
|
||||||
on it is not performed'''
|
|
||||||
|
|
||||||
def _cut_if_long(string):
|
|
||||||
if len(string) > max_chars:
|
|
||||||
string = string[:max_chars - 3] + '...'
|
|
||||||
return string
|
|
||||||
|
|
||||||
if isinstance(text, str):
|
|
||||||
text = text.decode('utf-8')
|
|
||||||
|
|
||||||
if max_lines == 0:
|
|
||||||
lines = text.split('\n')
|
|
||||||
else:
|
|
||||||
lines = text.split('\n', max_lines)[:max_lines]
|
|
||||||
if max_chars > 0:
|
|
||||||
if lines:
|
|
||||||
lines = [_cut_if_long(e) for e in lines]
|
|
||||||
if lines:
|
|
||||||
reduced_text = '\n'.join(lines)
|
|
||||||
if reduced_text != text:
|
|
||||||
reduced_text += '...'
|
|
||||||
else:
|
|
||||||
reduced_text = ''
|
|
||||||
return reduced_text
|
|
||||||
|
|
||||||
def get_account_status(account):
|
|
||||||
status = reduce_chars_newlines(account['status_line'], 100, 1)
|
|
||||||
return status
|
|
||||||
|
|
||||||
def get_notification_icon_tooltip_dict():
|
def get_notification_icon_tooltip_dict():
|
||||||
'''returns a dict of the form {acct: {'show': show, 'message': message,
|
'''returns a dict of the form {acct: {'show': show, 'message': message,
|
||||||
'event_lines': [list of text lines to show in tooltip]}'''
|
'event_lines': [list of text lines to show in tooltip]}'''
|
||||||
|
@ -1086,37 +1189,6 @@ def get_accounts_info():
|
||||||
'show': status, 'message': message})
|
'show': status, 'message': message})
|
||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
def get_avatar_path(prefix):
|
|
||||||
'''Returns the filename of the avatar, distinguishes between user- and
|
|
||||||
contact-provided one. Returns None if no avatar was found at all.
|
|
||||||
prefix is the path to the requested avatar just before the ".png" or
|
|
||||||
".jpeg".'''
|
|
||||||
# First, scan for a local, user-set avatar
|
|
||||||
for type_ in ('jpeg', 'png'):
|
|
||||||
file_ = prefix + '_local.' + type_
|
|
||||||
if os.path.exists(file_):
|
|
||||||
return file_
|
|
||||||
# If none available, scan for a contact-provided avatar
|
|
||||||
for type_ in ('jpeg', 'png'):
|
|
||||||
file_ = prefix + '.' + type_
|
|
||||||
if os.path.exists(file_):
|
|
||||||
return file_
|
|
||||||
return None
|
|
||||||
|
|
||||||
def datetime_tuple(timestamp):
|
|
||||||
'''Converts timestamp using strptime and the format: %Y%m%dT%H:%M:%S
|
|
||||||
Because of various datetime formats are used the following exceptions
|
|
||||||
are handled:
|
|
||||||
- Optional milliseconds appened to the string are removed
|
|
||||||
- Optional Z (that means UTC) appened to the string are removed
|
|
||||||
- XEP-082 datetime strings have all '-' cahrs removed to meet
|
|
||||||
the above format.'''
|
|
||||||
timestamp = timestamp.split('.')[0]
|
|
||||||
timestamp = timestamp.replace('-', '')
|
|
||||||
timestamp = timestamp.replace('z', '')
|
|
||||||
timestamp = timestamp.replace('Z', '')
|
|
||||||
from time import strptime
|
|
||||||
return strptime(timestamp, '%Y%m%dT%H:%M:%S')
|
|
||||||
|
|
||||||
def get_iconset_path(iconset):
|
def get_iconset_path(iconset):
|
||||||
if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
|
if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
|
||||||
|
@ -1179,74 +1251,6 @@ def prepare_and_validate_gpg_keyID(account, jid, keyID):
|
||||||
keyID = 'UNKNOWN'
|
keyID = 'UNKNOWN'
|
||||||
return keyID
|
return keyID
|
||||||
|
|
||||||
def sort_identities_func(i1, i2):
|
|
||||||
cat1 = i1['category']
|
|
||||||
cat2 = i2['category']
|
|
||||||
if cat1 < cat2:
|
|
||||||
return -1
|
|
||||||
if cat1 > cat2:
|
|
||||||
return 1
|
|
||||||
type1 = i1.get('type', '')
|
|
||||||
type2 = i2.get('type', '')
|
|
||||||
if type1 < type2:
|
|
||||||
return -1
|
|
||||||
if type1 > type2:
|
|
||||||
return 1
|
|
||||||
lang1 = i1.get('xml:lang', '')
|
|
||||||
lang2 = i2.get('xml:lang', '')
|
|
||||||
if lang1 < lang2:
|
|
||||||
return -1
|
|
||||||
if lang1 > lang2:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def sort_dataforms_func(d1, d2):
|
|
||||||
f1 = d1.getField('FORM_TYPE')
|
|
||||||
f2 = d2.getField('FORM_TYPE')
|
|
||||||
if f1 and f2 and (f1.getValue() < f2.getValue()):
|
|
||||||
return -1
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
|
|
||||||
'''Compute caps hash according to XEP-0115, V1.5
|
|
||||||
|
|
||||||
dataforms are xmpp.DataForms objects as common.dataforms don't allow several
|
|
||||||
values without a field type list-multi'''
|
|
||||||
S = ''
|
|
||||||
identities.sort(cmp=sort_identities_func)
|
|
||||||
for i in identities:
|
|
||||||
c = i['category']
|
|
||||||
type_ = i.get('type', '')
|
|
||||||
lang = i.get('xml:lang', '')
|
|
||||||
name = i.get('name', '')
|
|
||||||
S += '%s/%s/%s/%s<' % (c, type_, lang, name)
|
|
||||||
features.sort()
|
|
||||||
for f in features:
|
|
||||||
S += '%s<' % f
|
|
||||||
dataforms.sort(cmp=sort_dataforms_func)
|
|
||||||
for dataform in dataforms:
|
|
||||||
# fields indexed by var
|
|
||||||
fields = {}
|
|
||||||
for f in dataform.getChildren():
|
|
||||||
fields[f.getVar()] = f
|
|
||||||
form_type = fields.get('FORM_TYPE')
|
|
||||||
if form_type:
|
|
||||||
S += form_type.getValue() + '<'
|
|
||||||
del fields['FORM_TYPE']
|
|
||||||
for var in sorted(fields.keys()):
|
|
||||||
S += '%s<' % var
|
|
||||||
values = sorted(fields[var].getValues())
|
|
||||||
for value in values:
|
|
||||||
S += '%s<' % value
|
|
||||||
|
|
||||||
if hash_method == 'sha-1':
|
|
||||||
hash_ = hashlib.sha1(S)
|
|
||||||
elif hash_method == 'md5':
|
|
||||||
hash_ = hashlib.md5(S)
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
return base64.b64encode(hash_.digest())
|
|
||||||
|
|
||||||
def update_optional_features(account = None):
|
def update_optional_features(account = None):
|
||||||
if account:
|
if account:
|
||||||
accounts = [account]
|
accounts = [account]
|
||||||
|
|
Loading…
Reference in New Issue