From e49c64a4522ec0e4120d7f821dd151b269ffce14 Mon Sep 17 00:00:00 2001
From: Yann Leboulanger <asterix@lagaule.org>
Date: Sun, 8 Feb 2009 13:47:28 +0000
Subject: [PATCH] reorder functions in common/helpers.py. Fixes #4764. Thanks
 Vardo!

---
 src/common/helpers.py | 724 +++++++++++++++++++++---------------------
 1 file changed, 364 insertions(+), 360 deletions(-)

diff --git a/src/common/helpers.py b/src/common/helpers.py
index 66244597a8..735855d53e 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -42,16 +42,10 @@ import hashlib
 
 from encodings.punycode import punycode_encode
 
-import gajim
 from i18n import Q_
 from i18n import ngettext
 import xmpp
 
-try:
-	from osx import nsapp
-except ImportError:
-	pass
-
 try:
 	import winsound # windows-only built-in module for playing wav
 	import win32api
@@ -187,65 +181,6 @@ def temp_failure_retry(func, *args, **kwargs):
 			else:
 				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):
 	'''returns a userfriendly string for dnd/xa/chat
 	and makes all strings translatable
@@ -426,6 +361,347 @@ def build_command(executable, parameter):
 	command = '%s "%s"' % (executable, parameter)
 	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):
 	#kind = 'url' or 'mail'
 	if os.name == 'nt':
@@ -497,68 +773,25 @@ def play_sound_file(path_to_soundfile):
 	if path_to_soundfile == 'beep':
 		exec_command('beep')
 		return
-	if path_to_soundfile is None or not os.path.exists(path_to_soundfile):
-		return
-	if sys.platform == 'darwin':
-		try:
-			nsapp.playFile(path_to_soundfile)
-		except NameError:
-			pass
-	elif os.name == 'nt':
-		try:
-			winsound.PlaySound(path_to_soundfile,
-				winsound.SND_FILENAME|winsound.SND_ASYNC)
-		except Exception:
-			pass
-	elif os.name == 'posix':
-		if gajim.config.get('soundplayer') == '':
-			return
-		player = gajim.config.get('soundplayer')
-		command = build_command(player, path_to_soundfile)
-		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
+	if path_to_soundfile is None or not os.path.exists(path_to_soundfile):
+		return
+	if sys.platform == 'darwin':
+		try:
+			nsapp.playFile(path_to_soundfile)
+		except NameError:
+			pass
+	elif os.name == 'nt':
+		try:
+			winsound.PlaySound(path_to_soundfile,
+				winsound.SND_FILENAME|winsound.SND_ASYNC)
+		except Exception:
+			pass
+	elif os.name == 'posix':
+		if gajim.config.get('soundplayer') == '':
+			return
+		player = gajim.config.get('soundplayer')
+		command = build_command(player, path_to_soundfile)
+		exec_command(command)
 
 def get_global_show():
 	maxi = 0
@@ -625,91 +858,6 @@ def get_icon_name_to_show(contact, account = None):
 		return contact.show
 	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):
 	'''return the full jid (with resource) from an iq as unicode'''
 	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 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 = {
 	'Arch Linux': '/etc/arch-release',
@@ -832,22 +984,6 @@ def get_os_info():
 	gajim.os_info = 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',
 advanced_notif_num = None, is_first_message = True):
@@ -919,39 +1055,6 @@ def get_chat_control(account, contact):
 		# unknown contact or offline message
 		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():
 	'''returns a dict of the form {acct: {'show': show, 'message': message,
 	'event_lines': [list of text lines to show in tooltip]}'''
@@ -1086,37 +1189,6 @@ def get_accounts_info():
 				'show': status, 'message': message})
 	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):
 	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'
 	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):
 	if account:
 		accounts = [account]
-- 
GitLab