comparison sat/memory/disco.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents 073f386a191a
children
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
85 85
86 def __contains__(self, hash_): 86 def __contains__(self, hash_):
87 return self.hashes.__contains__(hash_) 87 return self.hashes.__contains__(hash_)
88 88
89 def load(self): 89 def load(self):
90 def fillHashes(hashes): 90 def fill_hashes(hashes):
91 for hash_, xml in hashes.items(): 91 for hash_, xml in hashes.items():
92 element = xml_tools.ElementParser()(xml) 92 element = xml_tools.ElementParser()(xml)
93 disco_info = disco.DiscoInfo.fromElement(element) 93 disco_info = disco.DiscoInfo.fromElement(element)
94 for ext_form in disco_info.extensions.values(): 94 for ext_form in disco_info.extensions.values():
95 # wokkel doesn't call typeCheck on reception, so we do it here 95 # wokkel doesn't call typeCheck on reception, so we do it here
104 self.hashes[hash_] = disco_info 104 self.hashes[hash_] = disco_info
105 105
106 log.info("Disco hashes loaded") 106 log.info("Disco hashes loaded")
107 107
108 d = self.persistent.load() 108 d = self.persistent.load()
109 d.addCallback(fillHashes) 109 d.addCallback(fill_hashes)
110 return d 110 return d
111 111
112 112
113 class Discovery(object): 113 class Discovery(object):
114 """ Manage capabilities of entities """ 114 """ Manage capabilities of entities """
129 @param feature: feature namespace 129 @param feature: feature namespace
130 @param jid_: jid of the target, or None for profile's server 130 @param jid_: jid of the target, or None for profile's server
131 @param node(unicode): optional node to use for disco request 131 @param node(unicode): optional node to use for disco request
132 @return: a Deferred which fire a boolean (True if feature is available) 132 @return: a Deferred which fire a boolean (True if feature is available)
133 """ 133 """
134 disco_infos = yield self.getInfos(client, jid_, node) 134 disco_infos = yield self.get_infos(client, jid_, node)
135 defer.returnValue(feature in disco_infos.features) 135 defer.returnValue(feature in disco_infos.features)
136 136
137 @defer.inlineCallbacks 137 @defer.inlineCallbacks
138 def checkFeature(self, client, feature, jid_=None, node=""): 138 def check_feature(self, client, feature, jid_=None, node=""):
139 """Like hasFeature, but raise an exception is feature is not Found 139 """Like hasFeature, but raise an exception is feature is not Found
140 140
141 @param feature: feature namespace 141 @param feature: feature namespace
142 @param jid_: jid of the target, or None for profile's server 142 @param jid_: jid of the target, or None for profile's server
143 @param node(unicode): optional node to use for disco request 143 @param node(unicode): optional node to use for disco request
144 144
145 @raise: exceptions.FeatureNotFound 145 @raise: exceptions.FeatureNotFound
146 """ 146 """
147 disco_infos = yield self.getInfos(client, jid_, node) 147 disco_infos = yield self.get_infos(client, jid_, node)
148 if not feature in disco_infos.features: 148 if not feature in disco_infos.features:
149 raise failure.Failure(exceptions.FeatureNotFound()) 149 raise failure.Failure(exceptions.FeatureNotFound())
150 150
151 @defer.inlineCallbacks 151 @defer.inlineCallbacks
152 def checkFeatures(self, client, features, jid_=None, identity=None, node=""): 152 def check_features(self, client, features, jid_=None, identity=None, node=""):
153 """Like checkFeature, but check several features at once, and check also identity 153 """Like check_feature, but check several features at once, and check also identity
154 154
155 @param features(iterable[unicode]): features to check 155 @param features(iterable[unicode]): features to check
156 @param jid_(jid.JID): jid of the target, or None for profile's server 156 @param jid_(jid.JID): jid of the target, or None for profile's server
157 @param node(unicode): optional node to use for disco request 157 @param node(unicode): optional node to use for disco request
158 @param identity(None, tuple(unicode, unicode): if not None, the entity must have an identity with this (category, type) tuple 158 @param identity(None, tuple(unicode, unicode): if not None, the entity must have an identity with this (category, type) tuple
159 159
160 @raise: exceptions.FeatureNotFound 160 @raise: exceptions.FeatureNotFound
161 """ 161 """
162 disco_infos = yield self.getInfos(client, jid_, node) 162 disco_infos = yield self.get_infos(client, jid_, node)
163 if not set(features).issubset(disco_infos.features): 163 if not set(features).issubset(disco_infos.features):
164 raise failure.Failure(exceptions.FeatureNotFound()) 164 raise failure.Failure(exceptions.FeatureNotFound())
165 165
166 if identity is not None and identity not in disco_infos.identities: 166 if identity is not None and identity not in disco_infos.identities:
167 raise failure.Failure(exceptions.FeatureNotFound()) 167 raise failure.Failure(exceptions.FeatureNotFound())
168 168
169 async def hasIdentity( 169 async def has_identity(
170 self, 170 self,
171 client: SatXMPPEntity, 171 client: SatXMPPEntity,
172 category: str, 172 category: str,
173 type_: str, 173 type_: str,
174 jid_: Optional[jid.JID] = None, 174 jid_: Optional[jid.JID] = None,
180 @param type_: identity type 180 @param type_: identity type
181 @param jid_: jid of the target, or None for profile's server 181 @param jid_: jid of the target, or None for profile's server
182 @param node(unicode): optional node to use for disco request 182 @param node(unicode): optional node to use for disco request
183 @return: True if the entity has the given identity 183 @return: True if the entity has the given identity
184 """ 184 """
185 disco_infos = await self.getInfos(client, jid_, node) 185 disco_infos = await self.get_infos(client, jid_, node)
186 return (category, type_) in disco_infos.identities 186 return (category, type_) in disco_infos.identities
187 187
188 def getInfos(self, client, jid_=None, node="", use_cache=True): 188 def get_infos(self, client, jid_=None, node="", use_cache=True):
189 """get disco infos from jid_, filling capability hash if needed 189 """get disco infos from jid_, filling capability hash if needed
190 190
191 @param jid_: jid of the target, or None for profile's server 191 @param jid_: jid of the target, or None for profile's server
192 @param node(unicode): optional node to use for disco request 192 @param node(unicode): optional node to use for disco request
193 @param use_cache(bool): if True, use cached data if available 193 @param use_cache(bool): if True, use cached data if available
197 jid_ = jid.JID(client.jid.host) 197 jid_ = jid.JID(client.jid.host)
198 try: 198 try:
199 if not use_cache: 199 if not use_cache:
200 # we ignore cache, so we pretend we haven't found it 200 # we ignore cache, so we pretend we haven't found it
201 raise KeyError 201 raise KeyError
202 cap_hash = self.host.memory.getEntityData( 202 cap_hash = self.host.memory.entity_data_get(
203 client, jid_, [C.ENTITY_CAP_HASH] 203 client, jid_, [C.ENTITY_CAP_HASH]
204 )[C.ENTITY_CAP_HASH] 204 )[C.ENTITY_CAP_HASH]
205 except (KeyError, exceptions.UnknownEntityError): 205 except (KeyError, exceptions.UnknownEntityError):
206 # capability hash is not available, we'll compute one 206 # capability hash is not available, we'll compute one
207 def infosCb(disco_infos): 207 def infos_cb(disco_infos):
208 cap_hash = self.generateHash(disco_infos) 208 cap_hash = self.generate_hash(disco_infos)
209 for ext_form in disco_infos.extensions.values(): 209 for ext_form in disco_infos.extensions.values():
210 # wokkel doesn't call typeCheck on reception, so we do it here 210 # wokkel doesn't call typeCheck on reception, so we do it here
211 # to avoid ending up with incorrect types. We have to do it after 211 # to avoid ending up with incorrect types. We have to do it after
212 # the hash has been generated (str value is needed to compute the 212 # the hash has been generated (str value is needed to compute the
213 # hash) 213 # hash)
214 ext_form.typeCheck() 214 ext_form.typeCheck()
215 self.hashes[cap_hash] = disco_infos 215 self.hashes[cap_hash] = disco_infos
216 self.host.memory.updateEntityData( 216 self.host.memory.update_entity_data(
217 client, jid_, C.ENTITY_CAP_HASH, cap_hash 217 client, jid_, C.ENTITY_CAP_HASH, cap_hash
218 ) 218 )
219 return disco_infos 219 return disco_infos
220 220
221 def infosEb(fail): 221 def infos_eb(fail):
222 if fail.check(defer.CancelledError): 222 if fail.check(defer.CancelledError):
223 reason = "request time-out" 223 reason = "request time-out"
224 fail = failure.Failure(exceptions.TimeOutError(str(fail.value))) 224 fail = failure.Failure(exceptions.TimeOutError(str(fail.value)))
225 else: 225 else:
226 try: 226 try:
234 ) 234 )
235 ) 235 )
236 236
237 # XXX we set empty disco in cache, to avoid getting an error or waiting 237 # XXX we set empty disco in cache, to avoid getting an error or waiting
238 # for a timeout again the next time 238 # for a timeout again the next time
239 self.host.memory.updateEntityData( 239 self.host.memory.update_entity_data(
240 client, jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR 240 client, jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR
241 ) 241 )
242 raise fail 242 raise fail
243 243
244 d = client.disco.requestInfo(jid_, nodeIdentifier=node) 244 d = client.disco.requestInfo(jid_, nodeIdentifier=node)
245 d.addCallback(infosCb) 245 d.addCallback(infos_cb)
246 d.addErrback(infosEb) 246 d.addErrback(infos_eb)
247 return d 247 return d
248 else: 248 else:
249 disco_infos = self.hashes[cap_hash] 249 disco_infos = self.hashes[cap_hash]
250 return defer.succeed(disco_infos) 250 return defer.succeed(disco_infos)
251 251
252 @defer.inlineCallbacks 252 @defer.inlineCallbacks
253 def getItems(self, client, jid_=None, node="", use_cache=True): 253 def get_items(self, client, jid_=None, node="", use_cache=True):
254 """get disco items from jid_, cache them for our own server 254 """get disco items from jid_, cache them for our own server
255 255
256 @param jid_(jid.JID): jid of the target, or None for profile's server 256 @param jid_(jid.JID): jid of the target, or None for profile's server
257 @param node(unicode): optional node to use for disco request 257 @param node(unicode): optional node to use for disco request
258 @param use_cache(bool): if True, use cached data if available 258 @param use_cache(bool): if True, use cached data if available
262 jid_ = client.server_jid 262 jid_ = client.server_jid
263 263
264 if jid_ == client.server_jid and not node: 264 if jid_ == client.server_jid and not node:
265 # we cache items only for our own server and if node is not set 265 # we cache items only for our own server and if node is not set
266 try: 266 try:
267 items = self.host.memory.getEntityData( 267 items = self.host.memory.entity_data_get(
268 client, jid_, ["DISCO_ITEMS"] 268 client, jid_, ["DISCO_ITEMS"]
269 )["DISCO_ITEMS"] 269 )["DISCO_ITEMS"]
270 log.debug("[%s] disco items are in cache" % jid_.full()) 270 log.debug("[%s] disco items are in cache" % jid_.full())
271 if not use_cache: 271 if not use_cache:
272 # we ignore cache, so we pretend we haven't found it 272 # we ignore cache, so we pretend we haven't found it
273 raise KeyError 273 raise KeyError
274 except (KeyError, exceptions.UnknownEntityError): 274 except (KeyError, exceptions.UnknownEntityError):
275 log.debug("Caching [%s] disco items" % jid_.full()) 275 log.debug("Caching [%s] disco items" % jid_.full())
276 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) 276 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
277 self.host.memory.updateEntityData( 277 self.host.memory.update_entity_data(
278 client, jid_, "DISCO_ITEMS", items 278 client, jid_, "DISCO_ITEMS", items
279 ) 279 )
280 else: 280 else:
281 try: 281 try:
282 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) 282 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
288 ) 288 )
289 items = disco.DiscoItems() 289 items = disco.DiscoItems()
290 290
291 defer.returnValue(items) 291 defer.returnValue(items)
292 292
293 def _infosEb(self, failure_, entity_jid): 293 def _infos_eb(self, failure_, entity_jid):
294 failure_.trap(StanzaError) 294 failure_.trap(StanzaError)
295 log.warning( 295 log.warning(
296 _("Error while requesting [%(jid)s]: %(error)s") 296 _("Error while requesting [%(jid)s]: %(error)s")
297 % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()} 297 % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()}
298 ) 298 )
299 299
300 def findServiceEntity(self, client, category, type_, jid_=None): 300 def find_service_entity(self, client, category, type_, jid_=None):
301 """Helper method to find first available entity from findServiceEntities 301 """Helper method to find first available entity from find_service_entities
302 302
303 args are the same as for [findServiceEntities] 303 args are the same as for [find_service_entities]
304 @return (jid.JID, None): found entity 304 @return (jid.JID, None): found entity
305 """ 305 """
306 d = self.host.findServiceEntities(client, category, type_) 306 d = self.host.find_service_entities(client, category, type_)
307 d.addCallback(lambda entities: entities.pop() if entities else None) 307 d.addCallback(lambda entities: entities.pop() if entities else None)
308 return d 308 return d
309 309
310 def findServiceEntities(self, client, category, type_, jid_=None): 310 def find_service_entities(self, client, category, type_, jid_=None):
311 """Return all available items of an entity which correspond to (category, type_) 311 """Return all available items of an entity which correspond to (category, type_)
312 312
313 @param category: identity's category 313 @param category: identity's category
314 @param type_: identitiy's type 314 @param type_: identitiy's type
315 @param jid_: the jid of the target server (None for profile's server) 315 @param jid_: the jid of the target server (None for profile's server)
316 @return: a set of found entities 316 @return: a set of found entities
317 @raise defer.CancelledError: the request timed out 317 @raise defer.CancelledError: the request timed out
318 """ 318 """
319 found_entities = set() 319 found_entities = set()
320 320
321 def infosCb(infos, entity_jid): 321 def infos_cb(infos, entity_jid):
322 if (category, type_) in infos.identities: 322 if (category, type_) in infos.identities:
323 found_entities.add(entity_jid) 323 found_entities.add(entity_jid)
324 324
325 def gotItems(items): 325 def got_items(items):
326 defers_list = [] 326 defers_list = []
327 for item in items: 327 for item in items:
328 info_d = self.getInfos(client, item.entity) 328 info_d = self.get_infos(client, item.entity)
329 info_d.addCallbacks( 329 info_d.addCallbacks(
330 infosCb, self._infosEb, [item.entity], None, [item.entity] 330 infos_cb, self._infos_eb, [item.entity], None, [item.entity]
331 ) 331 )
332 defers_list.append(info_d) 332 defers_list.append(info_d)
333 return defer.DeferredList(defers_list) 333 return defer.DeferredList(defers_list)
334 334
335 d = self.getItems(client, jid_) 335 d = self.get_items(client, jid_)
336 d.addCallback(gotItems) 336 d.addCallback(got_items)
337 d.addCallback(lambda __: found_entities) 337 d.addCallback(lambda __: found_entities)
338 reactor.callLater( 338 reactor.callLater(
339 TIMEOUT, d.cancel 339 TIMEOUT, d.cancel
340 ) # FIXME: one bad service make a general timeout 340 ) # FIXME: one bad service make a general timeout
341 return d 341 return d
342 342
343 def findFeaturesSet(self, client, features, identity=None, jid_=None): 343 def find_features_set(self, client, features, identity=None, jid_=None):
344 """Return entities (including jid_ and its items) offering features 344 """Return entities (including jid_ and its items) offering features
345 345
346 @param features: iterable of features which must be present 346 @param features: iterable of features which must be present
347 @param identity(None, tuple(unicode, unicode)): if not None, accept only this 347 @param identity(None, tuple(unicode, unicode)): if not None, accept only this
348 (category/type) identity 348 (category/type) identity
353 if jid_ is None: 353 if jid_ is None:
354 jid_ = jid.JID(client.jid.host) 354 jid_ = jid.JID(client.jid.host)
355 features = set(features) 355 features = set(features)
356 found_entities = set() 356 found_entities = set()
357 357
358 def infosCb(infos, entity): 358 def infos_cb(infos, entity):
359 if entity is None: 359 if entity is None:
360 log.warning(_("received an item without jid")) 360 log.warning(_("received an item without jid"))
361 return 361 return
362 if identity is not None and identity not in infos.identities: 362 if identity is not None and identity not in infos.identities:
363 return 363 return
364 if features.issubset(infos.features): 364 if features.issubset(infos.features):
365 found_entities.add(entity) 365 found_entities.add(entity)
366 366
367 def gotItems(items): 367 def got_items(items):
368 defer_list = [] 368 defer_list = []
369 for entity in [jid_] + [item.entity for item in items]: 369 for entity in [jid_] + [item.entity for item in items]:
370 infos_d = self.getInfos(client, entity) 370 infos_d = self.get_infos(client, entity)
371 infos_d.addCallbacks(infosCb, self._infosEb, [entity], None, [entity]) 371 infos_d.addCallbacks(infos_cb, self._infos_eb, [entity], None, [entity])
372 defer_list.append(infos_d) 372 defer_list.append(infos_d)
373 return defer.DeferredList(defer_list) 373 return defer.DeferredList(defer_list)
374 374
375 d = self.getItems(client, jid_) 375 d = self.get_items(client, jid_)
376 d.addCallback(gotItems) 376 d.addCallback(got_items)
377 d.addCallback(lambda __: found_entities) 377 d.addCallback(lambda __: found_entities)
378 reactor.callLater( 378 reactor.callLater(
379 TIMEOUT, d.cancel 379 TIMEOUT, d.cancel
380 ) # FIXME: one bad service make a general timeout 380 ) # FIXME: one bad service make a general timeout
381 return d 381 return d
382 382
383 def generateHash(self, services): 383 def generate_hash(self, services):
384 """ Generate a unique hash for given service 384 """ Generate a unique hash for given service
385 385
386 hash algorithm is the one described in XEP-0115 386 hash algorithm is the one described in XEP-0115
387 @param services: iterable of disco.DiscoIdentity/disco.DiscoFeature, as returned by discoHandler.info 387 @param services: iterable of disco.DiscoIdentity/disco.DiscoFeature, as returned by discoHandler.info
388 388
431 cap_hash = b64encode(sha1(b"".join(s)).digest()).decode('utf-8') 431 cap_hash = b64encode(sha1(b"".join(s)).digest()).decode('utf-8')
432 log.debug(_("Capability hash generated: [{cap_hash}]").format(cap_hash=cap_hash)) 432 log.debug(_("Capability hash generated: [{cap_hash}]").format(cap_hash=cap_hash))
433 return cap_hash 433 return cap_hash
434 434
435 @defer.inlineCallbacks 435 @defer.inlineCallbacks
436 def _discoInfos( 436 def _disco_infos(
437 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE 437 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE
438 ): 438 ):
439 """Discovery method for the bridge 439 """Discovery method for the bridge
440 @param entity_jid_s: entity we want to discover 440 @param entity_jid_s: entity we want to discover
441 @param use_cache(bool): if True, use cached data if available 441 @param use_cache(bool): if True, use cached data if available
442 @param node(unicode): optional node to use 442 @param node(unicode): optional node to use
443 443
444 @return: list of tuples 444 @return: list of tuples
445 """ 445 """
446 client = self.host.getClient(profile_key) 446 client = self.host.get_client(profile_key)
447 entity = jid.JID(entity_jid_s) 447 entity = jid.JID(entity_jid_s)
448 disco_infos = yield self.getInfos(client, entity, node, use_cache) 448 disco_infos = yield self.get_infos(client, entity, node, use_cache)
449 extensions = {} 449 extensions = {}
450 # FIXME: should extensions be serialised using tools.common.data_format? 450 # FIXME: should extensions be serialised using tools.common.data_format?
451 for form_type, form in list(disco_infos.extensions.items()): 451 for form_type, form in list(disco_infos.extensions.items()):
452 fields = [] 452 fields = []
453 for field in form.fieldList: 453 for field in form.fieldList:
457 if value is not None: 457 if value is not None:
458 data[attr] = value 458 data[attr] = value
459 459
460 values = [field.value] if field.value is not None else field.values 460 values = [field.value] if field.value is not None else field.values
461 if field.fieldType == "boolean": 461 if field.fieldType == "boolean":
462 values = [C.boolConst(v) for v in values] 462 values = [C.bool_const(v) for v in values]
463 fields.append((data, values)) 463 fields.append((data, values))
464 464
465 extensions[form_type or ""] = fields 465 extensions[form_type or ""] = fields
466 466
467 defer.returnValue(( 467 defer.returnValue((
481 log.warning(_("invalid item (no jid)")) 481 log.warning(_("invalid item (no jid)"))
482 continue 482 continue
483 yield (item.entity.full(), item.nodeIdentifier or "", item.name or "") 483 yield (item.entity.full(), item.nodeIdentifier or "", item.name or "")
484 484
485 @defer.inlineCallbacks 485 @defer.inlineCallbacks
486 def _discoItems( 486 def _disco_items(
487 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE 487 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE
488 ): 488 ):
489 """ Discovery method for the bridge 489 """ Discovery method for the bridge
490 490
491 @param entity_jid_s: entity we want to discover 491 @param entity_jid_s: entity we want to discover
492 @param node(unicode): optional node to use 492 @param node(unicode): optional node to use
493 @param use_cache(bool): if True, use cached data if available 493 @param use_cache(bool): if True, use cached data if available
494 @return: list of tuples""" 494 @return: list of tuples"""
495 client = self.host.getClient(profile_key) 495 client = self.host.get_client(profile_key)
496 entity = jid.JID(entity_jid_s) 496 entity = jid.JID(entity_jid_s)
497 disco_items = yield self.getItems(client, entity, node, use_cache) 497 disco_items = yield self.get_items(client, entity, node, use_cache)
498 ret = list(self.items2tuples(disco_items)) 498 ret = list(self.items2tuples(disco_items))
499 defer.returnValue(ret) 499 defer.returnValue(ret)