A portion of doc-string refactoring
This commit is contained in:
		
							parent
							
								
									a9a442c01c
								
							
						
					
					
						commit
						cea7c66f75
					
				
					 11 changed files with 637 additions and 364 deletions
				
			
		| 
						 | 
				
			
			@ -213,7 +213,9 @@ if gajim.HAVE_GPG:
 | 
			
		|||
			return self.get_keys(True)
 | 
			
		||||
 | 
			
		||||
		def _stripHeaderFooter(self, data):
 | 
			
		||||
			"""Remove header and footer from data"""
 | 
			
		||||
			"""
 | 
			
		||||
			Remove header and footer from data
 | 
			
		||||
			"""
 | 
			
		||||
			if not data: return ''
 | 
			
		||||
			lines = data.split('\n')
 | 
			
		||||
			while lines[0] != '':
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +231,9 @@ if gajim.HAVE_GPG:
 | 
			
		|||
			return line
 | 
			
		||||
 | 
			
		||||
		def _addHeaderFooter(self, data, type_):
 | 
			
		||||
			"""Add header and footer from data"""
 | 
			
		||||
			"""
 | 
			
		||||
			Add header and footer from data
 | 
			
		||||
			"""
 | 
			
		||||
			out = "-----BEGIN PGP %s-----\n" % type_
 | 
			
		||||
			out = out + "Version: PGP\n"
 | 
			
		||||
			out = out + "\n"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,8 @@
 | 
			
		|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
##
 | 
			
		||||
 | 
			
		||||
"""Interface to GNU Privacy Guard (GnuPG)
 | 
			
		||||
"""
 | 
			
		||||
Interface to GNU Privacy Guard (GnuPG)
 | 
			
		||||
 | 
			
		||||
GnuPGInterface is a Python module to interface with GnuPG.
 | 
			
		||||
It concentrates on interacting with GnuPG via filehandles,
 | 
			
		||||
| 
						 | 
				
			
			@ -249,7 +250,8 @@ _fd_options = { 'passphrase': '--passphrase-fd',
 | 
			
		|||
                'command':    '--command-fd' }
 | 
			
		||||
 | 
			
		||||
class GnuPG:
 | 
			
		||||
    """Class instances represent GnuPG.
 | 
			
		||||
    """
 | 
			
		||||
    Class instances represent GnuPG
 | 
			
		||||
 | 
			
		||||
    Instance attributes of a GnuPG object are:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -275,8 +277,10 @@ class GnuPG:
 | 
			
		|||
        self.options = Options()
 | 
			
		||||
 | 
			
		||||
    def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None):
 | 
			
		||||
        """Calls GnuPG with the list of string commands gnupg_commands,
 | 
			
		||||
        complete with prefixing dashes.
 | 
			
		||||
        """
 | 
			
		||||
        Calls GnuPG with the list of string commands gnupg_commands, complete
 | 
			
		||||
        with prefixing dashes
 | 
			
		||||
 | 
			
		||||
        For example, gnupg_commands could be
 | 
			
		||||
        '["--sign", "--encrypt"]'
 | 
			
		||||
        Returns a GnuPGInterface.Process object.
 | 
			
		||||
| 
						 | 
				
			
			@ -336,7 +340,6 @@ class GnuPG:
 | 
			
		|||
        is a FileObject connected to GnuPG's standard input,
 | 
			
		||||
        and can be written to.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if args is None: args = []
 | 
			
		||||
        if create_fhs is None: create_fhs = []
 | 
			
		||||
        if attach_fhs is None: attach_fhs = {}
 | 
			
		||||
| 
						 | 
				
			
			@ -367,9 +370,10 @@ class GnuPG:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs):
 | 
			
		||||
        """This is like run(), but without the passphrase-helping
 | 
			
		||||
        (note that run() calls this)."""
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        This is like run(), but without the passphrase-helping (note that run()
 | 
			
		||||
        calls this)
 | 
			
		||||
        """
 | 
			
		||||
        process = Process()
 | 
			
		||||
 | 
			
		||||
        for fh_name in create_fhs + attach_fhs.keys():
 | 
			
		||||
| 
						 | 
				
			
			@ -404,7 +408,9 @@ class GnuPG:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    def _as_parent(self, process):
 | 
			
		||||
        """Stuff run after forking in parent"""
 | 
			
		||||
        """
 | 
			
		||||
        Stuff run after forking in parent
 | 
			
		||||
        """
 | 
			
		||||
        for k, p in process._pipes.items():
 | 
			
		||||
            if not p.direct:
 | 
			
		||||
                os.close(p.child)
 | 
			
		||||
| 
						 | 
				
			
			@ -417,7 +423,9 @@ class GnuPG:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    def _as_child(self, process, gnupg_commands, args):
 | 
			
		||||
        """Stuff run after forking in child"""
 | 
			
		||||
        """
 | 
			
		||||
        Stuff run after forking in child
 | 
			
		||||
        """
 | 
			
		||||
        # child
 | 
			
		||||
        for std in _stds:
 | 
			
		||||
            p = process._pipes[std]
 | 
			
		||||
| 
						 | 
				
			
			@ -444,7 +452,10 @@ class GnuPG:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Pipe:
 | 
			
		||||
    """simple struct holding stuff about pipes we use"""
 | 
			
		||||
    """
 | 
			
		||||
    Simple struct holding stuff about pipes we use
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent, child, direct):
 | 
			
		||||
        self.parent = parent
 | 
			
		||||
        self.child = child
 | 
			
		||||
| 
						 | 
				
			
			@ -452,7 +463,8 @@ class Pipe:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Options:
 | 
			
		||||
    """Objects of this class encompass options passed to GnuPG.
 | 
			
		||||
    """
 | 
			
		||||
    Objects of this class encompass options passed to GnuPG.
 | 
			
		||||
    This class is responsible for determining command-line arguments
 | 
			
		||||
    which are based on options.  It can be said that a GnuPG
 | 
			
		||||
    object has-a Options object in its options attribute.
 | 
			
		||||
| 
						 | 
				
			
			@ -522,7 +534,6 @@ class Options:
 | 
			
		|||
    >>> gnupg.options.get_args()
 | 
			
		||||
    ['--armor', '--recipient', 'Alice', '--recipient', 'Bob', '--no-secmem-warning']
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        # booleans
 | 
			
		||||
        self.armor = 0
 | 
			
		||||
| 
						 | 
				
			
			@ -558,12 +569,15 @@ class Options:
 | 
			
		|||
        self.extra_args = []
 | 
			
		||||
 | 
			
		||||
    def get_args( self ):
 | 
			
		||||
        """Generate a list of GnuPG arguments based upon attributes."""
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        Generate a list of GnuPG arguments based upon attributes
 | 
			
		||||
        """
 | 
			
		||||
        return self.get_meta_args() + self.get_standard_args() + self.extra_args
 | 
			
		||||
 | 
			
		||||
    def get_standard_args( self ):
 | 
			
		||||
        """Generate a list of standard, non-meta or extra arguments"""
 | 
			
		||||
        """
 | 
			
		||||
        Generate a list of standard, non-meta or extra arguments
 | 
			
		||||
        """
 | 
			
		||||
        args = []
 | 
			
		||||
        if self.homedir is not None:
 | 
			
		||||
            args.extend( [ '--homedir', self.homedir ] )
 | 
			
		||||
| 
						 | 
				
			
			@ -595,7 +609,9 @@ class Options:
 | 
			
		|||
        return args
 | 
			
		||||
 | 
			
		||||
    def get_meta_args( self ):
 | 
			
		||||
        """Get a list of generated meta-arguments"""
 | 
			
		||||
        """
 | 
			
		||||
        Get a list of generated meta-arguments
 | 
			
		||||
        """
 | 
			
		||||
        args = []
 | 
			
		||||
 | 
			
		||||
        if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1',
 | 
			
		||||
| 
						 | 
				
			
			@ -608,8 +624,9 @@ class Options:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Process:
 | 
			
		||||
    """Objects of this class encompass properties of a GnuPG
 | 
			
		||||
    process spawned by GnuPG.run().
 | 
			
		||||
    """
 | 
			
		||||
    Objects of this class encompass properties of a GnuPG process spawned by
 | 
			
		||||
    GnuPG.run()
 | 
			
		||||
 | 
			
		||||
    # gnupg is a GnuPG object
 | 
			
		||||
    process = gnupg.run( [ '--decrypt' ], stdout = 1 )
 | 
			
		||||
| 
						 | 
				
			
			@ -637,9 +654,10 @@ class Process:
 | 
			
		|||
        self._waited = None
 | 
			
		||||
 | 
			
		||||
    def wait(self):
 | 
			
		||||
        """Wait on the process to exit, allowing for child cleanup.
 | 
			
		||||
        Will raise an IOError if the process exits non-zero."""
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        Wait on the process to exit, allowing for child cleanup.  Will raise an
 | 
			
		||||
        IOError if the process exits non-zero
 | 
			
		||||
        """
 | 
			
		||||
        e = os.waitpid(self.pid, 0)[1]
 | 
			
		||||
        if e != 0:
 | 
			
		||||
            raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,14 +19,14 @@
 | 
			
		|||
##
 | 
			
		||||
 | 
			
		||||
class Account(object):
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __init__(self, name, contacts, gc_contacts):
 | 
			
		||||
		self.name = name
 | 
			
		||||
		self.contacts = contacts
 | 
			
		||||
		self.gc_contacts = gc_contacts
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def __repr__(self):
 | 
			
		||||
		return self.name
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __hash__(self):
 | 
			
		||||
		return self.name.__hash__()
 | 
			
		||||
		return hash(self.name)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,9 +20,11 @@
 | 
			
		|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
##
 | 
			
		||||
 | 
			
		||||
''' Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
 | 
			
		||||
"""
 | 
			
		||||
Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
 | 
			
		||||
and services. Very simple. Actually implements only atom:entry. Implement more features
 | 
			
		||||
if you need. '''
 | 
			
		||||
if you need
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# suggestion: rewrite functions that return dates to return standard python time tuples,
 | 
			
		||||
# exteneded to contain timezone
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +33,11 @@ import xmpp
 | 
			
		|||
import time
 | 
			
		||||
 | 
			
		||||
class PersonConstruct(xmpp.Node, object):
 | 
			
		||||
	''' Not used for now, as we don't need authors/contributors in pubsub.com feeds.
 | 
			
		||||
	They rarely exist there. '''
 | 
			
		||||
	"""
 | 
			
		||||
	Not used for now, as we don't need authors/contributors in pubsub.com feeds.
 | 
			
		||||
	They rarely exist there
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, node):
 | 
			
		||||
		''' Create person construct from node. '''
 | 
			
		||||
		xmpp.Node.__init__(self, node=node)
 | 
			
		||||
| 
						 | 
				
			
			@ -60,15 +65,17 @@ class PersonConstruct(xmpp.Node, object):
 | 
			
		|||
 | 
			
		||||
class Entry(xmpp.Node, object):
 | 
			
		||||
	def __init__(self, node=None):
 | 
			
		||||
		''' Create new atom entry object. '''
 | 
			
		||||
		xmpp.Node.__init__(self, 'entry', node=node)
 | 
			
		||||
 | 
			
		||||
	def __repr__(self):
 | 
			
		||||
		return '<Atom:Entry object of id="%r">' % self.getAttr('id')
 | 
			
		||||
 | 
			
		||||
class OldEntry(xmpp.Node, object):
 | 
			
		||||
	''' Parser for feeds from pubsub.com. They use old Atom 0.3 format with
 | 
			
		||||
	their extensions. '''
 | 
			
		||||
	"""
 | 
			
		||||
	Parser for feeds from pubsub.com. They use old Atom 0.3 format with their
 | 
			
		||||
	extensions
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, node=None):
 | 
			
		||||
		''' Create new Atom 0.3 entry object. '''
 | 
			
		||||
		xmpp.Node.__init__(self, 'entry', node=node)
 | 
			
		||||
