Mercurial > libervia-backend
comparison sat/memory/disco.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | b6abf8af87db |
children | fee60f17ebac |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 # SAT: a jabber client | 4 # SAT: a jabber client |
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) | 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
6 | 6 |
48 | 48 |
49 def __init__(self, identity, lang=None): | 49 def __init__(self, identity, lang=None): |
50 assert isinstance(identity, disco.DiscoIdentity) | 50 assert isinstance(identity, disco.DiscoIdentity) |
51 self.category = identity.category.encode("utf-8") | 51 self.category = identity.category.encode("utf-8") |
52 self.idType = identity.type.encode("utf-8") | 52 self.idType = identity.type.encode("utf-8") |
53 self.name = identity.name.encode("utf-8") if identity.name else "" | 53 self.name = identity.name.encode("utf-8") if identity.name else b"" |
54 self.lang = lang.encode("utf-8") if lang is not None else "" | 54 self.lang = lang.encode("utf-8") if lang is not None else b"" |
55 | 55 |
56 def __str__(self): | 56 def __bytes__(self): |
57 return "%s/%s/%s/%s" % (self.category, self.idType, self.lang, self.name) | 57 return b"%s/%s/%s/%s" % (self.category, self.idType, self.lang, self.name) |
58 | 58 |
59 | 59 |
60 class HashManager(object): | 60 class HashManager(object): |
61 """map object which manage hashes | 61 """map object which manage hashes |
62 | 62 |
72 def __getitem__(self, key): | 72 def __getitem__(self, key): |
73 return self.hashes[key] | 73 return self.hashes[key] |
74 | 74 |
75 def __setitem__(self, hash_, disco_info): | 75 def __setitem__(self, hash_, disco_info): |
76 if hash_ in self.hashes: | 76 if hash_ in self.hashes: |
77 log.debug(u"ignoring hash set: it is already known") | 77 log.debug("ignoring hash set: it is already known") |
78 return | 78 return |
79 self.hashes[hash_] = disco_info | 79 self.hashes[hash_] = disco_info |
80 self.persistent[hash_] = disco_info.toElement().toXml() | 80 self.persistent[hash_] = disco_info.toElement().toXml() |
81 | 81 |
82 def __contains__(self, hash_): | 82 def __contains__(self, hash_): |
83 return self.hashes.__contains__(hash_) | 83 return self.hashes.__contains__(hash_) |
84 | 84 |
85 def load(self): | 85 def load(self): |
86 def fillHashes(hashes): | 86 def fillHashes(hashes): |
87 for hash_, xml in hashes.iteritems(): | 87 for hash_, xml in hashes.items(): |
88 element = xml_tools.ElementParser()(xml) | 88 element = xml_tools.ElementParser()(xml) |
89 disco_info = disco.DiscoInfo.fromElement(element) | 89 disco_info = disco.DiscoInfo.fromElement(element) |
90 if not disco_info.features and not disco_info.identities: | 90 if not disco_info.features and not disco_info.identities: |
91 log.warning( | 91 log.warning( |
92 _( | 92 _( |
93 u"no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}" | 93 "no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}" |
94 ).format(cap_hash=hash_, xml=xml) | 94 ).format(cap_hash=hash_, xml=xml) |
95 ) | 95 ) |
96 else: | 96 else: |
97 self.hashes[hash_] = disco_info | 97 self.hashes[hash_] = disco_info |
98 | 98 |
99 log.info(u"Disco hashes loaded") | 99 log.info("Disco hashes loaded") |
100 | 100 |
101 d = self.persistent.load() | 101 d = self.persistent.load() |
102 d.addCallback(fillHashes) | 102 d.addCallback(fillHashes) |
103 return d | 103 return d |
104 | 104 |
114 """Load persistent hashes""" | 114 """Load persistent hashes""" |
115 self.hashes = HashManager(persistent.PersistentDict("disco")) | 115 self.hashes = HashManager(persistent.PersistentDict("disco")) |
116 return self.hashes.load() | 116 return self.hashes.load() |
117 | 117 |
118 @defer.inlineCallbacks | 118 @defer.inlineCallbacks |
119 def hasFeature(self, client, feature, jid_=None, node=u""): | 119 def hasFeature(self, client, feature, jid_=None, node=""): |
120 """Tell if an entity has the required feature | 120 """Tell if an entity has the required feature |
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 node(unicode): optional node to use for disco request | 124 @param node(unicode): optional node to use for disco request |
126 """ | 126 """ |
127 disco_infos = yield self.getInfos(client, jid_, node) | 127 disco_infos = yield self.getInfos(client, jid_, node) |
128 defer.returnValue(feature in disco_infos.features) | 128 defer.returnValue(feature in disco_infos.features) |
129 | 129 |
130 @defer.inlineCallbacks | 130 @defer.inlineCallbacks |
131 def checkFeature(self, client, feature, jid_=None, node=u""): | 131 def checkFeature(self, client, feature, jid_=None, node=""): |
132 """Like hasFeature, but raise an exception is feature is not Found | 132 """Like hasFeature, but raise an exception is feature is not Found |
133 | 133 |
134 @param feature: feature namespace | 134 @param feature: feature namespace |
135 @param jid_: jid of the target, or None for profile's server | 135 @param jid_: jid of the target, or None for profile's server |
136 @param node(unicode): optional node to use for disco request | 136 @param node(unicode): optional node to use for disco request |
140 disco_infos = yield self.getInfos(client, jid_, node) | 140 disco_infos = yield self.getInfos(client, jid_, node) |
141 if not feature in disco_infos.features: | 141 if not feature in disco_infos.features: |
142 raise failure.Failure(exceptions.FeatureNotFound) | 142 raise failure.Failure(exceptions.FeatureNotFound) |
143 | 143 |
144 @defer.inlineCallbacks | 144 @defer.inlineCallbacks |
145 def checkFeatures(self, client, features, jid_=None, identity=None, node=u""): | 145 def checkFeatures(self, client, features, jid_=None, identity=None, node=""): |
146 """Like checkFeature, but check several features at once, and check also identity | 146 """Like checkFeature, but check several features at once, and check also identity |
147 | 147 |
148 @param features(iterable[unicode]): features to check | 148 @param features(iterable[unicode]): features to check |
149 @param jid_(jid.JID): jid of the target, or None for profile's server | 149 @param jid_(jid.JID): jid of the target, or None for profile's server |
150 @param node(unicode): optional node to use for disco request | 150 @param node(unicode): optional node to use for disco request |
157 raise failure.Failure(exceptions.FeatureNotFound()) | 157 raise failure.Failure(exceptions.FeatureNotFound()) |
158 | 158 |
159 if identity is not None and identity not in disco_infos.identities: | 159 if identity is not None and identity not in disco_infos.identities: |
160 raise failure.Failure(exceptions.FeatureNotFound()) | 160 raise failure.Failure(exceptions.FeatureNotFound()) |
161 | 161 |
162 def getInfos(self, client, jid_=None, node=u"", use_cache=True): | 162 def getInfos(self, client, jid_=None, node="", use_cache=True): |
163 """get disco infos from jid_, filling capability hash if needed | 163 """get disco infos from jid_, filling capability hash if needed |
164 | 164 |
165 @param jid_: jid of the target, or None for profile's server | 165 @param jid_: jid of the target, or None for profile's server |
166 @param node(unicode): optional node to use for disco request | 166 @param node(unicode): optional node to use for disco request |
167 @param use_cache(bool): if True, use cached data if available | 167 @param use_cache(bool): if True, use cached data if available |
186 ) | 186 ) |
187 return disco_infos | 187 return disco_infos |
188 | 188 |
189 def infosEb(fail): | 189 def infosEb(fail): |
190 if fail.check(defer.CancelledError): | 190 if fail.check(defer.CancelledError): |
191 reason = u"request time-out" | 191 reason = "request time-out" |
192 fail = failure.Failure(exceptions.TimeOutError(fail.message)) | 192 fail = failure.Failure(exceptions.TimeOutError(fail.message)) |
193 else: | 193 else: |
194 try: | 194 try: |
195 reason = unicode(fail.value) | 195 reason = str(fail.value) |
196 except AttributeError: | 196 except AttributeError: |
197 reason = unicode(fail) | 197 reason = str(fail) |
198 | 198 |
199 log.warning( | 199 log.warning( |
200 u"Error while requesting disco infos from {jid}: {reason}".format( | 200 "Error while requesting disco infos from {jid}: {reason}".format( |
201 jid=jid_.full(), reason=reason | 201 jid=jid_.full(), reason=reason |
202 ) | 202 ) |
203 ) | 203 ) |
204 | 204 |
205 # XXX we set empty disco in cache, to avoid getting an error or waiting | 205 # XXX we set empty disco in cache, to avoid getting an error or waiting |
216 else: | 216 else: |
217 disco_infos = self.hashes[cap_hash] | 217 disco_infos = self.hashes[cap_hash] |
218 return defer.succeed(disco_infos) | 218 return defer.succeed(disco_infos) |
219 | 219 |
220 @defer.inlineCallbacks | 220 @defer.inlineCallbacks |
221 def getItems(self, client, jid_=None, node=u"", use_cache=True): | 221 def getItems(self, client, jid_=None, node="", use_cache=True): |
222 """get disco items from jid_, cache them for our own server | 222 """get disco items from jid_, cache them for our own server |
223 | 223 |
224 @param jid_(jid.JID): jid of the target, or None for profile's server | 224 @param jid_(jid.JID): jid of the target, or None for profile's server |
225 @param node(unicode): optional node to use for disco request | 225 @param node(unicode): optional node to use for disco request |
226 @param use_cache(bool): if True, use cached data if available | 226 @param use_cache(bool): if True, use cached data if available |
234 # we cache items only for our own server and if node is not set | 234 # we cache items only for our own server and if node is not set |
235 try: | 235 try: |
236 items = self.host.memory.getEntityData( | 236 items = self.host.memory.getEntityData( |
237 jid_, ["DISCO_ITEMS"], client.profile | 237 jid_, ["DISCO_ITEMS"], client.profile |
238 )["DISCO_ITEMS"] | 238 )["DISCO_ITEMS"] |
239 log.debug(u"[%s] disco items are in cache" % jid_.full()) | 239 log.debug("[%s] disco items are in cache" % jid_.full()) |
240 if not use_cache: | 240 if not use_cache: |
241 # we ignore cache, so we pretend we haven't found it | 241 # we ignore cache, so we pretend we haven't found it |
242 raise KeyError | 242 raise KeyError |
243 except (KeyError, exceptions.UnknownEntityError): | 243 except (KeyError, exceptions.UnknownEntityError): |
244 log.debug(u"Caching [%s] disco items" % jid_.full()) | 244 log.debug("Caching [%s] disco items" % jid_.full()) |
245 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) | 245 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) |
246 self.host.memory.updateEntityData( | 246 self.host.memory.updateEntityData( |
247 jid_, "DISCO_ITEMS", items, profile_key=client.profile | 247 jid_, "DISCO_ITEMS", items, profile_key=client.profile |
248 ) | 248 ) |
249 else: | 249 else: |
250 try: | 250 try: |
251 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) | 251 items = yield client.disco.requestItems(jid_, nodeIdentifier=node) |
252 except StanzaError as e: | 252 except StanzaError as e: |
253 log.warning( | 253 log.warning( |
254 u"Error while requesting items for {jid}: {reason}".format( | 254 "Error while requesting items for {jid}: {reason}".format( |
255 jid=jid_.full(), reason=e.condition | 255 jid=jid_.full(), reason=e.condition |
256 ) | 256 ) |
257 ) | 257 ) |
258 items = disco.DiscoItems() | 258 items = disco.DiscoItems() |
259 | 259 |
260 defer.returnValue(items) | 260 defer.returnValue(items) |
261 | 261 |
262 def _infosEb(self, failure_, entity_jid): | 262 def _infosEb(self, failure_, entity_jid): |
263 failure_.trap(StanzaError) | 263 failure_.trap(StanzaError) |
264 log.warning( | 264 log.warning( |
265 _(u"Error while requesting [%(jid)s]: %(error)s") | 265 _("Error while requesting [%(jid)s]: %(error)s") |
266 % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()} | 266 % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()} |
267 ) | 267 ) |
268 | 268 |
269 def findServiceEntity(self, client, category, type_, jid_=None): | 269 def findServiceEntity(self, client, category, type_, jid_=None): |
270 """Helper method to find first available entity from findServiceEntities | 270 """Helper method to find first available entity from findServiceEntities |
324 features = set(features) | 324 features = set(features) |
325 found_entities = set() | 325 found_entities = set() |
326 | 326 |
327 def infosCb(infos, entity): | 327 def infosCb(infos, entity): |
328 if entity is None: | 328 if entity is None: |
329 log.warning(_(u"received an item without jid")) | 329 log.warning(_("received an item without jid")) |
330 return | 330 return |
331 if identity is not None and identity not in infos.identities: | 331 if identity is not None and identity not in infos.identities: |
332 return | 332 return |
333 if features.issubset(infos.features): | 333 if features.issubset(infos.features): |
334 found_entities.add(entity) | 334 found_entities.add(entity) |
365 ] # FIXME: lang must be managed here | 365 ] # FIXME: lang must be managed here |
366 byte_identities.sort(key=lambda i: i.lang) | 366 byte_identities.sort(key=lambda i: i.lang) |
367 byte_identities.sort(key=lambda i: i.idType) | 367 byte_identities.sort(key=lambda i: i.idType) |
368 byte_identities.sort(key=lambda i: i.category) | 368 byte_identities.sort(key=lambda i: i.category) |
369 for identity in byte_identities: | 369 for identity in byte_identities: |
370 s.append(str(identity)) | 370 s.append(bytes(identity)) |
371 s.append("<") | 371 s.append(b"<") |
372 # features | 372 # features |
373 byte_features = [ | 373 byte_features = [ |
374 service.encode("utf-8") | 374 service.encode("utf-8") |
375 for service in services | 375 for service in services |
376 if isinstance(service, disco.DiscoFeature) | 376 if isinstance(service, disco.DiscoFeature) |
377 ] | 377 ] |
378 byte_features.sort() # XXX: the default sort has the same behaviour as the requested RFC 4790 i;octet sort | 378 byte_features.sort() # XXX: the default sort has the same behaviour as the requested RFC 4790 i;octet sort |
379 for feature in byte_features: | 379 for feature in byte_features: |
380 s.append(feature) | 380 s.append(feature) |
381 s.append("<") | 381 s.append(b"<") |
382 | 382 |
383 # extensions | 383 # extensions |
384 ext = services.extensions.values() | 384 ext = list(services.extensions.values()) |
385 ext.sort(key=lambda f: f.formNamespace.encode('utf-8')) | 385 ext.sort(key=lambda f: f.formNamespace.encode('utf-8')) |
386 for extension in ext: | 386 for extension in ext: |
387 s.append(extension.formNamespace.encode('utf-8')) | 387 s.append(extension.formNamespace.encode('utf-8')) |
388 s.append("<") | 388 s.append(b"<") |
389 fields = extension.fieldList | 389 fields = extension.fieldList |
390 fields.sort(key=lambda f: f.var.encode('utf-8')) | 390 fields.sort(key=lambda f: f.var.encode('utf-8')) |
391 for field in fields: | 391 for field in fields: |
392 s.append(field.var.encode('utf-8')) | 392 s.append(field.var.encode('utf-8')) |
393 s.append("<") | 393 s.append(b"<") |
394 values = [v.encode('utf-8') for v in field.values] | 394 values = [v.encode('utf-8') for v in field.values] |
395 values.sort() | 395 values.sort() |
396 for value in values: | 396 for value in values: |
397 s.append(value) | 397 s.append(value) |
398 s.append("<") | 398 s.append(b"<") |
399 | 399 |
400 cap_hash = b64encode(sha1("".join(s)).digest()) | 400 cap_hash = b64encode(sha1(b"".join(s)).digest()).decode('utf-8') |
401 log.debug(_(u"Capability hash generated: [{cap_hash}]").format(cap_hash=cap_hash)) | 401 log.debug(_("Capability hash generated: [{cap_hash}]").format(cap_hash=cap_hash)) |
402 return cap_hash | 402 return cap_hash |
403 | 403 |
404 @defer.inlineCallbacks | 404 @defer.inlineCallbacks |
405 def _discoInfos( | 405 def _discoInfos( |
406 self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE | 406 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE |
407 ): | 407 ): |
408 """ Discovery method for the bridge | 408 """ Discovery method for the bridge |
409 @param entity_jid_s: entity we want to discover | 409 @param entity_jid_s: entity we want to discover |
410 @param use_cache(bool): if True, use cached data if available | 410 @param use_cache(bool): if True, use cached data if available |
411 @param node(unicode): optional node to use | 411 @param node(unicode): optional node to use |
415 client = self.host.getClient(profile_key) | 415 client = self.host.getClient(profile_key) |
416 entity = jid.JID(entity_jid_s) | 416 entity = jid.JID(entity_jid_s) |
417 disco_infos = yield self.getInfos(client, entity, node, use_cache) | 417 disco_infos = yield self.getInfos(client, entity, node, use_cache) |
418 extensions = {} | 418 extensions = {} |
419 # FIXME: should extensions be serialised using tools.common.data_format? | 419 # FIXME: should extensions be serialised using tools.common.data_format? |
420 for form_type, form in disco_infos.extensions.items(): | 420 for form_type, form in list(disco_infos.extensions.items()): |
421 fields = [] | 421 fields = [] |
422 for field in form.fieldList: | 422 for field in form.fieldList: |
423 data = {"type": field.fieldType} | 423 data = {"type": field.fieldType} |
424 for attr in ("var", "label", "desc"): | 424 for attr in ("var", "label", "desc"): |
425 value = getattr(field, attr) | 425 value = getattr(field, attr) |
426 if value is not None: | 426 if value is not None: |
427 data[attr] = value | 427 data[attr] = value |
428 | 428 |
429 values = [field.value] if field.value is not None else field.values | 429 values = [field.value] if field.value is not None else field.values |
430 if field.fieldType == u"boolean": | 430 if field.fieldType == "boolean": |
431 values = [C.boolConst(v) for v in values] | 431 values = [C.boolConst(v) for v in values] |
432 fields.append((data, values)) | 432 fields.append((data, values)) |
433 | 433 |
434 extensions[form_type or ""] = fields | 434 extensions[form_type or ""] = fields |
435 | 435 |
436 defer.returnValue(( | 436 defer.returnValue(( |
437 disco_infos.features, | 437 disco_infos.features, |
438 [(cat, type_, name or "") | 438 [(cat, type_, name or "") |
439 for (cat, type_), name in disco_infos.identities.items()], | 439 for (cat, type_), name in list(disco_infos.identities.items())], |
440 extensions)) | 440 extensions)) |
441 | 441 |
442 def items2tuples(self, disco_items): | 442 def items2tuples(self, disco_items): |
443 """convert disco items to tuple of strings | 443 """convert disco items to tuple of strings |
444 | 444 |
445 @param disco_items(iterable[disco.DiscoItem]): items | 445 @param disco_items(iterable[disco.DiscoItem]): items |
446 @return G(tuple[unicode,unicode,unicode]): serialised items | 446 @return G(tuple[unicode,unicode,unicode]): serialised items |
447 """ | 447 """ |
448 for item in disco_items: | 448 for item in disco_items: |
449 if not item.entity: | 449 if not item.entity: |
450 log.warning(_(u"invalid item (no jid)")) | 450 log.warning(_("invalid item (no jid)")) |
451 continue | 451 continue |
452 yield (item.entity.full(), item.nodeIdentifier or "", item.name or "") | 452 yield (item.entity.full(), item.nodeIdentifier or "", item.name or "") |
453 | 453 |
454 @defer.inlineCallbacks | 454 @defer.inlineCallbacks |
455 def _discoItems( | 455 def _discoItems( |
456 self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE | 456 self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE |
457 ): | 457 ): |
458 """ Discovery method for the bridge | 458 """ Discovery method for the bridge |
459 | 459 |
460 @param entity_jid_s: entity we want to discover | 460 @param entity_jid_s: entity we want to discover |
461 @param node(unicode): optional node to use | 461 @param node(unicode): optional node to use |