comparison sat/plugins/plugin_xep_0313.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 5245b675f7ad
children
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
61 61
62 class XEP_0313(object): 62 class XEP_0313(object):
63 def __init__(self, host): 63 def __init__(self, host):
64 log.info(_("Message Archive Management plugin initialization")) 64 log.info(_("Message Archive Management plugin initialization"))
65 self.host = host 65 self.host = host
66 self.host.registerNamespace("mam", mam.NS_MAM) 66 self.host.register_namespace("mam", mam.NS_MAM)
67 host.registerNamespace("fulltextmam", NS_FTS) 67 host.register_namespace("fulltextmam", NS_FTS)
68 self._rsm = host.plugins["XEP-0059"] 68 self._rsm = host.plugins["XEP-0059"]
69 self._sid = host.plugins["XEP-0359"] 69 self._sid = host.plugins["XEP-0359"]
70 # Deferred used to store last stanza id in order of reception 70 # Deferred used to store last stanza id in order of reception
71 self._last_stanza_id_d = defer.Deferred() 71 self._last_stanza_id_d = defer.Deferred()
72 self._last_stanza_id_d.callback(None) 72 self._last_stanza_id_d.callback(None)
73 host.bridge.addMethod( 73 host.bridge.add_method(
74 "MAMGet", ".plugin", in_sign='sss', 74 "mam_get", ".plugin", in_sign='sss',
75 out_sign='(a(sdssa{ss}a{ss}ss)ss)', method=self._getArchives, 75 out_sign='(a(sdssa{ss}a{ss}ss)ss)', method=self._get_archives,
76 async_=True) 76 async_=True)
77 77
78 async def resume(self, client): 78 async def resume(self, client):
79 """Retrieve one2one messages received since the last we have in local storage""" 79 """Retrieve one2one messages received since the last we have in local storage"""
80 stanza_id_data = await self.host.memory.storage.getPrivates( 80 stanza_id_data = await self.host.memory.storage.get_privates(
81 mam.NS_MAM, [KEY_LAST_STANZA_ID], profile=client.profile) 81 mam.NS_MAM, [KEY_LAST_STANZA_ID], profile=client.profile)
82 stanza_id = stanza_id_data.get(KEY_LAST_STANZA_ID) 82 stanza_id = stanza_id_data.get(KEY_LAST_STANZA_ID)
83 rsm_req = None 83 rsm_req = None
84 if stanza_id is None: 84 if stanza_id is None:
85 log.info("can't retrieve last stanza ID, checking history") 85 log.info("can't retrieve last stanza ID, checking history")
86 last_mess = await self.host.memory.historyGet( 86 last_mess = await self.host.memory.history_get(
87 None, None, limit=1, filters={'not_types': C.MESS_TYPE_GROUPCHAT, 87 None, None, limit=1, filters={'not_types': C.MESS_TYPE_GROUPCHAT,
88 'last_stanza_id': True}, 88 'last_stanza_id': True},
89 profile=client.profile) 89 profile=client.profile)
90 if not last_mess: 90 if not last_mess:
91 log.info(_("It seems that we have no MAM history yet")) 91 log.info(_("It seems that we have no MAM history yet"))
97 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) 97 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id)
98 mam_req = mam.MAMRequest(rsm_=rsm_req) 98 mam_req = mam.MAMRequest(rsm_=rsm_req)
99 complete = False 99 complete = False
100 count = 0 100 count = 0
101 while not complete: 101 while not complete:
102 mam_data = await self.getArchives(client, mam_req, 102 mam_data = await self.get_archives(client, mam_req,
103 service=client.jid.userhostJID()) 103 service=client.jid.userhostJID())
104 elt_list, rsm_response, mam_response = mam_data 104 elt_list, rsm_response, mam_response = mam_data
105 complete = mam_response["complete"] 105 complete = mam_response["complete"]
106 # we update MAM request for next iteration 106 # we update MAM request for next iteration
107 mam_req.rsm.after = rsm_response.last 107 mam_req.rsm.after = rsm_response.last
112 else: 112 else:
113 count += len(elt_list) 113 count += len(elt_list)
114 114
115 for mess_elt in elt_list: 115 for mess_elt in elt_list:
116 try: 116 try:
117 fwd_message_elt = self.getMessageFromResult( 117 fwd_message_elt = self.get_message_from_result(
118 client, mess_elt, mam_req) 118 client, mess_elt, mam_req)
119 except exceptions.DataError: 119 except exceptions.DataError:
120 continue 120 continue
121 121
122 try: 122 try:
140 'was expecting a message sent by our jid, but this one if ' 140 'was expecting a message sent by our jid, but this one if '
141 'from {from_jid}, ignoring\n{xml}').format( 141 'from {from_jid}, ignoring\n{xml}').format(
142 from_jid=from_jid.full(), xml=mess_elt.toXml())) 142 from_jid=from_jid.full(), xml=mess_elt.toXml()))
143 continue 143 continue
144 # adding message to history 144 # adding message to history
145 mess_data = client.messageProt.parseMessage(fwd_message_elt) 145 mess_data = client.messageProt.parse_message(fwd_message_elt)
146 try: 146 try:
147 await client.messageProt.addToHistory(mess_data) 147 await client.messageProt.add_to_history(mess_data)
148 except exceptions.CancelError as e: 148 except exceptions.CancelError as e:
149 log.warning( 149 log.warning(
150 "message has not been added to history: {e}".format(e=e)) 150 "message has not been added to history: {e}".format(e=e))
151 except Exception as e: 151 except Exception as e:
152 log.error( 152 log.error(
157 log.info(_("We have received no message while offline")) 157 log.info(_("We have received no message while offline"))
158 else: 158 else:
159 log.info(_("We have received {num_mess} message(s) while offline.") 159 log.info(_("We have received {num_mess} message(s) while offline.")
160 .format(num_mess=count)) 160 .format(num_mess=count))
161 161
162 def profileConnected(self, client): 162 def profile_connected(self, client):
163 defer.ensureDeferred(self.resume(client)) 163 defer.ensureDeferred(self.resume(client))
164 164
165 def getHandler(self, client): 165 def get_handler(self, client):
166 mam_client = client._mam = SatMAMClient(self) 166 mam_client = client._mam = SatMAMClient(self)
167 return mam_client 167 return mam_client
168 168
169 def parseExtra(self, extra, with_rsm=True): 169 def parse_extra(self, extra, with_rsm=True):
170 """Parse extra dictionnary to retrieve MAM arguments 170 """Parse extra dictionnary to retrieve MAM arguments
171 171
172 @param extra(dict): data for parse 172 @param extra(dict): data for parse
173 @param with_rsm(bool): if True, RSM data will be parsed too 173 @param with_rsm(bool): if True, RSM data will be parsed too
174 @return (data_form, None): request with parsed arguments 174 @return (data_form, None): request with parsed arguments
206 mam_args[arg] = value 206 mam_args[arg] = value
207 except KeyError: 207 except KeyError:
208 continue 208 continue
209 209
210 if with_rsm: 210 if with_rsm:
211 rsm_request = self._rsm.parseExtra(extra) 211 rsm_request = self._rsm.parse_extra(extra)
212 if rsm_request is not None: 212 if rsm_request is not None:
213 mam_args["rsm_"] = rsm_request 213 mam_args["rsm_"] = rsm_request
214 214
215 if form_args: 215 if form_args:
216 mam_args["form"] = mam.buildForm(**form_args) 216 mam_args["form"] = mam.buildForm(**form_args)
222 assert isinstance(order_by, list) 222 assert isinstance(order_by, list)
223 mam_args["orderBy"] = order_by 223 mam_args["orderBy"] = order_by
224 224
225 return mam.MAMRequest(**mam_args) if mam_args else None 225 return mam.MAMRequest(**mam_args) if mam_args else None
226 226
227 def getMessageFromResult(self, client, mess_elt, mam_req, service=None): 227 def get_message_from_result(self, client, mess_elt, mam_req, service=None):
228 """Extract usable <message/> from MAM query result 228 """Extract usable <message/> from MAM query result
229 229
230 The message will be validated, and stanza-id/delay will be added if necessary. 230 The message will be validated, and stanza-id/delay will be added if necessary.
231 @param mess_elt(domish.Element): result <message/> element wrapping the message 231 @param mess_elt(domish.Element): result <message/> element wrapping the message
232 to retrieve 232 to retrieve
265 else: 265 else:
266 if not result_elt["queryid"] == mam_req.query_id: 266 if not result_elt["queryid"] == mam_req.query_id:
267 log.error("Unexpected query id (was expecting {query_id}): {xml}" 267 log.error("Unexpected query id (was expecting {query_id}): {xml}"
268 .format(query_id=mam.query_id, xml=mess_elt.toXml())) 268 .format(query_id=mam.query_id, xml=mess_elt.toXml()))
269 raise exceptions.DataError("Invalid element") 269 raise exceptions.DataError("Invalid element")
270 stanza_id = self._sid.getStanzaId(fwd_message_elt, 270 stanza_id = self._sid.get_stanza_id(fwd_message_elt,
271 service_jid) 271 service_jid)
272 if stanza_id is None: 272 if stanza_id is None:
273 # not stanza-id element is present, we add one so message 273 # not stanza-id element is present, we add one so message
274 # will be archived with it, and we won't request several times 274 # will be archived with it, and we won't request several times
275 # the same MAM achive 275 # the same MAM achive
277 stanza_id = result_elt["id"] 277 stanza_id = result_elt["id"]
278 except AttributeError: 278 except AttributeError:
279 log.warning('Invalid MAM result: missing "id" attribute: {xml}' 279 log.warning('Invalid MAM result: missing "id" attribute: {xml}'
280 .format(xml=result_elt.toXml())) 280 .format(xml=result_elt.toXml()))
281 raise exceptions.DataError("Invalid element") 281 raise exceptions.DataError("Invalid element")
282 self._sid.addStanzaId(client, fwd_message_elt, stanza_id, by=service_jid) 282 self._sid.add_stanza_id(client, fwd_message_elt, stanza_id, by=service_jid)
283 283
284 if delay_elt is not None: 284 if delay_elt is not None:
285 fwd_message_elt.addChild(delay_elt) 285 fwd_message_elt.addChild(delay_elt)
286 286
287 return fwd_message_elt 287 return fwd_message_elt
302 None for user server 302 None for user server
303 @return (D(domish.Element)): <IQ/> result 303 @return (D(domish.Element)): <IQ/> result
304 """ 304 """
305 return client._mam.queryArchive(mam_req, service) 305 return client._mam.queryArchive(mam_req, service)
306 306
307 def _appendMessage(self, elt_list, message_cb, message_elt): 307 def _append_message(self, elt_list, message_cb, message_elt):
308 if message_cb is not None: 308 if message_cb is not None:
309 elt_list.append(message_cb(message_elt)) 309 elt_list.append(message_cb(message_elt))
310 else: 310 else:
311 elt_list.append(message_elt) 311 elt_list.append(message_elt)
312 312
313 def _queryFinished(self, iq_result, client, elt_list, event): 313 def _query_finished(self, iq_result, client, elt_list, event):
314 client.xmlstream.removeObserver(event, self._appendMessage) 314 client.xmlstream.removeObserver(event, self._append_message)
315 try: 315 try:
316 fin_elt = next(iq_result.elements(mam.NS_MAM, "fin")) 316 fin_elt = next(iq_result.elements(mam.NS_MAM, "fin"))
317 except StopIteration: 317 except StopIteration:
318 raise exceptions.DataError("Invalid MAM result") 318 raise exceptions.DataError("Invalid MAM result")
319 319
325 except rsm.RSMNotFoundError: 325 except rsm.RSMNotFoundError:
326 rsm_response = None 326 rsm_response = None
327 327
328 return (elt_list, rsm_response, mam_response) 328 return (elt_list, rsm_response, mam_response)
329 329
330 def serializeArchiveResult(self, data, client, mam_req, service): 330 def serialize_archive_result(self, data, client, mam_req, service):
331 elt_list, rsm_response, mam_response = data 331 elt_list, rsm_response, mam_response = data
332 mess_list = [] 332 mess_list = []
333 for elt in elt_list: 333 for elt in elt_list:
334 fwd_message_elt = self.getMessageFromResult(client, elt, mam_req, 334 fwd_message_elt = self.get_message_from_result(client, elt, mam_req,
335 service=service) 335 service=service)
336 mess_data = client.messageProt.parseMessage(fwd_message_elt) 336 mess_data = client.messageProt.parse_message(fwd_message_elt)
337 mess_list.append(client.messageGetBridgeArgs(mess_data)) 337 mess_list.append(client.message_get_bridge_args(mess_data))
338 metadata = { 338 metadata = {
339 'rsm': self._rsm.response2dict(rsm_response), 339 'rsm': self._rsm.response2dict(rsm_response),
340 'mam': mam_response 340 'mam': mam_response
341 } 341 }
342 return mess_list, data_format.serialise(metadata), client.profile 342 return mess_list, data_format.serialise(metadata), client.profile
343 343
344 def _getArchives(self, service, extra_ser, profile_key): 344 def _get_archives(self, service, extra_ser, profile_key):
345 """ 345 """
346 @return: tuple with: 346 @return: tuple with:
347 - list of message with same data as in bridge.messageNew 347 - list of message with same data as in bridge.message_new
348 - response metadata with: 348 - response metadata with:
349 - rsm data (first, last, count, index) 349 - rsm data (first, last, count, index)
350 - mam data (complete, stable) 350 - mam data (complete, stable)
351 - profile 351 - profile
352 """ 352 """
353 client = self.host.getClient(profile_key) 353 client = self.host.get_client(profile_key)
354 service = jid.JID(service) if service else None 354 service = jid.JID(service) if service else None
355 extra = data_format.deserialise(extra_ser, {}) 355 extra = data_format.deserialise(extra_ser, {})
356 mam_req = self.parseExtra(extra) 356 mam_req = self.parse_extra(extra)
357 357
358 d = self.getArchives(client, mam_req, service=service) 358 d = self.get_archives(client, mam_req, service=service)
359 d.addCallback(self.serializeArchiveResult, client, mam_req, service) 359 d.addCallback(self.serialize_archive_result, client, mam_req, service)
360 return d 360 return d
361 361
362 def getArchives(self, client, query, service=None, message_cb=None): 362 def get_archives(self, client, query, service=None, message_cb=None):
363 """Query archive and gather page result 363 """Query archive and gather page result
364 364
365 @param query(mam.MAMRequest): MAM request 365 @param query(mam.MAMRequest): MAM request
366 @param service(jid.JID, None): MAM service to use 366 @param service(jid.JID, None): MAM service to use
367 None to use our own server 367 None to use our own server
376 """ 376 """
377 if query.query_id is None: 377 if query.query_id is None:
378 query.query_id = str(uuid.uuid4()) 378 query.query_id = str(uuid.uuid4())
379 elt_list = [] 379 elt_list = []
380 event = MESSAGE_RESULT.format(mam_ns=mam.NS_MAM, query_id=query.query_id) 380 event = MESSAGE_RESULT.format(mam_ns=mam.NS_MAM, query_id=query.query_id)
381 client.xmlstream.addObserver(event, self._appendMessage, 0, elt_list, message_cb) 381 client.xmlstream.addObserver(event, self._append_message, 0, elt_list, message_cb)
382 d = self.queryArchive(client, query, service) 382 d = self.queryArchive(client, query, service)
383 d.addCallback(self._queryFinished, client, elt_list, event) 383 d.addCallback(self._query_finished, client, elt_list, event)
384 return d 384 return d
385 385
386 def getPrefs(self, client, service=None): 386 def get_prefs(self, client, service=None):
387 """Retrieve the current user preferences. 387 """Retrieve the current user preferences.
388 388
389 @param service: entity offering the MAM service (None for user archives) 389 @param service: entity offering the MAM service (None for user archives)
390 @return: the server response as a Deferred domish.Element 390 @return: the server response as a Deferred domish.Element
391 """ 391 """
392 # http://xmpp.org/extensions/xep-0313.html#prefs 392 # http://xmpp.org/extensions/xep-0313.html#prefs
393 return client._mam.queryPrefs(service) 393 return client._mam.queryPrefs(service)
394 394
395 def _setPrefs(self, service_s=None, default="roster", always=None, never=None, 395 def _set_prefs(self, service_s=None, default="roster", always=None, never=None,
396 profile_key=C.PROF_KEY_NONE): 396 profile_key=C.PROF_KEY_NONE):
397 service = jid.JID(service_s) if service_s else None 397 service = jid.JID(service_s) if service_s else None
398 always_jid = [jid.JID(entity) for entity in always] 398 always_jid = [jid.JID(entity) for entity in always]
399 never_jid = [jid.JID(entity) for entity in never] 399 never_jid = [jid.JID(entity) for entity in never]
400 # TODO: why not build here a MAMPrefs object instead of passing the args separately? 400 # TODO: why not build here a MAMPrefs object instead of passing the args separately?
411 @return: the server response as a Deferred domish.Element 411 @return: the server response as a Deferred domish.Element
412 """ 412 """
413 # http://xmpp.org/extensions/xep-0313.html#prefs 413 # http://xmpp.org/extensions/xep-0313.html#prefs
414 return client._mam.setPrefs(service, default, always, never) 414 return client._mam.setPrefs(service, default, always, never)
415 415
416 def onMessageStanzaId(self, message_elt, client): 416 def on_message_stanza_id(self, message_elt, client):
417 """Called when a message with a stanza-id is received 417 """Called when a message with a stanza-id is received
418 418
419 the messages' stanza ids are stored when received, so the last one can be used 419 the messages' stanza ids are stored when received, so the last one can be used
420 to retrieve missing history on next connection 420 to retrieve missing history on next connection
421 @param message_elt(domish.Element): <message> with a stanza-id 421 @param message_elt(domish.Element): <message> with a stanza-id
422 """ 422 """
423 service_jid = client.jid.userhostJID() 423 service_jid = client.jid.userhostJID()
424 stanza_id = self._sid.getStanzaId(message_elt, service_jid) 424 stanza_id = self._sid.get_stanza_id(message_elt, service_jid)
425 if stanza_id is None: 425 if stanza_id is None:
426 log.debug("Ignoring <message>, stanza id is not from our server") 426 log.debug("Ignoring <message>, stanza id is not from our server")
427 else: 427 else:
428 # we use self._last_stanza_id_d do be sure that last_stanza_id is stored in 428 # we use self._last_stanza_id_d do be sure that last_stanza_id is stored in
429 # the order of reception 429 # the order of reception
430 self._last_stanza_id_d.addCallback( 430 self._last_stanza_id_d.addCallback(
431 lambda __: self.host.memory.storage.setPrivateValue( 431 lambda __: self.host.memory.storage.set_private_value(
432 namespace=mam.NS_MAM, 432 namespace=mam.NS_MAM,
433 key=KEY_LAST_STANZA_ID, 433 key=KEY_LAST_STANZA_ID,
434 value=stanza_id, 434 value=stanza_id,
435 profile=client.profile)) 435 profile=client.profile))
436 436
447 447
448 def connectionInitialized(self): 448 def connectionInitialized(self):
449 observer_xpath = MESSAGE_STANZA_ID.format( 449 observer_xpath = MESSAGE_STANZA_ID.format(
450 ns_stanza_id=self.host.ns_map['stanza_id']) 450 ns_stanza_id=self.host.ns_map['stanza_id'])
451 self.xmlstream.addObserver( 451 self.xmlstream.addObserver(
452 observer_xpath, self.plugin_parent.onMessageStanzaId, client=self.parent 452 observer_xpath, self.plugin_parent.on_message_stanza_id, client=self.parent
453 ) 453 )
454 454
455 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 455 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
456 return [disco.DiscoFeature(mam.NS_MAM)] 456 return [disco.DiscoFeature(mam.NS_MAM)]
457 457