| 
						 | 
				
			
			@ -77,8 +84,10 @@ class OldEntry(xmpp.Node, object):
 | 
			
		|||
		return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id')
 | 
			
		||||
 | 
			
		||||
	def get_feed_title(self):
 | 
			
		||||
		''' Returns title of feed, where the entry was created. The result is the feed name
 | 
			
		||||
		concatenated with source-feed title. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Return title of feed, where the entry was created. The result is the feed
 | 
			
		||||
		name concatenated with source-feed title
 | 
			
		||||
		"""
 | 
			
		||||
		if self.parent is not None:
 | 
			
		||||
			main_feed = self.parent.getTagData('title')
 | 
			
		||||
		else:
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +113,9 @@ class OldEntry(xmpp.Node, object):
 | 
			
		|||
		which delivered this entry. ''')
 | 
			
		||||
 | 
			
		||||
	def get_feed_link(self):
 | 
			
		||||
		''' Get source link '''
 | 
			
		||||
		"""
 | 
			
		||||
		Get source link
 | 
			
		||||
		"""
 | 
			
		||||
		try:
 | 
			
		||||
			return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData()
 | 
			
		||||
		except Exception:
 | 
			
		||||
| 
						 | 
				
			
			@ -114,15 +125,19 @@ class OldEntry(xmpp.Node, object):
 | 
			
		|||
		''' Link to main webpage of the feed. ''')
 | 
			
		||||
 | 
			
		||||
	def get_title(self):
 | 
			
		||||
		''' Get an entry's title. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Get an entry's title
 | 
			
		||||
		"""
 | 
			
		||||
		return self.getTagData('title')
 | 
			
		||||
 | 
			
		||||
	title = property(get_title, None, None,
 | 
			
		||||
		''' Entry's title. ''')
 | 
			
		||||
 | 
			
		||||
	def get_uri(self):
 | 
			
		||||
		''' Get the uri the entry points to (entry's first link element with rel='alternate'
 | 
			
		||||
		or without rel attribute). '''
 | 
			
		||||
		"""
 | 
			
		||||
		Get the uri the entry points to (entry's first link element with
 | 
			
		||||
		rel='alternate' or without rel attribute)
 | 
			
		||||
		"""
 | 
			
		||||
		for element in self.getTags('link'):
 | 
			
		||||
			if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue
 | 
			
		||||
			try:
 | 
			
		||||
| 
						 | 
				
			
			@ -135,12 +150,16 @@ class OldEntry(xmpp.Node, object):
 | 
			
		|||
		''' URI that is pointed by the entry. ''')
 | 
			
		||||
 | 
			
		||||
	def get_updated(self):
 | 
			
		||||
		''' Get the time the entry was updated last time. This should be standarized,
 | 
			
		||||
		but pubsub.com sends it in human-readable format. We won't try to parse it.
 | 
			
		||||
		(Atom 0.3 uses the word	«modified» for that).
 | 
			
		||||
		"""
 | 
			
		||||
		Get the time the entry was updated last time
 | 
			
		||||
 | 
			
		||||
		This should be standarized, but pubsub.com sends it in human-readable
 | 
			
		||||
		format. We won't try to parse it. (Atom 0.3 uses the word «modified» for
 | 
			
		||||
		that).
 | 
			
		||||
 | 
			
		||||
		If there's no time given in the entry, we try with <published>
 | 
			
		||||
		and <issued> elements. '''
 | 
			
		||||
		and <issued> elements.
 | 
			
		||||
		"""
 | 
			
		||||
		for name in ('updated', 'modified', 'published', 'issued'):
 | 
			
		||||
			date = self.getTagData(name)
 | 
			
		||||
			if date is not None: break
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,13 +23,13 @@
 | 
			
		|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
##
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
Module containing all XEP-115 (Entity Capabilities) related classes 
 | 
			
		||||
"""
 | 
			
		||||
Module containing all XEP-115 (Entity Capabilities) related classes
 | 
			
		||||
 | 
			
		||||
Basic Idea:
 | 
			
		||||
CapsCache caches features to hash relationships. The cache is queried
 | 
			
		||||
through ClientCaps objects which are hold by contact instances. 
 | 
			
		||||
''' 
 | 
			
		||||
through ClientCaps objects which are hold by contact instances.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import gajim
 | 
			
		||||
import helpers
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +54,9 @@ CACHED = 2 # got the answer
 | 
			
		|||
 | 
			
		||||
capscache = None
 | 
			
		||||
def initialize(logger):
 | 
			
		||||
	''' Initializes this module '''
 | 
			
		||||
	"""
 | 
			
		||||
	Initialize this module
 | 
			
		||||
	"""
 | 
			
		||||
	global capscache
 | 
			
		||||
	capscache = CapsCache(logger)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,11 +75,12 @@ def client_supports(client_caps, requested_feature):
 | 
			
		|||
		return False
 | 
			
		||||
 | 
			
		||||
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
 | 
			
		||||
	'''Compute caps hash according to XEP-0115, V1.5
 | 
			
		||||
	"""
 | 
			
		||||
	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'''
 | 
			
		||||
	
 | 
			
		||||
	values without a field type list-multi
 | 
			
		||||
	"""
 | 
			
		||||
	def sort_identities_func(i1, i2):
 | 
			
		||||
		cat1 = i1['category']
 | 
			
		||||
		cat2 = i2['category']
 | 
			
		||||
| 
						 | 
				
			
			@ -98,14 +101,14 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-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
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	S = ''
 | 
			
		||||
	identities.sort(cmp=sort_identities_func)
 | 
			
		||||
	for i in identities:
 | 
			
		||||
