comparison sat/plugins/plugin_xep_0470.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 722c25818778
children
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
28 from sat.core.i18n import _ 28 from sat.core.i18n import _
29 from sat.core.log import getLogger 29 from sat.core.log import getLogger
30 from sat.core.core_types import SatXMPPEntity 30 from sat.core.core_types import SatXMPPEntity
31 from sat.core import exceptions 31 from sat.core import exceptions
32 from sat.tools.common import uri, data_format, date_utils 32 from sat.tools.common import uri, data_format, date_utils
33 from sat.tools.utils import asDeferred, xmpp_date 33 from sat.tools.utils import as_deferred, xmpp_date
34 34
35 35
36 log = getLogger(__name__) 36 log = getLogger(__name__)
37 37
38 IMPORT_NAME = "XEP-0470" 38 IMPORT_NAME = "XEP-0470"
56 class PubsubAttachments: 56 class PubsubAttachments:
57 namespace = NS_PUBSUB_ATTACHMENTS 57 namespace = NS_PUBSUB_ATTACHMENTS
58 58
59 def __init__(self, host): 59 def __init__(self, host):
60 log.info(_("XEP-0470 (Pubsub Attachments) plugin initialization")) 60 log.info(_("XEP-0470 (Pubsub Attachments) plugin initialization"))
61 host.registerNamespace("pubsub-attachments", NS_PUBSUB_ATTACHMENTS) 61 host.register_namespace("pubsub-attachments", NS_PUBSUB_ATTACHMENTS)
62 self.host = host 62 self.host = host
63 self._p = host.plugins["XEP-0060"] 63 self._p = host.plugins["XEP-0060"]
64 self.handlers: Dict[Tuple[str, str], dict[str, Any]] = {} 64 self.handlers: Dict[Tuple[str, str], dict[str, Any]] = {}
65 host.trigger.add("XEP-0277_send", self.onMBSend) 65 host.trigger.add("XEP-0277_send", self.on_mb_send)
66 self.register_attachment_handler( 66 self.register_attachment_handler(
67 "noticed", NS_PUBSUB_ATTACHMENTS, self.noticedGet, self.noticedSet 67 "noticed", NS_PUBSUB_ATTACHMENTS, self.noticed_get, self.noticed_set
68 ) 68 )
69 self.register_attachment_handler( 69 self.register_attachment_handler(
70 "reactions", NS_PUBSUB_ATTACHMENTS, self.reactionsGet, self.reactionsSet 70 "reactions", NS_PUBSUB_ATTACHMENTS, self.reactions_get, self.reactions_set
71 ) 71 )
72 host.bridge.addMethod( 72 host.bridge.add_method(
73 "psAttachmentsGet", 73 "ps_attachments_get",
74 ".plugin", 74 ".plugin",
75 in_sign="sssasss", 75 in_sign="sssasss",
76 out_sign="(ss)", 76 out_sign="(ss)",
77 method=self._get, 77 method=self._get,
78 async_=True, 78 async_=True,
79 ) 79 )
80 host.bridge.addMethod( 80 host.bridge.add_method(
81 "psAttachmentsSet", 81 "ps_attachments_set",
82 ".plugin", 82 ".plugin",
83 in_sign="ss", 83 in_sign="ss",
84 out_sign="", 84 out_sign="",
85 method=self._set, 85 method=self._set,
86 async_=True, 86 async_=True,
87 ) 87 )
88 88
89 def getHandler(self, client): 89 def get_handler(self, client):
90 return PubsubAttachments_Handler() 90 return PubsubAttachments_Handler()
91 91
92 def register_attachment_handler( 92 def register_attachment_handler(
93 self, 93 self,
94 name: str, 94 name: str,
123 self.handlers[(name, namespace)] = { 123 self.handlers[(name, namespace)] = {
124 "get": get_cb, 124 "get": get_cb,
125 "set": set_cb 125 "set": set_cb
126 } 126 }
127 127
128 def getAttachmentNodeName(self, service: jid.JID, node: str, item: str) -> str: 128 def get_attachment_node_name(self, service: jid.JID, node: str, item: str) -> str:
129 """Generate name to use for attachment node""" 129 """Generate name to use for attachment node"""
130 target_item_uri = uri.buildXMPPUri( 130 target_item_uri = uri.build_xmpp_uri(
131 "pubsub", 131 "pubsub",
132 path=service.userhost(), 132 path=service.userhost(),
133 node=node, 133 node=node,
134 item=item 134 item=item
135 ) 135 )
136 return f"{NS_PUBSUB_ATTACHMENTS}/{target_item_uri}" 136 return f"{NS_PUBSUB_ATTACHMENTS}/{target_item_uri}"
137 137
138 def isAttachmentNode(self, node: str) -> bool: 138 def is_attachment_node(self, node: str) -> bool:
139 """Return True if node name is an attachment node""" 139 """Return True if node name is an attachment node"""
140 return node.startswith(f"{NS_PUBSUB_ATTACHMENTS}/") 140 return node.startswith(f"{NS_PUBSUB_ATTACHMENTS}/")
141 141
142 def attachmentNode2Item(self, node: str) -> Tuple[jid.JID, str, str]: 142 def attachment_node_2_item(self, node: str) -> Tuple[jid.JID, str, str]:
143 """Retrieve service, node and item from attachement node's name""" 143 """Retrieve service, node and item from attachement node's name"""
144 if not self.isAttachmentNode(node): 144 if not self.is_attachment_node(node):
145 raise ValueError("this is not an attachment node!") 145 raise ValueError("this is not an attachment node!")
146 prefix_len = len(f"{NS_PUBSUB_ATTACHMENTS}/") 146 prefix_len = len(f"{NS_PUBSUB_ATTACHMENTS}/")
147 item_uri = node[prefix_len:] 147 item_uri = node[prefix_len:]
148 parsed_uri = uri.parseXMPPUri(item_uri) 148 parsed_uri = uri.parse_xmpp_uri(item_uri)
149 if parsed_uri["type"] != "pubsub": 149 if parsed_uri["type"] != "pubsub":
150 raise ValueError(f"unexpected URI type, it must be a pubsub URI: {item_uri}") 150 raise ValueError(f"unexpected URI type, it must be a pubsub URI: {item_uri}")
151 try: 151 try:
152 service = jid.JID(parsed_uri["path"]) 152 service = jid.JID(parsed_uri["path"])
153 except RuntimeError: 153 except RuntimeError:
154 raise ValueError(f"invalid service in pubsub URI: {item_uri}") 154 raise ValueError(f"invalid service in pubsub URI: {item_uri}")
155 node = parsed_uri["node"] 155 node = parsed_uri["node"]
156 item = parsed_uri["item"] 156 item = parsed_uri["item"]
157 return (service, node, item) 157 return (service, node, item)
158 158
159 async def onMBSend( 159 async def on_mb_send(
160 self, 160 self,
161 client: SatXMPPEntity, 161 client: SatXMPPEntity,
162 service: jid.JID, 162 service: jid.JID,
163 node: str, 163 node: str,
164 item: domish.Element, 164 item: domish.Element,
201 try: 201 try:
202 # FIXME: check if this is the best publish_model option 202 # FIXME: check if this is the best publish_model option
203 node_config.fields["pubsub#publish_model"].value = "open" 203 node_config.fields["pubsub#publish_model"].value = "open"
204 except KeyError: 204 except KeyError:
205 log.warning("pubsub#publish_model field is missing") 205 log.warning("pubsub#publish_model field is missing")
206 attachment_node = self.getAttachmentNodeName(service, node, item_id) 206 attachment_node = self.get_attachment_node_name(service, node, item_id)
207 # we use the same options as target node 207 # we use the same options as target node
208 try: 208 try:
209 await self._p.createIfNewNode( 209 await self._p.create_if_new_node(
210 client, service, attachment_node, options=dict(node_config) 210 client, service, attachment_node, options=dict(node_config)
211 ) 211 )
212 except Exception as e: 212 except Exception as e:
213 log.warning(f"Can't create attachment node {attachment_node}: {e}") 213 log.warning(f"Can't create attachment node {attachment_node}: {e}")
214 214
215 def items2attachmentData( 215 def items_2_attachment_data(
216 self, 216 self,
217 client: SatXMPPEntity, 217 client: SatXMPPEntity,
218 items: List[domish.Element] 218 items: List[domish.Element]
219 ) -> List[Dict[str, Any]]: 219 ) -> List[Dict[str, Any]]:
220 """Convert items from attachment node to attachment data""" 220 """Convert items from attachment node to attachment data"""
265 item: str, 265 item: str,
266 senders_s: List[str], 266 senders_s: List[str],
267 extra_s: str, 267 extra_s: str,
268 profile_key: str 268 profile_key: str
269 ) -> defer.Deferred: 269 ) -> defer.Deferred:
270 client = self.host.getClient(profile_key) 270 client = self.host.get_client(profile_key)
271 extra = data_format.deserialise(extra_s) 271 extra = data_format.deserialise(extra_s)
272 senders = [jid.JID(s) for s in senders_s] 272 senders = [jid.JID(s) for s in senders_s]
273 d = defer.ensureDeferred( 273 d = defer.ensureDeferred(
274 self.getAttachments(client, jid.JID(service_s), node, item, senders) 274 self.get_attachments(client, jid.JID(service_s), node, item, senders)
275 ) 275 )
276 d.addCallback( 276 d.addCallback(
277 lambda ret: 277 lambda ret:
278 (data_format.serialise(ret[0]), 278 (data_format.serialise(ret[0]),
279 data_format.serialise(ret[1])) 279 data_format.serialise(ret[1]))
280 ) 280 )
281 return d 281 return d
282 282
283 async def getAttachments( 283 async def get_attachments(
284 self, 284 self,
285 client: SatXMPPEntity, 285 client: SatXMPPEntity,
286 service: jid.JID, 286 service: jid.JID,
287 node: str, 287 node: str,
288 item: str, 288 item: str,
296 @param item: ID of the item for which attachments will be retrieved 296 @param item: ID of the item for which attachments will be retrieved
297 @param senders: bare JIDs of entities that are checked. Attachments from those 297 @param senders: bare JIDs of entities that are checked. Attachments from those
298 entities will be retrieved. 298 entities will be retrieved.
299 If None, attachments from all entities will be retrieved 299 If None, attachments from all entities will be retrieved
300 @param extra: extra data, will be used as ``extra`` argument when doing 300 @param extra: extra data, will be used as ``extra`` argument when doing
301 ``getItems`` call. 301 ``get_items`` call.
302 @return: A tuple with: 302 @return: A tuple with:
303 - the list of attachments data, one item per found sender. The attachments 303 - the list of attachments data, one item per found sender. The attachments
304 data are dict containing attachment, no ``extra`` field is used here 304 data are dict containing attachment, no ``extra`` field is used here
305 (contrarily to attachments data used with ``set_attachements``). 305 (contrarily to attachments data used with ``set_attachements``).
306 - metadata returned by the call to ``getItems`` 306 - metadata returned by the call to ``get_items``
307 """ 307 """
308 if extra is None: 308 if extra is None:
309 extra = {} 309 extra = {}
310 attachment_node = self.getAttachmentNodeName(service, node, item) 310 attachment_node = self.get_attachment_node_name(service, node, item)
311 item_ids = [e.userhost() for e in senders] if senders else None 311 item_ids = [e.userhost() for e in senders] if senders else None
312 items, metadata = await self._p.getItems( 312 items, metadata = await self._p.get_items(
313 client, service, attachment_node, item_ids=item_ids, extra=extra 313 client, service, attachment_node, item_ids=item_ids, extra=extra
314 ) 314 )
315 list_data = self.items2attachmentData(client, items) 315 list_data = self.items_2_attachment_data(client, items)
316 316
317 return list_data, metadata 317 return list_data, metadata
318 318
319 def _set( 319 def _set(
320 self, 320 self,
321 attachments_s: str, 321 attachments_s: str,
322 profile_key: str 322 profile_key: str
323 ) -> None: 323 ) -> None:
324 client = self.host.getClient(profile_key) 324 client = self.host.get_client(profile_key)
325 attachments = data_format.deserialise(attachments_s) or {} 325 attachments = data_format.deserialise(attachments_s) or {}
326 return defer.ensureDeferred(self.set_attachements(client, attachments)) 326 return defer.ensureDeferred(self.set_attachements(client, attachments))
327 327
328 async def apply_set_handler( 328 async def apply_set_handler(
329 self, 329 self,
376 continue 376 continue
377 try: 377 try:
378 former_elt = next(attachments_elt.elements(namespace, name)) 378 former_elt = next(attachments_elt.elements(namespace, name))
379 except StopIteration: 379 except StopIteration:
380 former_elt = None 380 former_elt = None
381 new_elt = await asDeferred( 381 new_elt = await as_deferred(
382 handler["set"], client, attachments_data, former_elt 382 handler["set"], client, attachments_data, former_elt
383 ) 383 )
384 if new_elt != former_elt: 384 if new_elt != former_elt:
385 if former_elt is not None: 385 if former_elt is not None:
386 attachments_elt.children.remove(former_elt) 386 attachments_elt.children.remove(former_elt)
415 item = attachments_data["id"] 415 item = attachments_data["id"]
416 except (KeyError, RuntimeError): 416 except (KeyError, RuntimeError):
417 raise ValueError( 417 raise ValueError(
418 'data must have "service", "node" and "id" set' 418 'data must have "service", "node" and "id" set'
419 ) 419 )
420 attachment_node = self.getAttachmentNodeName(service, node, item) 420 attachment_node = self.get_attachment_node_name(service, node, item)
421 try: 421 try:
422 items, __ = await self._p.getItems( 422 items, __ = await self._p.get_items(
423 client, service, attachment_node, item_ids=[client.jid.userhost()] 423 client, service, attachment_node, item_ids=[client.jid.userhost()]
424 ) 424 )
425 except exceptions.NotFound: 425 except exceptions.NotFound:
426 item_elt = None 426 item_elt = None
427 else: 427 else:
435 attachments_data, 435 attachments_data,
436 item_elt=item_elt, 436 item_elt=item_elt,
437 ) 437 )
438 438
439 try: 439 try:
440 await self._p.sendItems(client, service, attachment_node, [item_elt]) 440 await self._p.send_items(client, service, attachment_node, [item_elt])
441 except error.StanzaError as e: 441 except error.StanzaError as e:
442 if e.condition == "item-not-found": 442 if e.condition == "item-not-found":
443 # the node doesn't exist, we can't publish attachments 443 # the node doesn't exist, we can't publish attachments
444 log.warning( 444 log.warning(
445 f"no attachment node found at {service} on {node!r} for item " 445 f"no attachment node found at {service} on {node!r} for item "
460 460
461 @param service: service of target item (will also be used for attachment node) 461 @param service: service of target item (will also be used for attachment node)
462 @param node: node of target item (used to get attachment node's name) 462 @param node: node of target item (used to get attachment node's name)
463 @param item: name of target item (used to get attachment node's name) 463 @param item: name of target item (used to get attachment node's name)
464 """ 464 """
465 attachment_node = self.getAttachmentNodeName(service, node, item) 465 attachment_node = self.get_attachment_node_name(service, node, item)
466 await self._p.subscribe(client, service, attachment_node) 466 await self._p.subscribe(client, service, attachment_node)
467 467
468 468
469 def setTimestamp(self, attachment_elt: domish.Element, data: dict) -> None: 469 def set_timestamp(self, attachment_elt: domish.Element, data: dict) -> None:
470 """Check if a ``timestamp`` attribute is set, parse it, and fill data 470 """Check if a ``timestamp`` attribute is set, parse it, and fill data
471 471
472 @param attachments_elt: element where the ``timestamp`` attribute may be set 472 @param attachments_elt: element where the ``timestamp`` attribute may be set
473 @param data: data specific to the attachment (i.e. not the whole microblog data) 473 @param data: data specific to the attachment (i.e. not the whole microblog data)
474 ``timestamp`` field will be set there if timestamp exists and is parsable 474 ``timestamp`` field will be set there if timestamp exists and is parsable
480 except date_utils.ParserError: 480 except date_utils.ParserError:
481 log.warning(f"can't parse timestamp: {timestamp_raw}") 481 log.warning(f"can't parse timestamp: {timestamp_raw}")
482 else: 482 else:
483 data["timestamp"] = timestamp 483 data["timestamp"] = timestamp
484 484
485 def noticedGet( 485 def noticed_get(
486 self, 486 self,
487 client: SatXMPPEntity, 487 client: SatXMPPEntity,
488 attachments_elt: domish.Element, 488 attachments_elt: domish.Element,
489 data: Dict[str, Any], 489 data: Dict[str, Any],
490 ) -> None: 490 ) -> None:
496 pass 496 pass
497 else: 497 else:
498 noticed_data = { 498 noticed_data = {
499 "noticed": True 499 "noticed": True
500 } 500 }
501 self.setTimestamp(noticed_elt, noticed_data) 501 self.set_timestamp(noticed_elt, noticed_data)
502 data["noticed"] = noticed_data 502 data["noticed"] = noticed_data
503 503
504 def noticedSet( 504 def noticed_set(
505 self, 505 self,
506 client: SatXMPPEntity, 506 client: SatXMPPEntity,
507 data: Dict[str, Any], 507 data: Dict[str, Any],
508 former_elt: Optional[domish.Element] 508 former_elt: Optional[domish.Element]
509 ) -> Optional[domish.Element]: 509 ) -> Optional[domish.Element]:
523 } 523 }
524 ) 524 )
525 else: 525 else:
526 return None 526 return None
527 527
528 def reactionsGet( 528 def reactions_get(
529 self, 529 self,
530 client: SatXMPPEntity, 530 client: SatXMPPEntity,
531 attachments_elt: domish.Element, 531 attachments_elt: domish.Element,
532 data: Dict[str, Any], 532 data: Dict[str, Any],
533 ) -> None: 533 ) -> None:
540 else: 540 else:
541 reactions_data = {"reactions": []} 541 reactions_data = {"reactions": []}
542 reactions = reactions_data["reactions"] 542 reactions = reactions_data["reactions"]
543 for reaction_elt in reactions_elt.elements(NS_PUBSUB_ATTACHMENTS, "reaction"): 543 for reaction_elt in reactions_elt.elements(NS_PUBSUB_ATTACHMENTS, "reaction"):
544 reactions.append(str(reaction_elt)) 544 reactions.append(str(reaction_elt))
545 self.setTimestamp(reactions_elt, reactions_data) 545 self.set_timestamp(reactions_elt, reactions_data)
546 data["reactions"] = reactions_data 546 data["reactions"] = reactions_data
547 547
548 def reactionsSet( 548 def reactions_set(
549 self, 549 self,
550 client: SatXMPPEntity, 550 client: SatXMPPEntity,
551 data: Dict[str, Any], 551 data: Dict[str, Any],
552 former_elt: Optional[domish.Element] 552 former_elt: Optional[domish.Element]
553 ) -> Optional[domish.Element]: 553 ) -> Optional[domish.Element]: