root/trunk/src/common/caps.py

Revision 10810, 9.5 kB (checked in by js, 5 weeks ago)

Remove debug output.

Line 
1# -*- coding:utf-8 -*-
2## src/common/caps.py
3##
4## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
5##                    Travis Shirk <travis AT pobox.com>
6## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org>
7## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
8##                    Jonathan Schleifer <js-gajim AT webkeks.org>
9##                    Stephan Erb <steve-e AT h3c.de>
10##
11## This file is part of Gajim.
12##
13## Gajim is free software; you can redistribute it and/or modify
14## it under the terms of the GNU General Public License as published
15## by the Free Software Foundation; version 3 only.
16##
17## Gajim is distributed in the hope that it will be useful,
18## but WITHOUT ANY WARRANTY; without even the implied warranty of
19## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20## GNU General Public License for more details.
21##
22## You should have received a copy of the GNU General Public License
23## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
24##
25
26from itertools import *
27import gajim
28import helpers
29
30class CapsCache(object):
31        ''' This object keeps the mapping between caps data and real disco
32        features they represent, and provides simple way to query that info.
33        It is application-wide, that is there's one object for all
34        connections.
35        Goals:
36         * handle storing/retrieving info from database
37         * cache info in memory
38         * expose simple interface
39        Properties:
40         * one object for all connections (move to logger.py?)
41         * store info efficiently (a set() of urls -- we can assume there won't be
42           too much of these, ensure that (X,Y,Z1) and (X,Y,Z2) has different
43           features.
44
45        Connections with other objects: (TODO)
46
47        Interface:
48
49        # object creation
50        >>> cc=CapsCache(logger_object)
51
52        >>> caps = ('sha-1', '66/0NaeaBKkwk85efJTGmU47vXI=')
53        >>> muc = 'http://jabber.org/protocol/muc'
54        >>> chatstates = 'http://jabber.org/protocol/chatstates'
55
56        # setting data
57        >>> cc[caps].identities = [{'category':'client', 'type':'pc'}]
58        >>> cc[caps].features = [muc]
59
60        # retrieving data
61        >>> muc in cc[caps].features
62        True
63        >>> chatstates in cc[caps].features
64        False
65        >>> cc[caps].identities
66        [{'category': 'client', 'type': 'pc'}]
67        >>> x = cc[caps] # more efficient if making several queries for one set of caps
68        ATypicalBlackBoxObject
69        >>> muc in x.features
70        True
71
72        '''
73        def __init__(self, logger=None):
74                ''' Create a cache for entity capabilities. '''
75                # our containers:
76                # __cache is a dictionary mapping: pair of hash method and hash maps
77                #   to CapsCacheItem object
78                # __CacheItem is a class that stores data about particular
79                #   client (hash method/hash pair)
80                self.__cache = {}
81
82                class CacheItem(object):
83                        ''' TODO: logging data into db '''
84                        # __names is a string cache; every string long enough is given
85                        #   another object, and we will have plenty of identical long
86                        #   strings. therefore we can cache them
87                        #   TODO: maybe put all known xmpp namespace strings here
88                        #   (strings given in xmpppy)?
89                        __names = {}
90                        def __init__(ciself, hash_method, hash_):
91                                # cached into db
92                                ciself.hash_method = hash_method
93                                ciself.hash = hash_
94                                ciself._features = []
95                                ciself._identities = []
96
97                                # not cached into db:
98                                # have we sent the query?
99                                # 0 == not queried
100                                # 1 == queried
101                                # 2 == got the answer
102                                ciself.queried = 0
103
104                        def _get_features(ciself):
105                                return ciself._features
106
107                        def _set_features(ciself, value):
108                                ciself._features = []
109                                for feature in value:
110                                        ciself._features.append(ciself.__names.setdefault(feature,
111                                                feature))
112                        features = property(_get_features, _set_features)
113
114                        def _get_identities(ciself):
115                                list_ = []
116                                for i in ciself._identities:
117                                        # transforms it back in a dict
118                                        d = dict()
119                                        d['category'] = i[0]
120                                        if i[1]:
121                                                d['type'] = i[1]
122                                        if i[2]:
123                                                d['xml:lang'] = i[2]
124                                        if i[3]:
125                                                d['name'] = i[3]
126                                        list_.append(d)
127                                return list_
128                        def _set_identities(ciself, value):
129                                ciself._identities = []
130                                for identity in value:
131                                        # dict are not hashable, so transform it into a tuple
132                                        t = (identity['category'], identity.get('type'),
133                                                identity.get('xml:lang'), identity.get('name'))
134                                        ciself._identities.append(ciself.__names.setdefault(t, t))
135                        identities = property(_get_identities, _set_identities)
136
137                        def update(ciself, identities, features):
138                                # NOTE: self refers to CapsCache object, not to CacheItem
139                                ciself.identities=identities
140                                ciself.features=features
141                                self.logger.add_caps_entry(ciself.hash_method, ciself.hash,
142                                        identities, features)
143
144                self.__CacheItem = CacheItem
145
146                # prepopulate data which we are sure of; note: we do not log these info
147
148                for account in gajim.connections:
149                        gajimcaps = self[('sha-1', gajim.caps_hash[account])]
150                        gajimcaps.identities = [gajim.gajim_identity]
151                        gajimcaps.features = gajim.gajim_common_features + \
152                                gajim.gajim_optional_features[account]
153
154                # start logging data from the net
155                self.logger = logger
156
157        def load_from_db(self):
158                # get data from logger...
159                if self.logger is not None:
160                        for hash_method, hash_, identities, features in \
161                        self.logger.iter_caps_data():
162                                x = self[(hash_method, hash_)]
163                                x.identities = identities
164                                x.features = features
165                                x.queried = 2
166
167        def __getitem__(self, caps):
168                if caps in self.__cache:
169                        return self.__cache[caps]
170
171                hash_method, hash_ = caps
172
173                x = self.__CacheItem(hash_method, hash_)
174                self.__cache[(hash_method, hash_)] = x
175                return x
176
177        def preload(self, con, jid, node, hash_method, hash_):
178                ''' Preload data about (node, ver, exts) caps using disco
179                query to jid using proper connection. Don't query if
180                the data is already in cache. '''
181                if hash_method == 'old':
182                        q = self[(hash_method, node + '#' + hash_)]
183                else:
184                        q = self[(hash_method, hash_)]
185
186                if q.queried==0:
187                        # do query for bare node+hash pair
188                        # this will create proper object
189                        q.queried=1
190                        if hash_method == 'old':
191                                con.discoverInfo(jid)
192                        else:
193                                con.discoverInfo(jid, '%s#%s' % (node, hash_))
194
195        def is_supported(self, contact, feature):
196                if not contact:
197                        return False
198
199                # Unfortunately, if all resources are offline, the contact
200                # includes the last resource that was online. Check for its
201                # show, so we can be sure it's existant. Otherwise, we still
202                # return caps for a contact that has no resources left.
203                if contact.show == 'offline':
204                        return False
205
206                # FIXME: We assume everything is supported if we got no caps.
207                #        This is the "Asterix way", after 0.12 release, I will
208                #        likely implement a fallback to disco (could be disabled
209                #        for mobile users who pay for traffic)
210                if contact.caps_hash_method == 'old':
211                        features = self[(contact.caps_hash_method, contact.caps_node + '#' + \
212                                contact.caps_hash)].features
213                else:
214                        features = self[(contact.caps_hash_method, contact.caps_hash)].features
215                if feature in features or features == []:
216                        return True
217
218                return False
219
220gajim.capscache = CapsCache(gajim.logger)
221
222class ConnectionCaps(object):
223        ''' This class highly depends on that it is a part of Connection class. '''
224        def _capsPresenceCB(self, con, presence):
225                ''' Handle incoming presence stanzas... This is a callback
226                for xmpp registered in connection_handlers.py'''
227
228                # we will put these into proper Contact object and ask
229                # for disco... so that disco will learn how to interpret
230                # these caps
231                pm_ctrl = None
232                jid = helpers.get_full_jid_from_iq(presence)
233                contact = gajim.contacts.get_contact_from_full_jid(self.name, jid)
234                if contact is None:
235                        room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
236                        contact = gajim.contacts.get_gc_contact(
237                                self.name, room_jid, nick)
238                        pm_ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.name)
239                        if contact is None:
240                                # TODO: a way to put contact not-in-roster
241                                # into Contacts
242                                return
243
244                # get the caps element
245                caps = presence.getTag('c')
246                if not caps:
247                        contact.caps_node = None
248                        contact.caps_hash = None
249                        contact.caps_hash_method = None
250                        return
251
252                hash_method, node, hash_ = caps['hash'], caps['node'], caps['ver']
253
254                if hash_method is None and node and hash_:
255                        # Old XEP-115 implentation
256                        hash_method = 'old'
257
258                if hash_method is None or node is None or hash_ is None:
259                        # improper caps in stanza, ignoring
260                        contact.caps_node = None
261                        contact.caps_hash = None
262                        contact.hash_method = None
263                        return
264
265                # start disco query...
266                gajim.capscache.preload(self, jid, node, hash_method, hash_)
267
268                # overwriting old data
269                contact.caps_node = node
270                contact.caps_hash_method = hash_method
271                contact.caps_hash = hash_
272                if pm_ctrl:
273                        pm_ctrl.update_contact()
274
275        def _capsDiscoCB(self, jid, node, identities, features, dataforms):
276                contact = gajim.contacts.get_contact_from_full_jid(self.name, jid)
277                if not contact:
278                        room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
279                        contact = gajim.contacts.get_gc_contact(self.name, room_jid, nick)
280                        if contact is None:
281                                return
282                if not contact.caps_node:
283                        return # we didn't asked for that?
284                if contact.caps_hash_method != 'old':
285                        computed_hash = helpers.compute_caps_hash(identities, features,
286                                dataforms=dataforms, hash_method=contact.caps_hash_method)
287                        if computed_hash != contact.caps_hash:
288                                # wrong hash, forget it
289                                contact.caps_node = ''
290                                contact.caps_hash_method = ''
291                                contact.caps_hash = ''
292                                return
293                        # if we don't have this info already...
294                        caps = gajim.capscache[(contact.caps_hash_method, contact.caps_hash)]
295                else:
296                        # if we don't have this info already...
297                        caps = gajim.capscache[(contact.caps_hash_method, contact.caps_node + \
298                                '#' + contact.caps_hash)]
299                if caps.queried == 2:
300                        return
301
302                caps.update(identities, features)
303
304# vim: se ts=3:
Note: See TracBrowser for help on using the browser.