| 
						 | 
				
			
			@ -140,108 +143,119 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
 | 
			
		|||
	else:
 | 
			
		||||
		return ''
 | 
			
		||||
	return base64.b64encode(hash_.digest())
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
### Internal classes of this module
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
class AbstractClientCaps(object):
 | 
			
		||||
	'''
 | 
			
		||||
	Base class representing a client and its capabilities as advertised by
 | 
			
		||||
	a caps tag in a presence. 
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
	Base class representing a client and its capabilities as advertised by a
 | 
			
		||||
	caps tag in a presence
 | 
			
		||||
	"""
 | 
			
		||||
	def __init__(self, caps_hash, node):
 | 
			
		||||
		self._hash = caps_hash
 | 
			
		||||
		self._node = node
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def get_discover_strategy(self):
 | 
			
		||||
		return self._discover
 | 
			
		||||
							
 | 
			
		||||
 | 
			
		||||
	def _discover(self, connection, jid):
 | 
			
		||||
		''' To be implemented by subclassess '''
 | 
			
		||||
		raise NotImplementedError()
 | 
			
		||||
	
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by subclassess
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def get_cache_lookup_strategy(self):
 | 
			
		||||
		return self._lookup_in_cache
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _lookup_in_cache(self, caps_cache):
 | 
			
		||||
		''' To be implemented by subclassess '''
 | 
			
		||||
		raise NotImplementedError()
 | 
			
		||||
	
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by subclassess
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def get_hash_validation_strategy(self):
 | 
			
		||||
		return self._is_hash_valid
 | 
			
		||||
					
 | 
			
		||||
 | 
			
		||||
	def _is_hash_valid(self, identities, features, dataforms):
 | 
			
		||||
		''' To be implemented by subclassess '''
 | 
			
		||||
		raise NotImplementedError()		
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by subclassess
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientCaps(AbstractClientCaps):
 | 
			
		||||
	''' The current XEP-115 implementation '''
 | 
			
		||||
	
 | 
			
		||||
	"""
 | 
			
		||||
	The current XEP-115 implementation
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, caps_hash, node, hash_method):
 | 
			
		||||
		AbstractClientCaps.__init__(self, caps_hash, node)
 | 
			
		||||
		assert hash_method != 'old'
 | 
			
		||||
		self._hash_method = hash_method
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _lookup_in_cache(self, caps_cache):
 | 
			
		||||
		return caps_cache[(self._hash_method, self._hash)]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _discover(self, connection, jid):
 | 
			
		||||
		connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
	def _is_hash_valid(self, identities, features, dataforms):
 | 
			
		||||
		computed_hash = compute_caps_hash(identities, features,
 | 
			
		||||
				dataforms=dataforms, hash_method=self._hash_method)
 | 
			
		||||
		return computed_hash == self._hash	
 | 
			
		||||
		return computed_hash == self._hash
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
class OldClientCaps(AbstractClientCaps):
 | 
			
		||||
	''' Old XEP-115 implemtation. Kept around for background competability.  '''
 | 
			
		||||
	
 | 
			
		||||
	"""
 | 
			
		||||
	Old XEP-115 implemtation. Kept around for background competability
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, caps_hash, node):
 | 
			
		||||
		AbstractClientCaps.__init__(self, caps_hash, node)
 | 
			
		||||
 | 
			
		||||
	def _lookup_in_cache(self, caps_cache):
 | 
			
		||||
		return caps_cache[('old', self._node + '#' + self._hash)]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _discover(self, connection, jid):
 | 
			
		||||
		connection.discoverInfo(jid)
 | 
			
		||||
		
 | 
			
		||||
	def _is_hash_valid(self, identities, features, dataforms):
 | 
			
		||||
		return True	
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
	def _is_hash_valid(self, identities, features, dataforms):
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullClientCaps(AbstractClientCaps):
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
	This is a NULL-Object to streamline caps handling if a client has not
 | 
			
		||||
	advertised any caps or has advertised them in an improper way. 
 | 
			
		||||
	
 | 
			
		||||
	advertised any caps or has advertised them in an improper way
 | 
			
		||||
 | 
			
		||||
	Assumes (almost) everything is supported.
 | 
			
		||||
	''' 
 | 
			
		||||
	
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		AbstractClientCaps.__init__(self, None, None)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _lookup_in_cache(self, caps_cache):
 | 
			
		||||
		# lookup something which does not exist to get a new CacheItem created
 | 
			
		||||
		cache_item = caps_cache[('dummy', '')]
 | 
			
		||||
		assert cache_item.status != CACHED
 | 
			
		||||
		return cache_item
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _discover(self, connection, jid):
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
	def _is_hash_valid(self, identities, features, dataforms):
 | 
			
		||||
		return False	
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CapsCache(object):
 | 
			
		||||
	''' 
 | 
			
		||||
	This object keeps the mapping between caps data and real disco
 | 
			
		||||
	features they represent, and provides simple way to query that info.
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
	This object keeps the mapping between caps data and real disco features they
 | 
			
		||||
	represent, and provides simple way to query that info
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, logger=None):
 | 
			
		||||
		# our containers:
 | 
			
		||||
		# __cache is a dictionary mapping: pair of hash method and hash maps
 | 
			
		||||
| 
						 | 
				
			
			@ -255,7 +269,7 @@ class CapsCache(object):
 | 
			
		|||
			#   another object, and we will have plenty of identical long
 | 
			
		||||
			#   strings. therefore we can cache them
 | 
			
		||||
			__names = {}
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
			def __init__(self, hash_method, hash_, logger):
 | 
			
		||||
				# cached into db
 | 
			
		||||
				self.hash_method = hash_method
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +288,7 @@ class CapsCache(object):
 | 
			
		|||
				self._features = []
 | 
			
		||||
				for feature in value:
 | 
			
		||||
					self._features.append(self.__names.setdefault(feature, feature))
 | 
			
		||||
					
 | 
			
		||||
 | 
			
		||||
			features = property(_get_features, _set_features)
 | 
			
		||||
 | 
			
		||||
			def _get_identities(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +305,7 @@ class CapsCache(object):
 | 
			
		|||
						d['name'] = i[3]
 | 
			
		||||
					list_.append(d)
 | 
			
		||||
				return list_
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
			def _set_identities(self, value):
 | 
			
		||||
				self._identities = []
 | 
			
		||||
				for identity in value:
 | 
			
		||||
| 
						 | 
				
			
			@ -299,7 +313,7 @@ class CapsCache(object):
 | 
			
		|||
					t = (identity['category'], identity.get('type'),
 | 
			
		||||
						identity.get('xml:lang'), identity.get('name'))
 | 
			
		||||
					self._identities.append(self.__names.setdefault(t, t))
 | 
			
		||||
					
 | 
			
		||||
 | 
			
		||||
			identities = property(_get_identities, _set_identities)
 | 
			
		||||
 | 
			
		||||
			def set_and_store(self, identities, features):
 | 
			
		||||
| 
						 | 
				
			
			@ -308,7 +322,7 @@ class CapsCache(object):
 | 
			
		|||
				self._logger.add_caps_entry(self.hash_method, self.hash,
 | 
			
		||||
					identities, features)
 | 
			
		||||
				self.status = CACHED
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
			def update_last_seen(self):
 | 
			
		||||
				if not self._recently_seen:
 | 
			
		||||
					self._recently_seen = True
 | 
			
		||||
| 
						 | 
				
			
			@ -325,9 +339,11 @@ class CapsCache(object):
 | 
			
		|||
			x.identities = identities
 | 
			
		||||
			x.features = features
 | 
			
		||||
			x.status = CACHED
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def _remove_outdated_caps(self):
 | 
			
		||||
		'''Removes outdated values from the db'''
 | 
			
		||||
		"""
 | 
			
		||||
		Remove outdated values from the db
 | 
			
		||||
		"""
 | 
			
		||||
		self.logger.clean_caps_table()
 | 
			
		||||
 | 
			
		||||
	def __getitem__(self, caps):
 | 
			
		||||
| 
						 | 
				
			
			@ -341,20 +357,20 @@ class CapsCache(object):
 | 
			
		|||
		return x
 | 
			
		||||
 | 
			
		||||
	def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
 | 
			
		||||
		'''
 | 
			
		||||
		Start a disco query to determine caps (node, ver, exts).
 | 
			
		||||
		Won't query if the data is already in cache.
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		Start a disco query to determine caps (node, ver, exts). Won't query if
 | 
			
		||||
		the data is already in cache
 | 
			
		||||
		"""
 | 
			
		||||
		lookup_cache_item = client_caps.get_cache_lookup_strategy()
 | 
			
		||||
		q = lookup_cache_item(self)	
 | 
			
		||||
		
 | 
			
		||||
		q = lookup_cache_item(self)
 | 
			
		||||
 | 
			
		||||
		if q.status == NEW:
 | 
			
		||||
			# do query for bare node+hash pair
 | 
			
		||||
			# this will create proper object
 | 
			
		||||
			q.status = QUERIED
 | 
			
		||||
			discover = client_caps.get_discover_strategy()
 | 
			
		||||
			discover(connection, jid)
 | 
			
		||||
		else: 
 | 
			
		||||
		else:
 | 
			
		||||
			q.update_last_seen()
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
| 
						 | 
				
			
			@ -362,14 +378,15 @@ class CapsCache(object):
 | 
			
		|||
################################################################################
 | 
			
		||||
 | 
			
		||||
class ConnectionCaps(object):
 | 
			
		||||
	'''
 | 
			
		||||
	This class highly depends on that it is a part of Connection class.
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
	This class highly depends on that it is a part of Connection class
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def _capsPresenceCB(self, con, presence):
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		Handle incoming presence stanzas... This is a callback for xmpp
 | 
			
		||||
		registered in connection_handlers.py
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		# we will put these into proper Contact object and ask
 | 
			
		||||
		# for disco... so that disco will learn how to interpret
 | 
			
		||||
		# these caps
 | 
			
		||||
| 
						 | 
				
			
			@ -411,7 +428,7 @@ class ConnectionCaps(object):
 | 
			
		|||
		if pm_ctrl:
 | 
			
		||||
			pm_ctrl.update_contact()
 | 
			
		||||
 | 
			
		||||
	def _capsDiscoCB(self, jid, node, identities, features, dataforms):		
 | 
			
		||||
	def _capsDiscoCB(self, jid, node, identities, features, dataforms):
 | 
			
		||||
		contact = gajim.contacts.get_contact_from_full_jid(self.name, jid)
 | 
			
		||||
		if not contact:
 | 
			
		||||
			room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
 | 
			
		||||
| 
						 | 
				
			
			@ -420,14 +437,14 @@ class ConnectionCaps(object):
 | 
			
		|||
				return
 | 
			
		||||
 | 
			
		||||
		lookup = contact.client_caps.get_cache_lookup_strategy()
 | 
			
		||||
		cache_item = lookup(capscache)	
 | 
			
		||||
					
 | 
			
		||||
		cache_item = lookup(capscache)
 | 
			
		||||
 | 
			
		||||
		if cache_item.status == CACHED:
 | 
			
		||||
			return
 | 
			
		||||
		else:
 | 
			
		||||
			validate = contact.client_caps.get_hash_validation_strategy()
 | 
			
		||||
			hash_is_valid = validate(identities, features, dataforms)
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
			if hash_is_valid:
 | 
			
		||||
				cache_item.set_and_store(identities, features)
 | 
			
		||||
			else:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,10 +34,13 @@ class AdHocCommand:
 | 
			
		|||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def isVisibleFor(samejid):
 | 
			
		||||
		''' This returns True if that command should be visible and invokable
 | 
			
		||||
		for others.
 | 
			
		||||
		"""
 | 
			
		||||
		This returns True if that command should be visible and invokable for
 | 
			
		||||
		others
 | 
			
		||||
 | 
			
		||||
		samejid - True when command is invoked by an entity with the same bare
 | 
			
		||||
		jid.'''
 | 
			
		||||
		jid.
 | 
			
		||||
		"""
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def __init__(self, conn, jid, sessionid):
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +83,9 @@ class ChangeStatusCommand(AdHocCommand):
 | 
			
		|||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def isVisibleFor(samejid):
 | 
			
		||||
		''' Change status is visible only if the entity has the same bare jid. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Change status is visible only if the entity has the same bare jid
 | 
			
		||||
		"""
 | 
			
		||||
		return samejid
 | 
			
		||||
 | 
			
		||||
	def execute(self, request):
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +182,9 @@ class LeaveGroupchatsCommand(AdHocCommand):
 | 
			
		|||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def isVisibleFor(samejid):
 | 
			
		||||
		''' Change status is visible only if the entity has the same bare jid. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Change status is visible only if the entity has the same bare jid
 | 
			
		||||
		"""
 | 
			
		||||
		return samejid
 | 
			
		||||
 | 
			
		||||
	def execute(self, request):
 | 
			
		||||
| 
						 | 
				
			
			@ -259,7 +266,9 @@ class ForwardMessagesCommand(AdHocCommand):
 | 
			
		|||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def isVisibleFor(samejid):
 | 
			
		||||
		''' Change status is visible only if the entity has the same bare jid. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Change status is visible only if the entity has the same bare jid
 | 
			
		||||
		"""
 | 
			
		||||
		return samejid
 | 
			
		||||
 | 
			
		||||
	def execute(self, request):
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +291,10 @@ class ForwardMessagesCommand(AdHocCommand):
 | 
			
		|||
		return False	# finish the session
 | 
			
		||||
 | 
			
		||||
class ConnectionCommands:
 | 
			
		||||
	''' This class depends on that it is a part of Connection() class. '''
 | 
			
		||||
	"""
 | 
			
		||||
	This class depends on that it is a part of Connection() class
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		# a list of all commands exposed: node -> command class
 | 
			
		||||
		self.__commands = {}
 | 
			
		||||
| 
						 | 
				
			
			@ -297,7 +309,9 @@ class ConnectionCommands:
 | 
			
		|||
		return gajim.get_jid_from_account(self.name)
 | 
			
		||||
 | 
			
		||||
	def isSameJID(self, jid):
 | 
			
		||||
		''' Tests if the bare jid given is the same as our bare jid. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Test if the bare jid given is the same as our bare jid
 | 
			
		||||
		"""
 | 
			
		||||
		return xmpp.JID(jid).getStripped() == self.getOurBareJID()
 | 
			
		||||
 | 
			
		||||
	def commandListQuery(self, con, iq_obj):
 | 
			
		||||
| 
						 | 
				
			
			@ -318,8 +332,10 @@ class ConnectionCommands:
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def commandInfoQuery(self, con, iq_obj):
 | 
			
		||||
		''' Send disco#info result for query for command (JEP-0050, example 6.).
 | 
			
		||||
		Return True if the result was sent, False if not. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send disco#info result for query for command (JEP-0050, example 6.).
 | 
			
		||||
		Return True if the result was sent, False if not
 | 
			
		||||
		"""
 | 
			
		||||
		jid = helpers.get_full_jid_from_iq(iq_obj)
 | 
			
		||||
		node = iq_obj.getTagAttr('query', 'node')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -342,8 +358,10 @@ class ConnectionCommands:
 | 
			
		|||
		return False
 | 
			
		||||
 | 
			
		||||
	def commandItemsQuery(self, con, iq_obj):
 | 
			
		||||
		''' Send disco#items result for query for command.
 | 
			
		||||
		Return True if the result was sent, False if not. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send disco#items result for query for command. Return True if the result
 | 
			
		||||
		was sent, False if not.
 | 
			
		||||
		"""
 | 
			
		||||
		jid = helpers.get_full_jid_from_iq(iq_obj)
 | 
			
		||||
		node = iq_obj.getTagAttr('query', 'node')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -512,7 +512,9 @@ class Config:
 | 
			
		|||
					cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
 | 
			
		||||
 | 
			
		||||
	def get_children(self, node=None):
 | 
			
		||||
		''' Tree-like interface '''
 | 
			
		||||
		"""
 | 
			
		||||
		Tree-like interface
 | 
			
		||||
		"""
 | 
			
		||||
		if node is None:
 | 
			
		||||
			for child, option in self.__options.iteritems():
 | 
			
		||||
				yield (child, ), option
 | 
			
		||||
| 
						 | 
				
			
			@ -698,8 +700,9 @@ class Config:
 | 
			
		|||
		return False
 | 
			
		||||
 | 
			
		||||
	def should_log(self, account, jid):
 | 
			
		||||
		'''should conversations between a local account and a remote jid be
 | 
			
		||||
		logged?'''
 | 
			
		||||
		"""
 | 
			
		||||
		Should conversations between a local account and a remote jid be logged?
 | 
			
		||||
		"""
 | 
			
		||||
		no_log_for = self.get_per('accounts', account, 'no_log_for')
 | 
			
		||||
 | 
			
		||||
		if not no_log_for:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,7 +46,9 @@ import tempfile
 | 
			
		|||
# not displayed to the user, Unicode is not really necessary here.
 | 
			
		||||
 | 
			
		||||
def fse(s):
 | 
			
		||||
	'''Convert from filesystem encoding if not already Unicode'''
 | 
			
		||||
	"""
 | 
			
		||||
	Convert from filesystem encoding if not already Unicode
 | 
			
		||||
	"""
 | 
			
		||||
	return unicode(s, sys.getfilesystemencoding())
 | 
			
		||||
 | 
			
		||||
def windowsify(s):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,10 +100,11 @@ ssl_error = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
class CommonConnection:
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
	Common connection class, can be derivated for normal connection or zeroconf
 | 
			
		||||
	connection
 | 
			
		||||
	'''
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, name):
 | 
			
		||||
		self.name = name
 | 
			
		||||
		# self.connected:
 | 
			
		||||
