Mercurial > libervia-backend
comparison sat/plugins/plugin_exp_invitation.py @ 3462:12dc234f698c
plugin invitation: pubsub invitations:
- new Pubsub invitation plugin, to have a generic way to manage invitation on Pubsub based
features
- `invitePreflight` and `onInvitationPreflight` method can be implemented to customise
invitation for a namespace
- refactored events invitations to use the new plugin
- a Pubsub invitation can now be for a whole node instead of a specific item
- if invitation is for a node, a namespace can be specified to indicate what this node is
about. It is then added in `extra` data
- an element (domish.Element) can be added in `extra` data, it will then be added in the
invitation
- some code modernisation
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 19 Feb 2021 15:50:22 +0100 |
parents | 000b6722bd35 |
children | be6d91572633 |
comparison
equal
deleted
inserted
replaced
3461:02a8d227d5bb | 3462:12dc234f698c |
---|---|
14 # GNU Affero General Public License for more details. | 14 # GNU Affero General Public License for more details. |
15 | 15 |
16 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
19 from typing import Optional | |
20 from zope.interface import implementer | |
21 from twisted.internet import defer | |
22 from twisted.words.protocols.jabber import jid | |
23 from twisted.words.protocols.jabber.xmlstream import XMPPHandler | |
24 from wokkel import disco, iwokkel | |
19 from sat.core.i18n import _ | 25 from sat.core.i18n import _ |
20 from sat.core import exceptions | 26 from sat.core import exceptions |
21 from sat.core.constants import Const as C | 27 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 28 from sat.core.log import getLogger |
23 from twisted.internet import defer | 29 from sat.core.xmpp import SatXMPPEntity |
24 from twisted.words.protocols.jabber import jid | 30 from sat.tools import utils |
25 from wokkel import disco, iwokkel | |
26 from zope.interface import implementer | |
27 from twisted.words.protocols.jabber.xmlstream import XMPPHandler | |
28 | 31 |
29 log = getLogger(__name__) | 32 log = getLogger(__name__) |
30 | 33 |
31 | 34 |
32 PLUGIN_INFO = { | 35 PLUGIN_INFO = { |
121 .format(url=thumb_url)) | 124 .format(url=thumb_url)) |
122 else: | 125 else: |
123 invitation_elt['thumb_url'] = thumb_url | 126 invitation_elt['thumb_url'] = thumb_url |
124 return mess_data, invitation_elt | 127 return mess_data, invitation_elt |
125 | 128 |
126 def sendPubsubInvitation(self, client, invitee_jid, service, node, | 129 def sendPubsubInvitation( |
127 item_id, name, extra): | 130 self, |
131 client: SatXMPPEntity, | |
132 invitee_jid: jid.JID, | |
133 service: jid.JID, | |
134 node: str, | |
135 item_id: Optional[str], | |
136 name: Optional[str], | |
137 extra: Optional[dict] | |
138 ) -> None: | |
128 """Send an pubsub invitation in a <message> stanza | 139 """Send an pubsub invitation in a <message> stanza |
129 | 140 |
130 @param invitee_jid(jid.JID): entitee to send invitation to | 141 @param invitee_jid: entitee to send invitation to |
131 @param service(jid.JID): pubsub service | 142 @param service: pubsub service |
132 @param node(unicode): pubsub node | 143 @param node: pubsub node |
133 @param item_id(unicode): pubsub id | 144 @param item_id: pubsub id |
134 @param name(unicode, None): see [_generateBaseInvitation] | 145 None when the invitation is for a whole node |
135 @param extra(dict, None): see [_generateBaseInvitation] | 146 @param name: see [_generateBaseInvitation] |
147 @param extra: see [_generateBaseInvitation] | |
136 """ | 148 """ |
137 if extra is None: | 149 if extra is None: |
138 extra = {} | 150 extra = {} |
139 mess_data, invitation_elt = self._generateBaseInvitation( | 151 mess_data, invitation_elt = self._generateBaseInvitation( |
140 client, invitee_jid, name, extra) | 152 client, invitee_jid, name, extra) |
141 pubsub_elt = invitation_elt.addElement("pubsub") | 153 pubsub_elt = invitation_elt.addElement("pubsub") |
142 pubsub_elt["service"] = service.full() | 154 pubsub_elt["service"] = service.full() |
143 pubsub_elt["node"] = node | 155 pubsub_elt["node"] = node |
144 pubsub_elt["item"] = item_id | 156 if item_id is None: |
145 return client.send(mess_data["xml"]) | 157 try: |
158 namespace = extra.pop("namespace") | |
159 except KeyError: | |
160 raise exceptions.DataError('"namespace" key is missing in "extra" data') | |
161 node_data_elt = pubsub_elt.addElement("node_data") | |
162 node_data_elt["namespace"] = namespace | |
163 try: | |
164 node_data_elt.addChild(extra["element"]) | |
165 except KeyError: | |
166 pass | |
167 else: | |
168 pubsub_elt["item"] = item_id | |
169 if "element" in extra: | |
170 invitation_elt.addChild(extra.pop("element")) | |
171 client.send(mess_data["xml"]) | |
146 | 172 |
147 async def sendFileSharingInvitation( | 173 async def sendFileSharingInvitation( |
148 self, client, invitee_jid, service, repos_type=None, namespace=None, path=None, | 174 self, client, invitee_jid, service, repos_type=None, namespace=None, path=None, |
149 name=None, extra=None | 175 name=None, extra=None |
150 ): | 176 ): |
205 file_sharing_elt["namespace"] = namespace | 231 file_sharing_elt["namespace"] = namespace |
206 if path is not None: | 232 if path is not None: |
207 file_sharing_elt["path"] = path | 233 file_sharing_elt["path"] = path |
208 client.send(mess_data["xml"]) | 234 client.send(mess_data["xml"]) |
209 | 235 |
210 @defer.inlineCallbacks | 236 async def _parsePubsubElt(self, client, pubsub_elt): |
211 def _parsePubsubElt(self, client, pubsub_elt): | |
212 try: | 237 try: |
213 service = jid.JID(pubsub_elt["service"]) | 238 service = jid.JID(pubsub_elt["service"]) |
214 node = pubsub_elt["node"] | 239 node = pubsub_elt["node"] |
215 item_id = pubsub_elt.getAttribute("item") | |
216 except (RuntimeError, KeyError): | 240 except (RuntimeError, KeyError): |
217 log.warning(_("Bad invitation, ignoring")) | 241 raise exceptions.DataError("Bad invitation, ignoring") |
218 raise exceptions.DataError | 242 |
219 | 243 item_id = pubsub_elt.getAttribute("item") |
220 try: | 244 |
221 items, metadata = yield self._p.getItems(client, service, node, | 245 if item_id is not None: |
222 item_ids=[item_id]) | 246 try: |
223 except Exception as e: | 247 items, metadata = await self._p.getItems( |
224 log.warning(_("Can't get item linked with invitation: {reason}").format( | 248 client, service, node, item_ids=[item_id] |
225 reason=e)) | 249 ) |
226 try: | 250 except Exception as e: |
227 item_elt = items[0] | 251 log.warning(_("Can't get item linked with invitation: {reason}").format( |
228 except IndexError: | 252 reason=e)) |
229 log.warning(_("Invitation was linking to a non existing item")) | 253 try: |
230 raise exceptions.DataError | 254 item_elt = items[0] |
231 | 255 except IndexError: |
232 try: | 256 log.warning(_("Invitation was linking to a non existing item")) |
233 namespace = item_elt.firstChildElement().uri | 257 raise exceptions.DataError |
234 except Exception as e: | 258 |
235 log.warning(_("Can't retrieve namespace of invitation: {reason}").format( | 259 try: |
236 reason = e)) | 260 namespace = item_elt.firstChildElement().uri |
237 raise exceptions.DataError | 261 except Exception as e: |
238 | 262 log.warning(_("Can't retrieve namespace of invitation: {reason}").format( |
239 args = [service, node, item_id, item_elt] | 263 reason = e)) |
240 defer.returnValue((namespace, args)) | 264 raise exceptions.DataError |
241 | 265 |
242 def _parseFileSharingElt(self, client, file_sharing_elt): | 266 args = [service, node, item_id, item_elt] |
267 else: | |
268 try: | |
269 node_data_elt = next(pubsub_elt.elements((NS_INVITATION, "node_data"))) | |
270 except StopIteration: | |
271 raise exceptions.DataError("Bad invitation, ignoring") | |
272 namespace = node_data_elt['namespace'] | |
273 args = [service, node, None, node_data_elt] | |
274 | |
275 return namespace, args | |
276 | |
277 async def _parseFileSharingElt(self, client, file_sharing_elt): | |
243 try: | 278 try: |
244 service = jid.JID(file_sharing_elt["service"]) | 279 service = jid.JID(file_sharing_elt["service"]) |
245 except (RuntimeError, KeyError): | 280 except (RuntimeError, KeyError): |
246 log.warning(_("Bad invitation, ignoring")) | 281 log.warning(_("Bad invitation, ignoring")) |
247 raise exceptions.DataError | 282 raise exceptions.DataError |
248 repos_type = file_sharing_elt.getAttribute("type", "files") | 283 repos_type = file_sharing_elt.getAttribute("type", "files") |
249 namespace = file_sharing_elt.getAttribute("namespace") | 284 sharing_ns = file_sharing_elt.getAttribute("namespace") |
250 path = file_sharing_elt.getAttribute("path") | 285 path = file_sharing_elt.getAttribute("path") |
251 args = [service, repos_type, namespace, path] | 286 args = [service, repos_type, sharing_ns, path] |
252 ns_fis = self.host.getNamespace("fis") | 287 ns_fis = self.host.getNamespace("fis") |
253 return ns_fis, args | 288 return ns_fis, args |
254 | 289 |
255 @defer.inlineCallbacks | 290 async def onInvitation(self, message_elt, client): |
256 def onInvitation(self, message_elt, client): | |
257 log.debug("invitation received [{profile}]".format(profile=client.profile)) | 291 log.debug("invitation received [{profile}]".format(profile=client.profile)) |
258 invitation_elt = message_elt.invitation | 292 invitation_elt = message_elt.invitation |
259 | 293 |
260 name = invitation_elt.getAttribute("name") | 294 name = invitation_elt.getAttribute("name") |
261 extra = {} | 295 extra = {} |
273 else: | 307 else: |
274 log.warning("not implemented invitation element: {xml}".format( | 308 log.warning("not implemented invitation element: {xml}".format( |
275 xml = elt.toXml())) | 309 xml = elt.toXml())) |
276 continue | 310 continue |
277 try: | 311 try: |
278 namespace, args = yield method(client, elt) | 312 namespace, args = await method(client, elt) |
279 except exceptions.DataError: | 313 except exceptions.DataError: |
280 log.warning("Can't parse invitation element: {xml}".format( | 314 log.warning("Can't parse invitation element: {xml}".format( |
281 xml = elt.toXml())) | 315 xml = elt.toXml())) |
282 continue | 316 continue |
283 | 317 |
286 except KeyError: | 320 except KeyError: |
287 log.warning(_( | 321 log.warning(_( |
288 'No handler for namespace "{namespace}", invitation ignored') | 322 'No handler for namespace "{namespace}", invitation ignored') |
289 .format(namespace=namespace)) | 323 .format(namespace=namespace)) |
290 else: | 324 else: |
291 cb(client, name, extra, *args) | 325 await utils.asDeferred(cb, client, namespace, name, extra, *args) |
292 | 326 |
293 | 327 |
294 @implementer(iwokkel.IDisco) | 328 @implementer(iwokkel.IDisco) |
295 class PubsubInvitationHandler(XMPPHandler): | 329 class PubsubInvitationHandler(XMPPHandler): |
296 | 330 |
297 def __init__(self, plugin_parent): | 331 def __init__(self, plugin_parent): |
298 self.plugin_parent = plugin_parent | 332 self.plugin_parent = plugin_parent |
299 | 333 |
300 def connectionInitialized(self): | 334 def connectionInitialized(self): |
301 self.xmlstream.addObserver( | 335 self.xmlstream.addObserver( |
302 INVITATION, self.plugin_parent.onInvitation, client=self.parent | 336 INVITATION, |
337 lambda message_elt: defer.ensureDeferred( | |
338 self.plugin_parent.onInvitation(message_elt, client=self.parent) | |
339 ), | |
303 ) | 340 ) |
304 | 341 |
305 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | 342 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
306 return [ | 343 return [ |
307 disco.DiscoFeature(NS_INVITATION), | 344 disco.DiscoFeature(NS_INVITATION), |