comparison src/memory/disco.py @ 2148:a543eda2c923

core (memory/disco): getInfos now handle node + use client instead of profile in many methods
author Goffi <goffi@goffi.org>
date Sun, 12 Feb 2017 20:43:20 +0100
parents efe31f0881fb
children 91347fe95384
comparison
equal deleted inserted replaced
2147:bca699faf416 2148:a543eda2c923
102 """Load persistent hashes""" 102 """Load persistent hashes"""
103 self.hashes = HashManager(persistent.PersistentDict("disco")) 103 self.hashes = HashManager(persistent.PersistentDict("disco"))
104 return self.hashes.load() 104 return self.hashes.load()
105 105
106 @defer.inlineCallbacks 106 @defer.inlineCallbacks
107 def hasFeature(self, feature, jid_=None, profile=C.PROF_KEY_NONE): 107 def hasFeature(self, client, feature, jid_=None, node=u''):
108 """Tell if an entity has the required feature 108 """Tell if an entity has the required feature
109 109
110 @param feature: feature namespace 110 @param feature: feature namespace
111 @param jid_: jid of the target, or None for profile's server 111 @param jid_: jid of the target, or None for profile's server
112 @param profile: %(doc_profile)s 112 @param node(unicode): optional node to use for disco request
113 @return: a Deferred which fire a boolean (True if feature is available) 113 @return: a Deferred which fire a boolean (True if feature is available)
114 """ 114 """
115 disco_infos = yield self.getInfos(jid_, profile) 115 disco_infos = yield self.getInfos(client, jid_, node)
116 defer.returnValue(feature in disco_infos.features) 116 defer.returnValue(feature in disco_infos.features)
117 117
118 @defer.inlineCallbacks 118 @defer.inlineCallbacks
119 def checkFeature(self, feature, jid_=None, profile=C.PROF_KEY_NONE): 119 def checkFeature(self, client, feature, jid_=None, node=u''):
120 """Like hasFeature, but raise an exception is feature is not Found 120 """Like hasFeature, but raise an exception is feature is not Found
121 121
122 @param feature: feature namespace 122 @param feature: feature namespace
123 @param jid_: jid of the target, or None for profile's server 123 @param jid_: jid of the target, or None for profile's server
124 @param profile: %(doc_profile)s 124 @param node(unicode): optional node to use for disco request
125 125
126 @raise: exceptions.FeatureNotFound 126 @raise: exceptions.FeatureNotFound
127 """ 127 """
128 disco_infos = yield self.getInfos(jid_, profile) 128 disco_infos = yield self.getInfos(client, jid_, node)
129 if not feature in disco_infos.features: 129 if not feature in disco_infos.features:
130 raise failure.Failure(exceptions.FeatureNotFound) 130 raise failure.Failure(exceptions.FeatureNotFound)
131 131
132 @defer.inlineCallbacks 132 @defer.inlineCallbacks
133 def checkFeatures(self, features, jid_=None, identity=None, profile=C.PROF_KEY_NONE): 133 def checkFeatures(self, client, features, jid_=None, identity=None, node=u''):
134 """Like checkFeature, but check several features at once, and check also identity 134 """Like checkFeature, but check several features at once, and check also identity
135 135
136 @param features(iterable[unicode]): features to check 136 @param features(iterable[unicode]): features to check
137 @param jid_(jid.JID): jid of the target, or None for profile's server 137 @param jid_(jid.JID): jid of the target, or None for profile's server
138 @param node(unicode): optional node to use for disco request
138 @param identity(None, tuple(unicode, unicode): if not None, the entity must have an identity with this (category, type) tuple 139 @param identity(None, tuple(unicode, unicode): if not None, the entity must have an identity with this (category, type) tuple
139 @param profile: %(doc_profile)s
140 140
141 @raise: exceptions.FeatureNotFound 141 @raise: exceptions.FeatureNotFound
142 """ 142 """
143 disco_infos = yield self.getInfos(jid_, profile) 143 disco_infos = yield self.getInfos(client, jid_, node)
144 if not set(features).issubset(disco_infos.features): 144 if not set(features).issubset(disco_infos.features):
145 raise failure.Failure(exceptions.FeatureNotFound()) 145 raise failure.Failure(exceptions.FeatureNotFound())
146 146
147 if identity is not None and identity not in disco_infos.identities: 147 if identity is not None and identity not in disco_infos.identities:
148 raise failure.Failure(exceptions.FeatureNotFound()) 148 raise failure.Failure(exceptions.FeatureNotFound())
149 149
150 def getInfos(self, jid_=None, profile=C.PROF_KEY_NONE): 150 def getInfos(self, client, jid_=None, node=u''):
151 """get disco infos from jid_, filling capability hash if needed 151 """get disco infos from jid_, filling capability hash if needed
152 152
153 @param jid_: jid of the target, or None for profile's server 153 @param jid_: jid of the target, or None for profile's server
154 @param profile: %(doc_profile)s 154 @param node(unicode): optional node to use for disco request
155 @return: a Deferred which fire disco.DiscoInfo 155 @return: a Deferred which fire disco.DiscoInfo
156 """ 156 """
157 client = self.host.getClient(profile)
158 if jid_ is None: 157 if jid_ is None:
159 jid_ = jid.JID(client.jid.host) 158 jid_ = jid.JID(client.jid.host)
160 try: 159 try:
161 cap_hash = self.host.memory.getEntityData(jid_, [C.ENTITY_CAP_HASH], client.profile)[C.ENTITY_CAP_HASH] 160 cap_hash = self.host.memory.getEntityData(jid_, [C.ENTITY_CAP_HASH], client.profile)[C.ENTITY_CAP_HASH]
162 except (KeyError, exceptions.UnknownEntityError): 161 except (KeyError, exceptions.UnknownEntityError):
176 reason = unicode(fail) 175 reason = unicode(fail)
177 log.warning(u"Error while requesting disco infos from {jid}: {reason}".format(jid=jid_.full(), reason=reason)) 176 log.warning(u"Error while requesting disco infos from {jid}: {reason}".format(jid=jid_.full(), reason=reason))
178 self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile) 177 self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile)
179 disco_infos = self.hashes[CAP_HASH_ERROR] 178 disco_infos = self.hashes[CAP_HASH_ERROR]
180 return disco_infos 179 return disco_infos
181 d = client.disco.requestInfo(jid_) 180 d = client.disco.requestInfo(jid_, nodeIdentifier=node)
182 d.addCallback(infosCb) 181 d.addCallback(infosCb)
183 d.addErrback(infosEb) 182 d.addErrback(infosEb)
184 return d 183 return d
185 else: 184 else:
186 disco_infos = self.hashes[cap_hash] 185 disco_infos = self.hashes[cap_hash]
187 return defer.succeed(disco_infos) 186 return defer.succeed(disco_infos)
188 187
189 @defer.inlineCallbacks 188 @defer.inlineCallbacks
190 def getItems(self, jid_=None, nodeIdentifier='', profile=C.PROF_KEY_NONE): 189 def getItems(self, client, jid_=None, node=u''):
191 """get disco items from jid_, cache them for our own server 190 """get disco items from jid_, cache them for our own server
192 191
193 @param jid_ (jid.JID): jid of the target, or None for profile's server 192 @param jid_(jid.JID): jid of the target, or None for profile's server
194 @param nodeIdentifier (str): optional node identifier 193 @param node(unicode): optional node to use for disco request
195 @param profile: %(doc_profile)s
196 @return: a Deferred which fire disco.DiscoItems 194 @return: a Deferred which fire disco.DiscoItems
197 """ 195 """
198 client = self.host.getClient(profile)
199 if jid_ is None: 196 if jid_ is None:
200 jid_ = jid.JID(client.jid.host) 197 jid_ = jid.JID(client.jid.host)
201 # we cache items only for our own server 198 # we cache items only for our own server
202 try: 199 try:
203 items = self.host.memory.getEntityData(jid_, ["DISCO_ITEMS"], client.profile)["DISCO_ITEMS"] 200 items = self.host.memory.getEntityData(jid_, ["DISCO_ITEMS"], client.profile)["DISCO_ITEMS"]
204 log.debug(u"[%s] disco items are in cache" % jid_.full()) 201 log.debug(u"[%s] disco items are in cache" % jid_.full())
205 except (KeyError, exceptions.UnknownEntityError): 202 except (KeyError, exceptions.UnknownEntityError):
206 log.debug(u"Caching [%s] disco items" % jid_.full()) 203 log.debug(u"Caching [%s] disco items" % jid_.full())
207 items = yield client.disco.requestItems(jid_, nodeIdentifier) 204 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
208 self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile) 205 self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile)
209 else: 206 else:
210 try: 207 try:
211 items = yield client.disco.requestItems(jid_, nodeIdentifier) 208 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
212 except StanzaError as e: 209 except StanzaError as e:
213 log.warning(u"Error while requesting items for {jid}: {reason}" 210 log.warning(u"Error while requesting items for {jid}: {reason}"
214 .format(jid=jid_.full(), reason=e.condition)) 211 .format(jid=jid_.full(), reason=e.condition))
215 items = disco.DiscoItems() 212 items = disco.DiscoItems()
216 213
220 def _infosEb(self, failure_, entity_jid): 217 def _infosEb(self, failure_, entity_jid):
221 failure_.trap(StanzaError) 218 failure_.trap(StanzaError)
222 log.warning(_(u"Error while requesting [%(jid)s]: %(error)s") % {'jid': entity_jid.full(), 219 log.warning(_(u"Error while requesting [%(jid)s]: %(error)s") % {'jid': entity_jid.full(),
223 'error': failure_.getErrorMessage()}) 220 'error': failure_.getErrorMessage()})
224 221
225 def findServiceEntities(self, category, type_, jid_=None, profile=C.PROF_KEY_NONE): 222 def findServiceEntities(self, client, category, type_, jid_=None):
226 """Return all available items of an entity which correspond to (category, type_) 223 """Return all available items of an entity which correspond to (category, type_)
227 224
228 @param category: identity's category 225 @param category: identity's category
229 @param type_: identitiy's type 226 @param type_: identitiy's type
230 @param jid_: the jid of the target server (None for profile's server) 227 @param jid_: the jid of the target server (None for profile's server)
231 @param profile: %(doc_profile)s
232 @return: a set of found entities 228 @return: a set of found entities
233 @raise defer.CancelledError: the request timed out 229 @raise defer.CancelledError: the request timed out
234 """ 230 """
235 found_entities = set() 231 found_entities = set()
236 232
239 found_entities.add(entity_jid) 235 found_entities.add(entity_jid)
240 236
241 def gotItems(items): 237 def gotItems(items):
242 defers_list = [] 238 defers_list = []
243 for item in items: 239 for item in items:
244 info_d = self.getInfos(item.entity, profile) 240 info_d = self.getInfos(client, item.entity)
245 info_d.addCallbacks(infosCb, self._infosEb, [item.entity], None, [item.entity]) 241 info_d.addCallbacks(infosCb, self._infosEb, [item.entity], None, [item.entity])
246 defers_list.append(info_d) 242 defers_list.append(info_d)
247 return defer.DeferredList(defers_list) 243 return defer.DeferredList(defers_list)
248 244
249 d = self.getItems(jid_, profile=profile) 245 d = self.getItems(client, jid_)
250 d.addCallback(gotItems) 246 d.addCallback(gotItems)
251 d.addCallback(lambda dummy: found_entities) 247 d.addCallback(lambda dummy: found_entities)
252 reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout 248 reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout
253 return d 249 return d
254 250
255 def findFeaturesSet(self, features, identity=None, jid_=None, profile=C.PROF_KEY_NONE): 251 def findFeaturesSet(self, client, features, identity=None, jid_=None):
256 """Return entities (including jid_ and its items) offering features 252 """Return entities (including jid_ and its items) offering features
257 253
258 @param features: iterable of features which must be present 254 @param features: iterable of features which must be present
259 @param identity(None, tuple(unicode, unicode)): if not None, accept only this (category/type) identity 255 @param identity(None, tuple(unicode, unicode)): if not None, accept only this (category/type) identity
260 @param jid_: the jid of the target server (None for profile's server) 256 @param jid_: the jid of the target server (None for profile's server)
261 @param profile: %(doc_profile)s 257 @param profile: %(doc_profile)s
262 @return: a set of found entities 258 @return: a set of found entities
263 """ 259 """
264 client = self.host.getClient(profile)
265 if jid_ is None: 260 if jid_ is None:
266 jid_ = jid.JID(client.jid.host) 261 jid_ = jid.JID(client.jid.host)
267 features = set(features) 262 features = set(features)
268 found_entities = set() 263 found_entities = set()
269 264
274 found_entities.add(entity) 269 found_entities.add(entity)
275 270
276 def gotItems(items): 271 def gotItems(items):
277 defer_list = [] 272 defer_list = []
278 for entity in [jid_] + [item.entity for item in items]: 273 for entity in [jid_] + [item.entity for item in items]:
279 infos_d = self.getInfos(entity, profile) 274 infos_d = self.getInfos(client, entity)
280 infos_d.addCallbacks(infosCb, self._infosEb, [entity], None, [entity]) 275 infos_d.addCallbacks(infosCb, self._infosEb, [entity], None, [entity])
281 defer_list.append(infos_d) 276 defer_list.append(infos_d)
282 return defer.DeferredList(defer_list) 277 return defer.DeferredList(defer_list)
283 278
284 d = self.getItems(jid_, profile=profile) 279 d = self.getItems(client, jid_)
285 d.addCallback(gotItems) 280 d.addCallback(gotItems)
286 d.addCallback(lambda dummy: found_entities) 281 d.addCallback(lambda dummy: found_entities)
287 reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout 282 reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout
288 return d 283 return d
289 284
317 """ Discovery method for the bridge 312 """ Discovery method for the bridge
318 @param entity_jid_s: entity we want to discover 313 @param entity_jid_s: entity we want to discover
319 314
320 @return: list of tuples 315 @return: list of tuples
321 """ 316 """
322 profile = self.host.memory.getProfileName(profile_key) 317 client = self.host.getClient(profile_key)
323 entity = jid.JID(entity_jid_s) 318 entity = jid.JID(entity_jid_s)
324 disco_infos = yield self.getInfos(entity, profile) 319 disco_infos = yield self.getInfos(client, entity)
325 extensions = {} 320 extensions = {}
326 for form_type, form in disco_infos.extensions.items(): 321 for form_type, form in disco_infos.extensions.items():
327 fields = [] 322 fields = []
328 for field in form.fieldList: 323 for field in form.fieldList:
329 data = {'type': field.fieldType} 324 data = {'type': field.fieldType}
339 334
340 defer.returnValue((disco_infos.features, 335 defer.returnValue((disco_infos.features,
341 [(cat, type_, name or '') for (cat, type_), name in disco_infos.identities.items()], 336 [(cat, type_, name or '') for (cat, type_), name in disco_infos.identities.items()],
342 extensions)) 337 extensions))
343 338
339 def items2tuples(self, disco_items):
340 """convert disco items to tuple of strings
341
342 @param disco_items(iterable[disco.DiscoItem]): items
343 @return G(tuple[unicode,unicode,unicode]): serialised items
344 """
345 for item in disco_items:
346 if not item.entity:
347 log.warning(_(u"invalid item (no jid)"))
348 continue
349 yield (item.entity.full(), item.nodeIdentifier or '', item.name or '')
350
344 @defer.inlineCallbacks 351 @defer.inlineCallbacks
345 def _discoItems(self, entity_jid_s, profile_key=C.PROF_KEY_NONE): 352 def _discoItems(self, entity_jid_s, profile_key=C.PROF_KEY_NONE):
346 """ Discovery method for the bridge 353 """ Discovery method for the bridge
354
347 @param entity_jid_s: entity we want to discover 355 @param entity_jid_s: entity we want to discover
348
349 @return: list of tuples""" 356 @return: list of tuples"""
350 profile = self.host.memory.getProfileName(profile_key) 357 client = self.host.getClient(profile_key)
351 entity = jid.JID(entity_jid_s) 358 entity = jid.JID(entity_jid_s)
352 disco_items = yield self.getItems(entity, profile=profile) 359 disco_items = yield self.getItems(client, entity)
353 try: 360 ret = list(self.items2tuples(disco_items))
354 ret = [(item.entity.full(), item.nodeIdentifier or '', item.name or '') for item in disco_items]
355 except AttributeError:
356 log.error(u"error while getting items from {entity}, it may result from invalid item(s)".format(entity=entity.full()))
357 # second try by ignoring empty items
358 ret = [(item.entity.full(), item.nodeIdentifier or '', item.name or '') for item in disco_items if item.entity]
359
360 defer.returnValue(ret) 361 defer.returnValue(ret)