| 
						 | 
				
			
			@ -162,11 +163,15 @@ class CommonConnection:
 | 
			
		|||
		return resource
 | 
			
		||||
 | 
			
		||||
	def dispatch(self, event, data):
 | 
			
		||||
		'''always passes account name as first param'''
 | 
			
		||||
		"""
 | 
			
		||||
		Always passes account name as first param
 | 
			
		||||
		"""
 | 
			
		||||
		gajim.interface.dispatch(event, self.name, data)
 | 
			
		||||
 | 
			
		||||
	def _reconnect(self):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def quit(self, kill_core):
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +179,9 @@ class CommonConnection:
 | 
			
		|||
			self.disconnect(on_purpose=True)
 | 
			
		||||
 | 
			
		||||
	def test_gpg_passphrase(self, password):
 | 
			
		||||
		'''Returns 'ok', 'bad_pass' or 'expired' '''
 | 
			
		||||
		"""
 | 
			
		||||
		Returns 'ok', 'bad_pass' or 'expired'
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.gpg:
 | 
			
		||||
			return False
 | 
			
		||||
		self.gpg.passphrase = password
 | 
			
		||||
| 
						 | 
				
			
			@ -188,10 +195,12 @@ class CommonConnection:
 | 
			
		|||
		return 'ok'
 | 
			
		||||
 | 
			
		||||
	def get_signed_msg(self, msg, callback = None):
 | 
			
		||||
		'''returns the signed message if possible
 | 
			
		||||
		or an empty string if gpg is not used
 | 
			
		||||
		or None if waiting for passphrase.
 | 
			
		||||
		callback is the function to call when user give the passphrase'''
 | 
			
		||||
		"""
 | 
			
		||||
		Returns the signed message if possible or an empty string if gpg is not
 | 
			
		||||
		used or None if waiting for passphrase
 | 
			
		||||
 | 
			
		||||
		callback is the function to call when user give the passphrase
 | 
			
		||||
		"""
 | 
			
		||||
		signed = ''
 | 
			
		||||
		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
 | 
			
		||||
		if keyID and self.USE_GPG:
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +217,9 @@ class CommonConnection:
 | 
			
		|||
		return signed
 | 
			
		||||
 | 
			
		||||
	def _on_disconnected(self):
 | 
			
		||||
		''' called when a disconnect request has completed successfully'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when a disconnect request has completed successfully
 | 
			
		||||
		"""
 | 
			
		||||
		self.disconnect(on_purpose=True)
 | 
			
		||||
		self.dispatch('STATUS', 'offline')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -216,9 +227,10 @@ class CommonConnection:
 | 
			
		|||
		return gajim.SHOW_LIST[self.connected]
 | 
			
		||||
 | 
			
		||||
	def check_jid(self, jid):
 | 
			
		||||
		'''this function must be implemented by derivated classes.
 | 
			
		||||
		It has to return the valid jid, or raise a helpers.InvalidFormat exception
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		This function must be implemented by derivated classes. It has to return
 | 
			
		||||
		the valid jid, or raise a helpers.InvalidFormat exception
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
 | 
			
		||||
| 
						 | 
				
			
			@ -424,32 +436,46 @@ class CommonConnection:
 | 
			
		|||
							common.logger.LOG_DB_PATH
 | 
			
		||||
 | 
			
		||||
	def ack_subscribed(self, jid):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def ack_unsubscribed(self, jid):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def request_subscription(self, jid, msg='', name='', groups=[],
 | 
			
		||||
	auto_auth=False):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
			auto_auth=False):
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def send_authorization(self, jid):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def refuse_authorization(self, jid):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def unsubscribe(self, jid, remove_auth = True):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def unsubscribe_agent(self, agent):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def update_contact(self, jid, name, groups):
 | 
			
		||||
| 
						 | 
				
			
			@ -457,47 +483,67 @@ class CommonConnection:
 | 
			
		|||
			self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
 | 
			
		||||
 | 
			
		||||
	def update_contacts(self, contacts):
 | 
			
		||||
		'''update multiple roster items'''
 | 
			
		||||
		"""
 | 
			
		||||
		Update multiple roster items
 | 
			
		||||
		"""
 | 
			
		||||
		if self.connection:
 | 
			
		||||
			self.connection.getRoster().setItemMulti(contacts)
 | 
			
		||||
 | 
			
		||||
	def new_account(self, name, config, sync=False):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def _on_new_account(self, con=None, con_type=None):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def account_changed(self, new_name):
 | 
			
		||||
		self.name = new_name
 | 
			
		||||
 | 
			
		||||
	def request_last_status_time(self, jid, resource):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def request_os_info(self, jid, resource):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def get_settings(self):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def get_bookmarks(self):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def store_bookmarks(self):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def get_metacontacts(self):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def send_agent_status(self, agent, ptype):
 | 
			
		||||
		'''To be implemented by derivated classes'''
 | 
			
		||||
		"""
 | 
			
		||||
		To be implemented by derivated classes
 | 
			
		||||
		"""
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def gpg_passphrase(self, passphrase):
 | 
			
		||||
| 
						 | 
				
			
			@ -581,7 +627,6 @@ class CommonConnection:
 | 
			
		|||
			self._update_status(show, msg)
 | 
			
		||||
 | 
			
		||||
class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		||||
	'''Connection class'''
 | 
			
		||||
	def __init__(self, name):
 | 
			
		||||
		CommonConnection.__init__(self, name)
 | 
			
		||||
		ConnectionHandlers.__init__(self)
 | 
			
		||||
| 
						 | 
				
			
			@ -672,7 +717,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			self.connection = None
 | 
			
		||||
 | 
			
		||||
	def _disconnectedReconnCB(self):
 | 
			
		||||
		'''Called when we are disconnected'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we are disconnected
 | 
			
		||||
		"""
 | 
			
		||||
		log.info('disconnectedReconnCB called')
 | 
			
		||||
		if gajim.account_is_connected(self.name):
 | 
			
		||||
			# we cannot change our status to offline or connecting
 | 
			
		||||
| 
						 | 
				
			
			@ -830,11 +877,11 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
				self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
 | 
			
		||||
 | 
			
		||||
	def _select_next_host(self, hosts):
 | 
			
		||||
		'''Selects the next host according to RFC2782 p.3 based on it's
 | 
			
		||||
		priority. Chooses between hosts with the same priority randomly,
 | 
			
		||||
		where the probability of being selected is proportional to the weight
 | 
			
		||||
		of the host.'''
 | 
			
		||||
 | 
			
		||||
		"""
 | 
			
		||||
		Selects the next host according to RFC2782 p.3 based on it's priority.
 | 
			
		||||
		Chooses between hosts with the same priority randomly, where the
 | 
			
		||||
		probability of being selected is proportional to the weight of the host
 | 
			
		||||
		"""
 | 
			
		||||
		hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
 | 
			
		||||
 | 
			
		||||
		try:
 | 
			
		||||
| 
						 | 
				
			
			@ -856,10 +903,13 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
					return host
 | 
			
		||||
 | 
			
		||||
	def connect(self, data = None):
 | 
			
		||||
		''' Start a connection to the Jabber server.
 | 
			
		||||
		Returns connection, and connection type ('tls', 'ssl', 'plain', '')
 | 
			
		||||
		data MUST contain hostname, usessl, proxy, use_custom_host,
 | 
			
		||||
		custom_host (if use_custom_host), custom_port (if use_custom_host)'''
 | 
			
		||||
		"""
 | 
			
		||||
		Start a connection to the Jabber server
 | 
			
		||||
 | 
			
		||||
		Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
 | 
			
		||||
		MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if
 | 
			
		||||
		use_custom_host), custom_port (if use_custom_host)
 | 
			
		||||
		"""
 | 
			
		||||
		if self.connection:
 | 
			
		||||
			return self.connection, ''
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1256,8 +1306,10 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			self.connection.send(' ')
 | 
			
		||||
 | 
			
		||||
	def sendPing(self, pingTo=None):
 | 
			
		||||
		'''Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent
 | 
			
		||||
		to server to detect connection failure at application level.'''
 | 
			
		||||
		"""
 | 
			
		||||
		Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
 | 
			
		||||
		server to detect connection failure at application level
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		id_ = self.connection.getAnID()
 | 
			
		||||
| 
						 | 
				
			
			@ -1325,7 +1377,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
 | 
			
		||||
 | 
			
		||||
	def build_privacy_rule(self, name, action, order=1):
 | 
			
		||||
		'''Build a Privacy rule stanza for invisibility'''
 | 
			
		||||
		"""
 | 
			
		||||
		Build a Privacy rule stanza for invisibility
 | 
			
		||||
		"""
 | 
			
		||||
		iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
 | 
			
		||||
		l = iq.getTag('query').setTag('list', {'name': name})
 | 
			
		||||
		i = l.setTag('item', {'action': action, 'order': str(order)})
 | 
			
		||||
| 
						 | 
				
			
			@ -1358,7 +1412,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def activate_privacy_rule(self, name):
 | 
			
		||||
		'''activate a privacy rule'''
 | 
			
		||||
		"""
 | 
			
		||||
		Activate a privacy rule
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
 | 
			
		||||
| 
						 | 
				
			
			@ -1534,7 +1590,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			original_message=original_message, delayed=delayed, callback=cb)
 | 
			
		||||
 | 
			
		||||
	def send_contacts(self, contacts, jid):
 | 
			
		||||
		'''Send contacts with RosterX (Xep-0144)'''
 | 
			
		||||
		"""
 | 
			
		||||
		Send contacts with RosterX (Xep-0144)
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		if len(contacts) == 1:
 | 
			
		||||
| 
						 | 
				
			
			@ -1553,7 +1611,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(msg_iq)
 | 
			
		||||
 | 
			
		||||
	def send_stanza(self, stanza):
 | 
			
		||||
		''' send a stanza untouched '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send a stanza untouched
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		self.connection.send(stanza)
 | 
			
		||||
| 
						 | 
				
			
			@ -1573,7 +1633,7 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(p)
 | 
			
		||||
 | 
			
		||||
	def request_subscription(self, jid, msg = '', name = '', groups = [],
 | 
			
		||||
	auto_auth = False, user_nick = ''):
 | 
			
		||||
			auto_auth = False, user_nick = ''):
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		log.debug('subscription request for %s' % jid)
 | 
			
		||||
| 
						 | 
				
			
			@ -1677,8 +1737,10 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		common.xmpp.features_nb.getRegInfo(con, self._hostname)
 | 
			
		||||
 | 
			
		||||
	def request_last_status_time(self, jid, resource, groupchat_jid=None):
 | 
			
		||||
		'''groupchat_jid is used when we want to send a request to a real jid
 | 
			
		||||
		and act as if the answer comes from the groupchat_jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		groupchat_jid is used when we want to send a request to a real jid and
 | 
			
		||||
		act as if the answer comes from the groupchat_jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		to_whom_jid = jid
 | 
			
		||||
| 
						 | 
				
			
			@ -1694,8 +1756,10 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def request_os_info(self, jid, resource, groupchat_jid=None):
 | 
			
		||||
		'''groupchat_jid is used when we want to send a request to a real jid
 | 
			
		||||
		and act as if the answer comes from the groupchat_jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		groupchat_jid is used when we want to send a request to a real jid and
 | 
			
		||||
		act as if the answer comes from the groupchat_jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		# If we are invisible, do not request
 | 
			
		||||
| 
						 | 
				
			
			@ -1715,8 +1779,10 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def request_entity_time(self, jid, resource, groupchat_jid=None):
 | 
			
		||||
		'''groupchat_jid is used when we want to send a request to a real jid
 | 
			
		||||
		and act as if the answer comes from the groupchat_jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		groupchat_jid is used when we want to send a request to a real jid and
 | 
			
		||||
		act as if the answer comes from the groupchat_jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		# If we are invisible, do not request
 | 
			
		||||
| 
						 | 
				
			
			@ -1736,7 +1802,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def get_settings(self):
 | 
			
		||||
		''' Get Gajim settings as described in XEP 0049 '''
 | 
			
		||||
		"""
 | 
			
		||||
		Get Gajim settings as described in XEP 0049
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ='get')
 | 
			
		||||
| 
						 | 
				
			
			@ -1757,9 +1825,12 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			self._request_bookmarks_xml()
 | 
			
		||||
 | 
			
		||||
	def get_bookmarks(self, storage_type=None):
 | 
			
		||||
		'''Get Bookmarks from storage or PubSub if supported as described in
 | 
			
		||||
		XEP 0048
 | 
			
		||||
		storage_type can be set to xml to force request to xml storage'''
 | 
			
		||||
		"""
 | 
			
		||||
		Get Bookmarks from storage or PubSub if supported as described in XEP
 | 
			
		||||
		0048
 | 
			
		||||
 | 
			
		||||
		storage_type can be set to xml to force request to xml storage
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		if self.pubsub_supported and storage_type != 'xml':
 | 
			
		||||
| 
						 | 
				
			
			@ -1771,9 +1842,12 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			self._request_bookmarks_xml()
 | 
			
		||||
 | 
			
		||||
	def store_bookmarks(self, storage_type=None):
 | 
			
		||||
		''' Send bookmarks to the storage namespace or PubSub if supported
 | 
			
		||||
		"""
 | 
			
		||||
		Send bookmarks to the storage namespace or PubSub if supported
 | 
			
		||||
 | 
			
		||||
		storage_type can be set to 'pubsub' or 'xml' so store in only one method
 | 
			
		||||
		else it will be stored on both'''
 | 
			
		||||
		else it will be stored on both
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
 | 
			
		||||
| 
						 | 
				
			
			@ -1815,7 +1889,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			self.connection.send(iqA)
 | 
			
		||||
 | 
			
		||||
	def get_annotations(self):
 | 
			
		||||
		'''Get Annonations from storage as described in XEP 0048, and XEP 0145'''
 | 
			
		||||
		"""
 | 
			
		||||
		Get Annonations from storage as described in XEP 0048, and XEP 0145
 | 
			
		||||
		"""
 | 
			
		||||
		self.annotations = {}
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			@ -1825,7 +1901,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def store_annotations(self):
 | 
			
		||||
		'''Set Annonations in private storage as described in XEP 0048, and XEP 0145'''
 | 
			
		||||
		"""
 | 
			
		||||
		Set Annonations in private storage as described in XEP 0048, and XEP 0145
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ='set')
 | 
			
		||||
| 
						 | 
				
			
			@ -1840,7 +1918,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
	def get_metacontacts(self):
 | 
			
		||||
		'''Get metacontacts list from storage as described in XEP 0049'''
 | 
			
		||||
		"""
 | 
			
		||||
		Get metacontacts list from storage as described in XEP 0049
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ='get')
 | 
			
		||||
| 
						 | 
				
			
			@ -1852,7 +1932,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def store_metacontacts(self, tags_list):
 | 
			
		||||
		''' Send meta contacts to the storage namespace '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send meta contacts to the storage namespace
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ='set')
 | 
			
		||||
| 
						 | 
				
			
			@ -2000,16 +2082,20 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(p)
 | 
			
		||||
 | 
			
		||||
	def gc_got_disconnected(self, room_jid):
 | 
			
		||||
		''' A groupchat got disconnected. This can be or purpose or not.
 | 
			
		||||
		"""
 | 
			
		||||
		A groupchat got disconnected. This can be or purpose or not
 | 
			
		||||
 | 
			
		||||
		Save the time we quit to avoid duplicate logs AND be faster than get that
 | 
			
		||||
		date from DB. Save it in mem AND in a small table (with fast access)
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		log_time = time_time()
 | 
			
		||||
		self.last_history_time[room_jid] = log_time
 | 
			
		||||
		gajim.logger.set_room_last_message_time(room_jid, log_time)
 | 
			
		||||
 | 
			
		||||
	def gc_set_role(self, room_jid, nick, role, reason = ''):
 | 
			
		||||
		'''role is for all the life of the room so it's based on nick'''
 | 
			
		||||
		"""
 | 
			
		||||
		Role is for all the life of the room so it's based on nick
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
 | 
			
		||||
| 
						 | 
				
			
			@ -2022,7 +2108,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
 | 
			
		||||
		'''affiliation is for all the life of the room so it's based on jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Affiliation is for all the life of the room so it's based on jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
 | 
			
		||||
| 
						 | 
				
			
			@ -2117,7 +2205,9 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
			_on_unregister_account_connect(self.connection)
 | 
			
		||||
 | 
			
		||||
	def send_invite(self, room, to, reason='', continue_tag=False):
 | 
			
		||||
		'''sends invitation'''
 | 
			
		||||
		"""
 | 
			
		||||
		Send invitation
 | 
			
		||||
		"""
 | 
			
		||||
		message=common.xmpp.Message(to = room)
 | 
			
		||||
		c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER)
 | 
			
		||||
		c = c.addChild(name = 'invite', attrs={'to' : to})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,9 +100,9 @@ class ConnectionBytestream:
 | 
			
		|||
		return True
 | 
			
		||||
 | 
			
		||||
	def send_success_connect_reply(self, streamhost):
 | 
			
		||||
		''' send reply to the initiator of FT that we
 | 
			
		||||
		made a connection
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		Send reply to the initiator of FT that we made a connection
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		if streamhost is None:
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +117,9 @@ class ConnectionBytestream:
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def remove_transfers_for_contact(self, contact):
 | 
			
		||||
		''' stop all active transfer for contact '''
 | 
			
		||||
		"""
 | 
			
		||||
		Stop all active transfer for contact
 | 
			
		||||
		"""
 | 
			
		||||
		for file_props in self.files_props.values():
 | 
			
		||||
			if self.is_transfer_stopped(file_props):
 | 
			
		||||
				continue
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +134,9 @@ class ConnectionBytestream:
 | 
			
		|||
				self.remove_transfer(file_props)
 | 
			
		||||
 | 
			
		||||
	def remove_all_transfers(self):
 | 
			
		||||
		''' stops and removes all active connections from the socks5 pool '''
 | 
			
		||||
		"""
 | 
			
		||||
		Stop and remove all active connections from the socks5 pool
 | 
			
		||||
		"""
 | 
			
		||||
		for file_props in self.files_props.values():
 | 
			
		||||
			self.remove_transfer(file_props, remove_from_list = False)
 | 
			
		||||
		del(self.files_props)
 | 
			
		||||
| 
						 | 
				
			
			@ -161,9 +165,11 @@ class ConnectionBytestream:
 | 
			
		|||
					gajim.socks5queue.remove_receiver(host['idx'])
 | 
			
		||||
					gajim.socks5queue.remove_sender(host['idx'])
 | 
			
		||||
 | 
			
		||||
	def send_socks5_info(self, file_props, fast = True, receiver = None,
 | 
			
		||||
		sender = None):
 | 
			
		||||
		''' send iq for the present streamhosts and proxies '''
 | 
			
		||||
	def send_socks5_info(self, file_props, fast = True, receiver = None, sender
 | 
			
		||||
			= None):
 | 
			
		||||
		"""
 | 
			
		||||
		Send iq for the present streamhosts and proxies
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		if not isinstance(self.peerhost, tuple):
 | 
			
		||||
| 
						 | 
				
			
			@ -269,9 +275,12 @@ class ConnectionBytestream:
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def send_file_rejection(self, file_props, code='403', typ=None):
 | 
			
		||||
		''' informs sender that we refuse to download the file
 | 
			
		||||
		"""
 | 
			
		||||
		Inform sender that we refuse to download the file
 | 
			
		||||
 | 
			
		||||
		typ is used when code = '400', in this case typ can be 'strean' for
 | 
			
		||||
		invalid stream or 'profile' for invalid profile'''
 | 
			
		||||
		invalid stream or 'profile' for invalid profile
 | 
			
		||||
		"""
 | 
			
		||||
		# user response to ConfirmationDialog may come after we've disconneted
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			@ -294,7 +303,9 @@ class ConnectionBytestream:
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def send_file_approval(self, file_props):
 | 
			
		||||
		''' send iq, confirming that we want to download the file '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send iq, confirming that we want to download the file
 | 
			
		||||
		"""
 | 
			
		||||
		# user response to ConfirmationDialog may come after we've disconneted
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			@ -326,7 +337,9 @@ class ConnectionBytestream:
 | 
			
		|||
		return file_props['receiver'].jid + '/' + file_props['receiver'].resource
 | 
			
		||||
 | 
			
		||||
	def send_file_request(self, file_props):
 | 
			
		||||
		''' send iq for new FT request '''
 | 
			
		||||
		"""
 | 
			
		||||
		Send iq for new FT request
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		file_props['sender'] = self._ft_get_our_jid()
 | 
			
		||||
| 
						 | 
				
			
			@ -357,7 +370,9 @@ class ConnectionBytestream:
 | 
			
		|||
		self.connection.send(iq)
 | 
			
		||||
 | 
			
		||||
	def _result_socks5_sid(self, sid, hash_id):
 | 
			
		||||
		''' store the result of sha message from auth. '''
 | 
			
		||||
		"""
 | 
			
		||||
		Store the result of SHA message from auth
 | 
			
		||||
		"""
 | 
			
		||||
		if sid not in self.files_props:
 | 
			
		||||
			return
 | 
			
		||||
		file_props = self.files_props[sid]
 | 
			
		||||
| 
						 | 
				
			
			@ -365,8 +380,10 @@ class ConnectionBytestream:
 | 
			
		|||
		return
 | 
			
		||||
 | 
			
		||||
	def _connect_error(self, to, _id, sid, code=404):
 | 
			
		||||
		''' cb, when there is an error establishing BS connection, or
 | 
			
		||||
		when connection is rejected'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when there is an error establishing BS connection, or when
 | 
			
		||||
		connection is rejected
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		msg_dict = {
 | 
			
		||||
| 
						 | 
				
			
			@ -391,7 +408,9 @@ class ConnectionBytestream:
 | 
			
		|||
				self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
 | 
			
		||||
 | 
			
		||||
	def _proxy_auth_ok(self, proxy):
 | 
			
		||||
		'''cb, called after authentication to proxy server '''
 | 
			
		||||
		"""
 | 
			
		||||
		Called after authentication to proxy server
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		file_props = self.files_props[proxy['sid']]
 | 
			
		||||
| 
						 | 
				
			
			@ -673,16 +692,24 @@ class ConnectionBytestream:
 | 
			
		|||
		raise common.xmpp.NodeProcessed
 | 
			
		||||
 | 
			
		||||
class ConnectionDisco:
 | 
			
		||||
	''' hold xmpppy handlers and public methods for discover services'''
 | 
			
		||||
	"""
 | 
			
		||||
	Holds xmpppy handlers and public methods for discover services
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def discoverItems(self, jid, node = None, id_prefix = None):
 | 
			
		||||
		'''According to XEP-0030: jid is mandatory,
 | 
			
		||||
		name, node, action is optional.'''
 | 
			
		||||
		"""
 | 
			
		||||
		According to XEP-0030:
 | 
			
		||||
			jid is mandatory;
 | 
			
		||||
			name, node, action is optional.
 | 
			
		||||
		"""
 | 
			
		||||
		self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
 | 
			
		||||
 | 
			
		||||
	def discoverInfo(self, jid, node = None, id_prefix = None):
 | 
			
		||||
		'''According to XEP-0030:
 | 
			
		||||
		"""
 | 
			
		||||
		According to XEP-0030:
 | 
			
		||||
			For identity: category, type is mandatory, name is optional.
 | 
			
		||||
			For feature: var is mandatory'''
 | 
			
		||||
			For feature: var is mandatory.
 | 
			
		||||
		"""
 | 
			
		||||
		self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix)
 | 
			
		||||
 | 
			
		||||
	def request_register_agent_info(self, agent):
 | 
			
		||||
| 
						 | 
				
			
			@ -738,7 +765,9 @@ class ConnectionDisco:
 | 
			
		|||
		self._IqCB(con, resp)
 | 
			
		||||
 | 
			
		||||
	def _discoGetCB(self, con, iq_obj):
 | 
			
		||||
		''' get disco info '''
 | 
			
		||||
		"""
 | 
			
		||||
		Get disco info
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		frm = helpers.get_full_jid_from_iq(iq_obj)
 | 
			
		||||
| 
						 | 
				
			
			@ -1008,9 +1037,11 @@ class ConnectionVcard:
 | 
			
		|||
			self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
 | 
			
		||||
 | 
			
		||||
	def get_cached_vcard(self, fjid, is_fake_jid = False):
 | 
			
		||||
		'''return the vcard as a dict
 | 
			
		||||
		return {} if vcard was too old
 | 
			
		||||
		return None if we don't have cached vcard'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the vcard as a dict.
 | 
			
		||||
		Return {} if vcard was too old.
 | 
			
		||||
		Return None if we don't have cached vcard.
 | 
			
		||||
		"""
 | 
			
		||||
		jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
 | 
			
		||||
		puny_jid = helpers.sanitize_filename(jid)
 | 
			
		||||
		if is_fake_jid:
 | 
			
		||||
| 
						 | 
				
			
			@ -1045,9 +1076,13 @@ class ConnectionVcard:
 | 
			
		|||
		return vcard
 | 
			
		||||
 | 
			
		||||
	def request_vcard(self, jid = None, groupchat_jid = None):
 | 
			
		||||
		'''request the VCARD. If groupchat_jid is not nul, it means we request a vcard
 | 
			
		||||
		to a fake jid, like in private messages in groupchat. jid can be the
 | 
			
		||||
		real jid of the contact, but we want to consider it comes from a fake jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Request the VCARD
 | 
			
		||||
 | 
			
		||||
		If groupchat_jid is not nul, it means we request a vcard to a fake jid,
 | 
			
		||||
		like in private messages in groupchat. jid can be the real jid of the
 | 
			
		||||
		contact, but we want to consider it comes from a fake jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		iq = common.xmpp.Iq(typ = 'get')
 | 
			
		||||
| 
						 | 
				
			
			@ -1238,8 +1273,9 @@ class ConnectionVcard:
 | 
			
		|||
		del self.awaiting_answers[id_]
 | 
			
		||||
 | 
			
		||||
	def _vCardCB(self, con, vc):
 | 
			
		||||
		'''Called when we receive a vCard
 | 
			
		||||
		Parse the vCard and send it to plugins'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we receive a vCard Parse the vCard and send it to plugins
 | 
			
		||||
		"""
 | 
			
		||||
		if not vc.getTag('vCard'):
 | 
			
		||||
			return
 | 
			
		||||
		if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD:
 | 
			
		||||
| 
						 | 
				
			
			@ -1345,8 +1381,9 @@ class ConnectionHandlersBase:
 | 
			
		|||
		self.sessions = {}
 | 
			
		||||
 | 
			
		||||
	def get_sessions(self, jid):
 | 
			
		||||
		'''get all sessions for the given full jid'''
 | 
			
		||||
 | 
			
		||||
		"""
 | 
			
		||||
		Get all sessions for the given full jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not gajim.interface.is_pm_contact(jid, self.name):
 | 
			
		||||
			jid = gajim.get_jid_without_resource(jid)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1356,9 +1393,10 @@ class ConnectionHandlersBase:
 | 
			
		|||
			return []
 | 
			
		||||
 | 
			
		||||
	def get_or_create_session(self, fjid, thread_id):
 | 
			
		||||
		'''returns an existing session between this connection and 'jid', returns a
 | 
			
		||||
		new one if none exist.'''
 | 
			
		||||
 | 
			
		||||
		"""
 | 
			
		||||
		Return an existing session between this connection and 'jid', returns a
 | 
			
		||||
		new one if none exist
 | 
			
		||||
		"""
 | 
			
		||||
		pm = True
 | 
			
		||||
		jid = fjid
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1386,7 +1424,9 @@ class ConnectionHandlersBase:
 | 
			
		|||
			return None
 | 
			
		||||
 | 
			
		||||
	def terminate_sessions(self, send_termination=False):
 | 
			
		||||
		'''send termination messages and delete all active sessions'''
 | 
			
		||||
		"""
 | 
			
		||||
		Send termination messages and delete all active sessions
 | 
			
		||||
		"""
 | 
			
		||||
		for jid in self.sessions:
 | 
			
		||||
			for thread_id in self.sessions[jid]:
 | 
			
		||||
				self.sessions[jid][thread_id].terminate(send_termination)
 | 
			
		||||
| 
						 | 
				
			
			@ -1405,10 +1445,11 @@ class ConnectionHandlersBase:
 | 
			
		|||
			del self.sessions[jid]
 | 
			
		||||
 | 
			
		||||
	def find_null_session(self, jid):
 | 
			
		||||
		'''finds all of the sessions between us and a remote jid in which we
 | 
			
		||||
haven't received a thread_id yet and returns the session that we last
 | 
			
		||||
sent a message to.'''
 | 
			
		||||
 | 
			
		||||
		"""
 | 
			
		||||
		Find all of the sessions between us and a remote jid in which we haven't
 | 
			
		||||
		received a thread_id yet and returns the session that we last sent a
 | 
			
		||||
		message to
 | 
			
		||||
		"""
 | 
			
		||||
		sessions = self.sessions[jid].values()
 | 
			
		||||
 | 
			
		||||
		# sessions that we haven't received a thread ID in
 | 
			
		||||
| 
						 | 
				
			
			@ -1425,8 +1466,9 @@ sent a message to.'''
 | 
			
		|||
			return None
 | 
			
		||||
 | 
			
		||||
	def find_controlless_session(self, jid, resource=None):
 | 
			
		||||
		'''find an active session that doesn't have a control attached'''
 | 
			
		||||
 | 
			
		||||
		"""
 | 
			
		||||
		Find an active session that doesn't have a control attached
 | 
			
		||||
		"""
 | 
			
		||||
		try:
 | 
			
		||||
			sessions = self.sessions[jid].values()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1444,8 +1486,12 @@ sent a message to.'''
 | 
			
		|||
			return None
 | 
			
		||||
 | 
			
		||||
	def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
 | 
			
		||||
		'''create and register a new session. thread_id=None to generate one.
 | 
			
		||||
		type_ should be 'chat' or 'pm'.'''
 | 
			
		||||
		"""
 | 
			
		||||
		Create and register a new session
 | 
			
		||||
 | 
			
		||||
		thread_id=None to generate one.
 | 
			
		||||
		type_ should be 'chat' or 'pm'.
 | 
			
		||||
		"""
 | 
			
		||||
		if not cls:
 | 
			
		||||
			cls = gajim.default_session_type
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1463,7 +1509,9 @@ sent a message to.'''
 | 
			
		|||
 | 
			
		||||
		return sess
 | 
			
		||||
 | 
			
		||||
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
 | 
			
		||||
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
 | 
			
		||||
		ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
 | 
			
		||||
		ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		ConnectionVcard.__init__(self)
 | 
			
		||||
		ConnectionBytestream.__init__(self)
 | 
			
		||||
| 
						 | 
				
			
			@ -1544,9 +1592,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
		self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
 | 
			
		||||
 | 
			
		||||
	def _PrivateCB(self, con, iq_obj):
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		Private Data (XEP 048 and 049)
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		log.debug('PrivateCB')
 | 
			
		||||
		query = iq_obj.getTag('query')
 | 
			
		||||
		storage = query.getTag('storage')
 | 
			
		||||
| 
						 | 
				
			
			@ -1573,8 +1621,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
					self.annotations[jid] = annotation
 | 
			
		||||
 | 
			
		||||
	def _parse_bookmarks(self, storage, storage_type):
 | 
			
		||||
		'''storage_type can be 'pubsub' or 'xml' to tell from where we got
 | 
			
		||||
		bookmarks'''
 | 
			
		||||
		"""
 | 
			
		||||
		storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks
 | 
			
		||||
		"""
 | 
			
		||||
		# Bookmarked URLs and Conferences
 | 
			
		||||
		# http://www.xmpp.org/extensions/xep-0048.html
 | 
			
		||||
		resend_to_pubsub = False
 | 
			
		||||
| 
						 | 
				
			
			@ -1788,7 +1837,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
		self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info))
 | 
			
		||||
 | 
			
		||||
	def _gMailNewMailCB(self, con, gm):
 | 
			
		||||
		'''Called when we get notified of new mail messages in gmail account'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we get notified of new mail messages in gmail account
 | 
			
		||||
		"""
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
		if not gm.getTag('new-mail'):
 | 
			
		||||
| 
						 | 
				
			
			@ -1810,7 +1861,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
			raise common.xmpp.NodeProcessed
 | 
			
		||||
 | 
			
		||||
	def _gMailQueryCB(self, con, gm):
 | 
			
		||||
		'''Called when we receive results from Querying the server for mail messages in gmail account'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we receive results from Querying the server for mail messages
 | 
			
		||||
		in gmail account
 | 
			
		||||
		"""
 | 
			
		||||
		if not gm.getTag('mailbox'):
 | 
			
		||||
			return
 | 
			
		||||
		self.gmail_url = gm.getTag('mailbox').getAttr('url')
 | 
			
		||||
| 
						 | 
				
			
			@ -1856,7 +1910,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
			raise common.xmpp.NodeProcessed
 | 
			
		||||
 | 
			
		||||
	def _rosterItemExchangeCB(self, con, msg):
 | 
			
		||||
		''' XEP-0144 Roster Item Echange '''
 | 
			
		||||
		"""
 | 
			
		||||
		XEP-0144 Roster Item Echange
 | 
			
		||||
		"""
 | 
			
		||||
		exchange_items_list = {}
 | 
			
		||||
		jid_from = helpers.get_full_jid_from_iq(msg)
 | 
			
		||||
		items_list = msg.getTag('x').getChildren()
 | 
			
		||||
| 
						 | 
				
			
			@ -1898,7 +1954,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
		raise common.xmpp.NodeProcessed
 | 
			
		||||
 | 
			
		||||
	def _messageCB(self, con, msg):
 | 
			
		||||
		'''Called when we receive a message'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we receive a message
 | 
			
		||||
		"""
 | 
			
		||||
		log.debug('MessageCB')
 | 
			
		||||
		mtype = msg.getType()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2167,7 +2225,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
			is_continued))
 | 
			
		||||
 | 
			
		||||
	def _presenceCB(self, con, prs):
 | 
			
		||||
		'''Called when we receive a presence'''
 | 
			
		||||
		"""
 | 
			
		||||
		Called when we receive a presence
 | 
			
		||||
		"""
 | 
			
		||||
		ptype = prs.getType()
 | 
			
		||||
		if ptype == 'available':
 | 
			
		||||
			ptype = None
 | 
			
		||||
| 
						 | 
				
			
			@ -2269,7 +2329,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
 | 
			
		||||
				gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
 | 
			
		||||
						self.name)
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
				# If gc_control is missing - it may be minimized. Try to get it from
 | 
			
		||||
				# there. If it's not there - then it's missing anyway and will
 | 
			
		||||
				# remain set to None.
 | 
			
		||||
| 
						 | 
				
			
			@ -2550,11 +2610,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
			
		|||
		raise common.xmpp.NodeProcessed
 | 
			
		||||
 | 
			
		||||
	def _PrivacySetCB(self, con, iq_obj):
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		Privacy lists (XEP 016)
 | 
			
		||||
 | 
			
		||||
		A list has been set
 | 
			
		||||
		'''
 | 
			
		||||
		A list has been set.
 | 
			
		||||
		"""
 | 
			
		||||
		log.debug('PrivacySetCB')
 | 
			
		||||
		if not self.connection or self.connected < 2:
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,26 +33,28 @@ import caps
 | 
			
		|||
from account import Account
 | 
			
		||||
 | 
			
		||||
class XMPPEntity(object):
 | 
			
		||||
	'''Base representation of entities in XMPP'''
 | 
			
		||||
	
 | 
			
		||||
	"""
 | 
			
		||||
	Base representation of entities in XMPP
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, jid, account, resource):
 | 
			
		||||
		self.jid = jid
 | 
			
		||||
		self.resource = resource
 | 
			
		||||
		self.account = account
 | 
			
		||||
 | 
			
		||||
class CommonContact(XMPPEntity):
 | 
			
		||||
	
 | 
			
		||||
	def __init__(self, jid, account, resource, show, status, name, our_chatstate,
 | 
			
		||||
	composing_xep, chatstate, client_caps=None):
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def __init__(self, jid, account, resource, show, status, name,
 | 
			
		||||
			our_chatstate, composing_xep, chatstate, client_caps=None):
 | 
			
		||||
 | 
			
		||||
		XMPPEntity.__init__(self, jid, account, resource)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.show = show
 | 
			
		||||
		self.status = status
 | 
			
		||||
		self.name = name
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.client_caps = client_caps or caps.NullClientCaps()
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
 | 
			
		||||
		# we keep track of xep85 support with the peer by three extra states:
 | 
			
		||||
		# None, False and 'ask'
 | 
			
		||||
| 
						 | 
				
			
			@ -67,18 +69,18 @@ class CommonContact(XMPPEntity):
 | 
			
		|||
		self.composing_xep = composing_xep
 | 
			
		||||
		# this is contact's chatstate
 | 
			
		||||
		self.chatstate = chatstate
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_full_jid(self):
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_shown_name(self):
 | 
			
		||||
		raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
	def supports(self, requested_feature):
 | 
			
		||||
		'''
 | 
			
		||||
		Returns True if the contact has advertised to support the feature
 | 
			
		||||
		"""
 | 
			
		||||
		Return True if the contact has advertised to support the feature
 | 
			
		||||
		identified by the given namespace. False otherwise.
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		if self.show == 'offline':
 | 
			
		||||
			# Unfortunately, if all resources are offline, the contact
 | 
			
		||||
			# includes the last resource that was online. Check for its
 | 
			
		||||
| 
						 | 
				
			
			@ -90,26 +92,29 @@ class CommonContact(XMPPEntity):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Contact(CommonContact):
 | 
			
		||||
	'''Information concerning each contact'''
 | 
			
		||||
	def __init__(self, jid, account, name='', groups=[], show='', status='', sub='',
 | 
			
		||||
	ask='', resource='', priority=0, keyID='', client_caps=None,
 | 
			
		||||
	our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None,
 | 
			
		||||
	composing_xep=None):
 | 
			
		||||
		
 | 
			
		||||
		CommonContact.__init__(self, jid, account, resource, show, status, name, 
 | 
			
		||||
	"""
 | 
			
		||||
	Information concerning each contact
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, jid, account, name='', groups=[], show='', status='',
 | 
			
		||||
			sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
 | 
			
		||||
			our_chatstate=None, chatstate=None, last_status_time=None, msg_id =
 | 
			
		||||
			None, composing_xep=None):
 | 
			
		||||
 | 
			
		||||
		CommonContact.__init__(self, jid, account, resource, show, status, name,
 | 
			
		||||
			our_chatstate, composing_xep, chatstate, client_caps=client_caps)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.contact_name = '' # nick choosen by contact
 | 
			
		||||
		self.groups = [i for i in set(groups)] # filter duplicate values
 | 
			
		||||
 | 
			
		||||
		self.sub = sub
 | 
			
		||||
		self.ask = ask
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.priority = priority
 | 
			
		||||
		self.keyID = keyID
 | 
			
		||||
		self.msg_id = msg_id
 | 
			
		||||
		self.last_status_time = last_status_time
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.pep = {}
 | 
			
		||||
 | 
			
		||||
	def get_full_jid(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +142,9 @@ class Contact(CommonContact):
 | 
			
		|||
			return self.groups
 | 
			
		||||
 | 
			
		||||
	def is_hidden_from_roster(self):
 | 
			
		||||
		'''if contact should not be visible in roster'''
 | 
			
		||||
		"""
 | 
			
		||||
		If contact should not be visible in roster
 | 
			
		||||
		"""
 | 
			
		||||
		# XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
 | 
			
		||||
		if self.is_transport():
 | 
			
		||||
			return False
 | 
			
		||||
| 
						 | 
				
			
			@ -173,43 +180,50 @@ class Contact(CommonContact):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class GC_Contact(CommonContact):
 | 
			
		||||
	'''Information concerning each groupchat contact'''
 | 
			
		||||
	"""
 | 
			
		||||
	Information concerning each groupchat contact
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, room_jid, account, name='', show='', status='', role='',
 | 
			
		||||
	affiliation='', jid='', resource='', our_chatstate=None,
 | 
			
		||||
	composing_xep=None, chatstate=None):
 | 
			
		||||
		
 | 
			
		||||
			affiliation='', jid='', resource='', our_chatstate=None,
 | 
			
		||||
			composing_xep=None, chatstate=None):
 | 
			
		||||
 | 
			
		||||
		CommonContact.__init__(self, jid, account, resource, show, status, name,
 | 
			
		||||
				our_chatstate, composing_xep, chatstate)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self.room_jid = room_jid
 | 
			
		||||
		self.role = role
 | 
			
		||||
		self.affiliation = affiliation
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def get_full_jid(self):
 | 
			
		||||
		return self.room_jid + '/' + self.name
 | 
			
		||||
 | 
			
		||||
	def get_shown_name(self):
 | 
			
		||||
		return self.name
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def as_contact(self):
 | 
			
		||||
		'''Create a Contact instance from this GC_Contact instance'''
 | 
			
		||||
		"""
 | 
			
		||||
		Create a Contact instance from this GC_Contact instance
 | 
			
		||||
		"""
 | 
			
		||||
		return Contact(jid=self.get_full_jid(), account=self.account,
 | 
			
		||||
			resource=self.resource, name=self.name, groups=[], show=self.show,
 | 
			
		||||
			status=self.status, sub='none', client_caps=self.client_caps)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Contacts:
 | 
			
		||||
	'''Information concerning all contacts and groupchat contacts'''
 | 
			
		||||
	"""
 | 
			
		||||
	Information concerning all contacts and groupchat contacts
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		
 | 
			
		||||
		self._metacontact_manager = MetacontactManager(self)
 | 
			
		||||
		self._accounts = {}
 | 
			
		||||
		
 | 
			
		||||
	def change_account_name(self, old_name, new_name):		
 | 
			
		||||
 | 
			
		||||
	def change_account_name(self, old_name, new_name):
 | 
			
		||||
		self._accounts[new_name] = self._accounts[old_name]
 | 
			
		||||
		self._accounts[new_name].name = new_name
 | 
			
		||||
		del self._accounts[old_name]
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		self._metacontact_manager.change_account_name(old_name, new_name)
 | 
			
		||||
 | 
			
		||||
	def add_account(self, account_name):
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +248,7 @@ class Contacts:
 | 
			
		|||
			keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate,
 | 
			
		||||
			chatstate=chatstate, last_status_time=last_status_time,
 | 
			
		||||
			composing_xep=composing_xep)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def create_self_contact(self, jid, account, resource, show, status, priority,
 | 
			
		||||
	name='', keyID=''):
 | 
			
		||||
		conn = common.gajim.connections[account]
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +260,7 @@ class Contacts:
 | 
			
		|||
			resource=resource)
 | 
			
		||||
		self_contact.pep = conn.pep
 | 
			
		||||
		return self_contact
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''):
 | 
			
		||||
		account = self._accounts.get(account, account) # Use Account object if available
 | 
			
		||||
		return self.create_contact(jid=jid, account=account, resource=resource,
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +289,7 @@ class Contacts:
 | 
			
		|||
		self._accounts[account].contacts.remove_jid(jid)
 | 
			
		||||
		if remove_meta:
 | 
			
		||||
			self._metacontact_manager.remove_metacontact(account, jid)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def get_contacts(self, account, jid):
 | 
			
		||||
		return self._accounts[account].contacts.get_contacts(jid)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -288,13 +302,13 @@ class Contacts:
 | 
			
		|||
 | 
			
		||||
	def get_contact_from_full_jid(self, account, fjid):
 | 
			
		||||
		return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_first_contact_from_jid(self, account, jid):
 | 
			
		||||
		return self._accounts[account].contacts.get_first_contact_from_jid(jid)
 | 
			
		||||
 | 
			
		||||
	def get_contacts_from_group(self, account, group):
 | 
			
		||||
		return self._accounts[account].contacts.get_contacts_from_group(group)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_jid_list(self, account):
 | 
			
		||||
		return self._accounts[account].contacts.get_jid_list()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -318,10 +332,11 @@ class Contacts:
 | 
			
		|||
			contact = self.get_gc_contact(account, room, nick)
 | 
			
		||||
			return contact
 | 
			
		||||
		return self.get_highest_prio_contact_from_contacts(contacts)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_nb_online_total_contacts(self, accounts=[], groups=[]):
 | 
			
		||||
		'''Returns the number of online contacts and the total number of
 | 
			
		||||
		contacts'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the number of online contacts and the total number of contacts
 | 
			
		||||
		"""
 | 
			
		||||
		if accounts == []:
 | 
			
		||||
			accounts = self.get_accounts()
 | 
			
		||||
		nbr_online = 0
 | 
			
		||||
| 
						 | 
				
			
			@ -358,24 +373,28 @@ class Contacts:
 | 
			
		|||
		return nbr_online, nbr_total
 | 
			
		||||
 | 
			
		||||
	def is_pm_from_jid(self, account, jid):
 | 
			
		||||
		'''Returns True if the given jid is a private message jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return True if the given jid is a private message jid
 | 
			
		||||
		"""
 | 
			
		||||
		if jid in self._contacts[account]:
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def is_pm_from_contact(self, account, contact):
 | 
			
		||||
		'''Returns True if the given contact is a private message contact'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return True if the given contact is a private message contact
 | 
			
		||||
		"""
 | 
			
		||||
		if isinstance(contact, Contact):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __getattr__(self, attr_name):
 | 
			
		||||
		# Only called if self has no attr_name
 | 
			
		||||
		if hasattr(self._metacontact_manager, attr_name):
 | 
			
		||||
			return getattr(self._metacontact_manager, attr_name)
 | 
			
		||||
		else:
 | 
			
		||||
			raise AttributeError(attr_name)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def create_gc_contact(self, room_jid, account, name='', show='', status='',
 | 
			
		||||
		role='', affiliation='', jid='', resource=''):
 | 
			
		||||
		account = self._accounts.get(account, account) # Use Account object if available
 | 
			
		||||
| 
						 | 
				
			
			@ -402,15 +421,15 @@ class Contacts:
 | 
			
		|||
 | 
			
		||||
	def get_nb_role_total_gc_contacts(self, account, room_jid, role):
 | 
			
		||||
		return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Contacts_New():
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		# list of contacts  {jid1: [C1, C2]}, } one Contact per resource
 | 
			
		||||
		self._contacts = {}
 | 
			
		||||
		
 | 
			
		||||
	def add_contact(self, contact):		
 | 
			
		||||
 | 
			
		||||
	def add_contact(self, contact):
 | 
			
		||||
		if contact.jid not in self._contacts:
 | 
			
		||||
			self._contacts[contact.jid] = [contact]
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			@ -426,7 +445,7 @@ class Contacts_New():
 | 
			
		|||
				self.remove_contact(c)
 | 
			
		||||
				break
 | 
			
		||||
		contacts.append(contact)
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def remove_contact(self, contact):
 | 
			
		||||
		if contact.jid not in self._contacts:
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			@ -434,28 +453,34 @@ class Contacts_New():
 | 
			
		|||
			self._contacts[contact.jid].remove(contact)
 | 
			
		||||
		if len(self._contacts[contact.jid]) == 0:
 | 
			
		||||
			del self._contacts[contact.jid]
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
	def remove_jid(self, jid):
 | 
			
		||||
		'''Removes all contacts for a given jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Remove all contacts for a given jid
 | 
			
		||||
		"""
 | 
			
		||||
		if jid not in self._contacts:
 | 
			
		||||
			return
 | 
			
		||||
		del self._contacts[jid]
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
	def get_contacts(self, jid):
 | 
			
		||||
		'''Returns the list of contact instances for this jid.'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the list of contact instances for this jid
 | 
			
		||||
		"""
 | 
			
		||||
		if jid in self._contacts:
 | 
			
		||||
			return self._contacts[jid]
 | 
			
		||||
		else:
 | 
			
		||||
			return []
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def get_contact(self, jid, resource=None):
 | 
			
		||||
		### WARNING ###
 | 
			
		||||
		# This function returns a *RANDOM* resource if resource = None!
 | 
			
		||||
		# Do *NOT* use if you need to get the contact to which you
 | 
			
		||||
		# send a message for example, as a bare JID in Jabber means
 | 
			
		||||
		# highest available resource, which this function ignores!
 | 
			
		||||
		'''Returns the contact instance for the given resource if it's given else
 | 
			
		||||
		the first contact is no resource is given or None if there is not'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the contact instance for the given resource if it's given else the
 | 
			
		||||
		first contact is no resource is given or None if there is not
 | 
			
		||||
		"""
 | 
			
		||||
		if jid in self._contacts:
 | 
			
		||||
			if not resource:
 | 
			
		||||
				return self._contacts[jid][0]
 | 
			
		||||
| 
						 | 
				
			
			@ -468,22 +493,26 @@ class Contacts_New():
 | 
			
		|||
		for jid in self._contacts.keys():
 | 
			
		||||
			for contact in self._contacts[jid][:]:
 | 
			
		||||
				yield contact
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
	def get_jid_list(self):
 | 
			
		||||
		return self._contacts.keys()
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
	def get_contact_from_full_jid(self, fjid):
 | 
			
		||||
		''' Get Contact object for specific resource of given jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Get Contact object for specific resource of given jid
 | 
			
		||||
		"""
 | 
			
		||||
		barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
 | 
			
		||||
		return self.get_contact(barejid, resource)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_first_contact_from_jid(self, jid):
 | 
			
		||||
		if jid in self._contacts:
 | 
			
		||||
			return self._contacts[jid][0]
 | 
			
		||||
		return None
 | 
			
		||||
 | 
			
		||||
	def get_contacts_from_group(self, group):
 | 
			
		||||
		'''Returns all contacts in the given group'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return all contacts in the given group
 | 
			
		||||
		"""
 | 
			
		||||
		group_contacts = []
 | 
			
		||||
		for jid in self._contacts:
 | 
			
		||||
			contacts = self.get_contacts(jid)
 | 
			
		||||
| 
						 | 
				
			
			@ -502,11 +531,11 @@ class Contacts_New():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class GC_Contacts():
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		# list of contacts that are in gc {room_jid: {nick: C}}}
 | 
			
		||||
		self._rooms = {}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def add_gc_contact(self, gc_contact):
 | 
			
		||||
		if gc_contact.room_jid not in self._rooms:
 | 
			
		||||
			self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
 | 
			
		||||
| 
						 | 
				
			
			@ -544,8 +573,10 @@ class GC_Contacts():
 | 
			
		|||
		return self._rooms[room_jid][nick]
 | 
			
		||||
 | 
			
		||||
	def get_nb_role_total_gc_contacts(self, room_jid, role):
 | 
			
		||||
		'''Returns the number of group chat contacts for the given role and the
 | 
			
		||||
		total number of group chat contacts'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the number of group chat contacts for the given role and the total
 | 
			
		||||
		number of group chat contacts
 | 
			
		||||
		"""
 | 
			
		||||
		if room_jid not in self._rooms:
 | 
			
		||||
			return 0, 0
 | 
			
		||||
		nb_role = nb_total = 0
 | 
			
		||||
| 
						 | 
				
			
			@ -554,25 +585,25 @@ class GC_Contacts():
 | 
			
		|||
				nb_role += 1
 | 
			
		||||
			nb_total += 1
 | 
			
		||||
		return nb_role, nb_total
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetacontactManager():
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def __init__(self, contacts):
 | 
			
		||||
		self._metacontacts_tags = {}
 | 
			
		||||
		self._contacts = contacts
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def change_account_name(self, old_name, new_name):
 | 
			
		||||
		self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
 | 
			
		||||
		del self._metacontacts_tags[old_name]
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def add_account(self, account):
 | 
			
		||||
		if account not in self._metacontacts_tags:
 | 
			
		||||
			self._metacontacts_tags[account] = {}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def remove_account(self, account):
 | 
			
		||||
		del self._metacontacts_tags[account]
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	def define_metacontacts(self, account, tags_list):
 | 
			
		||||
		self._metacontacts_tags[account] = tags_list
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -582,13 +613,15 @@ class MetacontactManager():
 | 
			
		|||
		#FIXME: can this append ?
 | 
			
		||||
		assert False
 | 
			
		||||
 | 
			
		||||
	def iter_metacontacts_families(self, account):		
 | 
			
		||||
	def iter_metacontacts_families(self, account):
 | 
			
		||||
		for tag in self._metacontacts_tags[account]:
 | 
			
		||||
			family = self._get_metacontacts_family_from_tag(account, tag)
 | 
			
		||||
			yield family
 | 
			
		||||
 | 
			
		||||
	def _get_metacontacts_tag(self, account, jid):
 | 
			
		||||
		'''Returns the tag of a jid'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the tag of a jid
 | 
			
		||||
		"""
 | 
			
		||||
		if not account in self._metacontacts_tags:
 | 
			
		||||
			return None
 | 
			
		||||
		for tag in self._metacontacts_tags[account]:
 | 
			
		||||
| 
						 | 
				
			
			@ -626,7 +659,7 @@ class MetacontactManager():
 | 
			
		|||
	def remove_metacontact(self, account, jid):
 | 
			
		||||
		if not account in self._metacontacts_tags:
 | 
			
		||||
			return None
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		found = None
 | 
			
		||||
		for tag in self._metacontacts_tags[account]:
 | 
			
		||||
			for data in self._metacontacts_tags[account][tag]:
 | 
			
		||||
| 
						 | 
				
			
			@ -657,7 +690,9 @@ class MetacontactManager():
 | 
			
		|||
		return False
 | 
			
		||||
 | 
			
		||||
	def _get_metacontacts_jids(self, tag, accounts):
 | 
			
		||||
		'''Returns all jid for the given tag in the form {acct: [jid1, jid2],.}'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return all jid for the given tag in the form {acct: [jid1, jid2],.}
 | 
			
		||||
		"""
 | 
			
		||||
		answers = {}
 | 
			
		||||
		for account in self._metacontacts_tags:
 | 
			
		||||
			if tag in self._metacontacts_tags[account]:
 | 
			
		||||
| 
						 | 
				
			
			@ -669,9 +704,10 @@ class MetacontactManager():
 | 
			
		|||
		return answers
 | 
			
		||||
 | 
			
		||||
	def get_metacontacts_family(self, account, jid):
 | 
			
		||||
		'''return the family of the given jid, including jid in the form:
 | 
			
		||||
		[{'account': acct, 'jid': jid, 'order': order}, ]
 | 
			
		||||
		'order' is optional'''
 | 
			
		||||
		"""
 | 
			
		||||
		Return the family of the given jid, including jid in the form:
 | 
			
		||||
		[{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
 | 
			
		||||
		"""
 | 
			
		||||
		tag = self._get_metacontacts_tag(account, jid)
 | 
			
		||||
		return self._get_metacontacts_family_from_tag(account, tag)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -687,9 +723,12 @@ class MetacontactManager():
 | 
			
		|||
		return answers
 | 
			
		||||
 | 
			
		||||
	def _compare_metacontacts(self, data1, data2):
 | 
			
		||||
		'''compare 2 metacontacts.
 | 
			
		||||
		Data is {'jid': jid, 'account': account, 'order': order}
 | 
			
		||||
		order is optional'''
 | 
			
		||||
		"""
 | 
			
		||||
		Compare 2 metacontacts
 | 
			
		||||
 | 
			
		||||
		Data is {'jid': jid, 'account': account, 'order': order} order is
 | 
			
		||||
		optional
 | 
			
		||||
		"""
 | 
			
		||||
		jid1 = data1['jid']
 | 
			
		||||
		jid2 = data2['jid']
 | 
			
		||||
		account1 = data1['account']
 | 
			
		||||
| 
						 | 
				
			
			@ -765,16 +804,17 @@ class MetacontactManager():
 | 
			
		|||
		if account2 > account1:
 | 
			
		||||
			return -1
 | 
			
		||||
		return 0
 | 
			
		||||
	
 | 
			
		||||
	def get_nearby_family_and_big_brother(self, family, account):
 | 
			
		||||
		'''Return the nearby family and its Big Brother
 | 
			
		||||
 | 
			
		||||
		Nearby family is the part of the family that is grouped with the metacontact.
 | 
			
		||||
		A metacontact may be over different accounts. If accounts are not merged
 | 
			
		||||
		then the given family is split account wise.
 | 
			
		||||
	def get_nearby_family_and_big_brother(self, family, account):
 | 
			
		||||
		"""
 | 
			
		||||
		Return the nearby family and its Big Brother
 | 
			
		||||
 | 
			
		||||
		Nearby family is the part of the family that is grouped with the
 | 
			
		||||
		metacontact.  A metacontact may be over different accounts. If accounts
 | 
			
		||||
		are not merged then the given family is split account wise.
 | 
			
		||||
 | 
			
		||||
		(nearby_family, big_brother_jid, big_brother_account)
 | 
			
		||||
		'''
 | 
			
		||||
		"""
 | 
			
		||||
		if common.gajim.config.get('mergeaccounts'):
 | 
			
		||||
			# group all together
 | 
			
		||||
			nearby_family = family
 | 
			
		||||
| 
						 | 
				
			
			@ -789,9 +829,11 @@ class MetacontactManager():
 | 
			
		|||
		return (nearby_family, big_brother_jid, big_brother_account)
 | 
			
		||||
 | 
			
		||||
	def _get_metacontacts_big_brother(self, family):
 | 
			
		||||
		'''which of the family will be the big brother under wich all
 | 
			
		||||
		others will be ?'''
 | 
			
		||||
		"""
 | 
			
		||||
		Which of the family will be the big brother under wich all others will be
 | 
			
		||||
		?
 | 
			
		||||
		"""
 | 
			
		||||
		family.sort(cmp=self._compare_metacontacts)
 | 
			
		||||
		return family[-1]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
# vim: se ts=3:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue