comparison sat/plugins/plugin_xep_0096.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 849374e59178
children
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
62 self.host.plugins["XEP-0047"].NAMESPACE, 62 self.host.plugins["XEP-0047"].NAMESPACE,
63 ] # Stream methods managed 63 ] # Stream methods managed
64 self._f = self.host.plugins["FILE"] 64 self._f = self.host.plugins["FILE"]
65 self._f.register(self) 65 self._f.register(self)
66 self._si = self.host.plugins["XEP-0095"] 66 self._si = self.host.plugins["XEP-0095"]
67 self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest) 67 self._si.register_si_profile(SI_PROFILE_NAME, self._transfer_request)
68 host.bridge.addMethod( 68 host.bridge.add_method(
69 "siSendFile", ".plugin", in_sign="sssss", out_sign="s", method=self._fileSend 69 "si_file_send", ".plugin", in_sign="sssss", out_sign="s", method=self._file_send
70 ) 70 )
71 71
72 async def canHandleFileSend(self, client, peer_jid, filepath): 72 async def can_handle_file_send(self, client, peer_jid, filepath):
73 return await self.host.hasFeature(client, NS_SI_FT, peer_jid) 73 return await self.host.hasFeature(client, NS_SI_FT, peer_jid)
74 74
75 def unload(self): 75 def unload(self):
76 self._si.unregisterSIProfile(SI_PROFILE_NAME) 76 self._si.unregister_si_profile(SI_PROFILE_NAME)
77 77
78 def _badRequest(self, client, iq_elt, message=None): 78 def _bad_request(self, client, iq_elt, message=None):
79 """Send a bad-request error 79 """Send a bad-request error
80 80
81 @param iq_elt(domish.Element): initial <IQ> element of the SI request 81 @param iq_elt(domish.Element): initial <IQ> element of the SI request
82 @param message(None, unicode): informational message to display in the logs 82 @param message(None, unicode): informational message to display in the logs
83 """ 83 """
84 if message is not None: 84 if message is not None:
85 log.warning(message) 85 log.warning(message)
86 self._si.sendError(client, iq_elt, "bad-request") 86 self._si.sendError(client, iq_elt, "bad-request")
87 87
88 def _parseRange(self, parent_elt, file_size): 88 def _parse_range(self, parent_elt, file_size):
89 """find and parse <range/> element 89 """find and parse <range/> element
90 90
91 @param parent_elt(domish.Element): direct parent of the <range/> element 91 @param parent_elt(domish.Element): direct parent of the <range/> element
92 @return (tuple[bool, int, int]): a tuple with 92 @return (tuple[bool, int, int]): a tuple with
93 - True if range is required 93 - True if range is required
116 if range_offset != 0 or range_length != file_size: 116 if range_offset != 0 or range_length != file_size:
117 raise NotImplementedError # FIXME 117 raise NotImplementedError # FIXME
118 118
119 return range_, range_offset, range_length 119 return range_, range_offset, range_length
120 120
121 def _transferRequest(self, client, iq_elt, si_id, si_mime_type, si_elt): 121 def _transfer_request(self, client, iq_elt, si_id, si_mime_type, si_elt):
122 """Called when a file transfer is requested 122 """Called when a file transfer is requested
123 123
124 @param iq_elt(domish.Element): initial <IQ> element of the SI request 124 @param iq_elt(domish.Element): initial <IQ> element of the SI request
125 @param si_id(unicode): Stream Initiation session id 125 @param si_id(unicode): Stream Initiation session id
126 @param si_mime_type("unicode"): Mime type of the file (or default "application/octet-stream" if unknown) 126 @param si_mime_type("unicode"): Mime type of the file (or default "application/octet-stream" if unknown)
130 peer_jid = jid.JID(iq_elt["from"]) 130 peer_jid = jid.JID(iq_elt["from"])
131 131
132 try: 132 try:
133 file_elt = next(si_elt.elements(NS_SI_FT, "file")) 133 file_elt = next(si_elt.elements(NS_SI_FT, "file"))
134 except StopIteration: 134 except StopIteration:
135 return self._badRequest( 135 return self._bad_request(
136 client, iq_elt, "No <file/> element found in SI File Transfer request" 136 client, iq_elt, "No <file/> element found in SI File Transfer request"
137 ) 137 )
138 138
139 try: 139 try:
140 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) 140 feature_elt = self.host.plugins["XEP-0020"].get_feature_elt(si_elt)
141 except exceptions.NotFound: 141 except exceptions.NotFound:
142 return self._badRequest( 142 return self._bad_request(
143 client, iq_elt, "No <feature/> element found in SI File Transfer request" 143 client, iq_elt, "No <feature/> element found in SI File Transfer request"
144 ) 144 )
145 145
146 try: 146 try:
147 filename = file_elt["name"] 147 filename = file_elt["name"]
148 file_size = int(file_elt["size"]) 148 file_size = int(file_elt["size"])
149 except (KeyError, ValueError): 149 except (KeyError, ValueError):
150 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") 150 return self._bad_request(client, iq_elt, "Malformed SI File Transfer request")
151 151
152 file_date = file_elt.getAttribute("date") 152 file_date = file_elt.getAttribute("date")
153 file_hash = file_elt.getAttribute("hash") 153 file_hash = file_elt.getAttribute("hash")
154 154
155 log.info( 155 log.info(
162 file_desc = str(next(file_elt.elements(NS_SI_FT, "desc"))) 162 file_desc = str(next(file_elt.elements(NS_SI_FT, "desc")))
163 except StopIteration: 163 except StopIteration:
164 file_desc = "" 164 file_desc = ""
165 165
166 try: 166 try:
167 range_, range_offset, range_length = self._parseRange(file_elt, file_size) 167 range_, range_offset, range_length = self._parse_range(file_elt, file_size)
168 except ValueError: 168 except ValueError:
169 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") 169 return self._bad_request(client, iq_elt, "Malformed SI File Transfer request")
170 170
171 try: 171 try:
172 stream_method = self.host.plugins["XEP-0020"].negotiate( 172 stream_method = self.host.plugins["XEP-0020"].negotiate(
173 feature_elt, "stream-method", self.managed_stream_m, namespace=None 173 feature_elt, "stream-method", self.managed_stream_m, namespace=None
174 ) 174 )
175 except KeyError: 175 except KeyError:
176 return self._badRequest(client, iq_elt, "No stream method found") 176 return self._bad_request(client, iq_elt, "No stream method found")
177 177
178 if stream_method: 178 if stream_method:
179 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 179 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
180 plugin = self.host.plugins["XEP-0065"] 180 plugin = self.host.plugins["XEP-0065"]
181 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 181 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
205 "stream_method": stream_method, 205 "stream_method": stream_method,
206 "stream_plugin": plugin, 206 "stream_plugin": plugin,
207 } 207 }
208 208
209 d = defer.ensureDeferred( 209 d = defer.ensureDeferred(
210 self._f.getDestDir(client, peer_jid, data, data, stream_object=True) 210 self._f.get_dest_dir(client, peer_jid, data, data, stream_object=True)
211 ) 211 )
212 d.addCallback(self.confirmationCb, client, iq_elt, data) 212 d.addCallback(self.confirmation_cb, client, iq_elt, data)
213 213
214 def confirmationCb(self, accepted, client, iq_elt, data): 214 def confirmation_cb(self, accepted, client, iq_elt, data):
215 """Called on confirmation answer 215 """Called on confirmation answer
216 216
217 @param accepted(bool): True if file transfer is accepted 217 @param accepted(bool): True if file transfer is accepted
218 @param iq_elt(domish.Element): initial SI request 218 @param iq_elt(domish.Element): initial SI request
219 @param data(dict): session data 219 @param data(dict): session data
242 # del client._xep_0096_waiting_for_approval[sid] 242 # del client._xep_0096_waiting_for_approval[sid]
243 # return 243 # return
244 244
245 # file_obj = self._getFileObject(dest_path, can_range) 245 # file_obj = self._getFileObject(dest_path, can_range)
246 # range_offset = file_obj.tell() 246 # range_offset = file_obj.tell()
247 d = data["stream_plugin"].createSession( 247 d = data["stream_plugin"].create_session(
248 client, data["stream_object"], client.jid, data["peer_jid"], data["si_id"] 248 client, data["stream_object"], client.jid, data["peer_jid"], data["si_id"]
249 ) 249 )
250 d.addCallback(self._transferCb, client, data) 250 d.addCallback(self._transfer_cb, client, data)
251 d.addErrback(self._transferEb, client, data) 251 d.addErrback(self._transfer_eb, client, data)
252 252
253 # we can send the iq result 253 # we can send the iq result
254 feature_elt = self.host.plugins["XEP-0020"].chooseOption( 254 feature_elt = self.host.plugins["XEP-0020"].choose_option(
255 {"stream-method": data["stream_method"]}, namespace=None 255 {"stream-method": data["stream_method"]}, namespace=None
256 ) 256 )
257 misc_elts = [] 257 misc_elts = []
258 misc_elts.append(domish.Element((SI_PROFILE, "file"))) 258 misc_elts.append(domish.Element((SI_PROFILE, "file")))
259 # if can_range: 259 # if can_range:
260 # range_elt = domish.Element((None, "range")) 260 # range_elt = domish.Element((None, "range"))
261 # range_elt['offset'] = str(range_offset) 261 # range_elt['offset'] = str(range_offset)
262 # #TODO: manage range length 262 # #TODO: manage range length
263 # misc_elts.append(range_elt) 263 # misc_elts.append(range_elt)
264 self._si.acceptStream(client, iq_elt, feature_elt, misc_elts) 264 self._si.accept_stream(client, iq_elt, feature_elt, misc_elts)
265 265
266 def _transferCb(self, __, client, data): 266 def _transfer_cb(self, __, client, data):
267 """Called by the stream method when transfer successfuly finished 267 """Called by the stream method when transfer successfuly finished
268 268
269 @param data: session data 269 @param data: session data
270 """ 270 """
271 # TODO: check hash 271 # TODO: check hash
272 data["stream_object"].close() 272 data["stream_object"].close()
273 log.info("Transfer {si_id} successfuly finished".format(**data)) 273 log.info("Transfer {si_id} successfuly finished".format(**data))
274 274
275 def _transferEb(self, failure, client, data): 275 def _transfer_eb(self, failure, client, data):
276 """Called when something went wrong with the transfer 276 """Called when something went wrong with the transfer
277 277
278 @param id: stream id 278 @param id: stream id
279 @param data: session data 279 @param data: session data
280 """ 280 """
283 reason=str(failure.value), **data 283 reason=str(failure.value), **data
284 ) 284 )
285 ) 285 )
286 data["stream_object"].close() 286 data["stream_object"].close()
287 287
288 def _fileSend(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE): 288 def _file_send(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE):
289 client = self.host.getClient(profile) 289 client = self.host.get_client(profile)
290 return self.fileSend( 290 return self.file_send(
291 client, jid.JID(peer_jid_s), filepath, name or None, desc or None 291 client, jid.JID(peer_jid_s), filepath, name or None, desc or None
292 ) 292 )
293 293
294 def fileSend(self, client, peer_jid, filepath, name=None, desc=None, extra=None): 294 def file_send(self, client, peer_jid, filepath, name=None, desc=None, extra=None):
295 """Send a file using XEP-0096 295 """Send a file using XEP-0096
296 296
297 @param peer_jid(jid.JID): recipient 297 @param peer_jid(jid.JID): recipient
298 @param filepath(str): absolute path to the file to send 298 @param filepath(str): absolute path to the file to send
299 @param name(unicode): name of the file to send 299 @param name(unicode): name of the file to send
300 name must not contain "/" characters 300 name must not contain "/" characters
301 @param desc: description of the file 301 @param desc: description of the file
302 @param extra: not used here 302 @param extra: not used here
303 @return: an unique id to identify the transfer 303 @return: an unique id to identify the transfer
304 """ 304 """
305 feature_elt = self.host.plugins["XEP-0020"].proposeFeatures( 305 feature_elt = self.host.plugins["XEP-0020"].propose_features(
306 {"stream-method": self.managed_stream_m}, namespace=None 306 {"stream-method": self.managed_stream_m}, namespace=None
307 ) 307 )
308 308
309 file_transfer_elts = [] 309 file_transfer_elts = []
310 310
318 file_elt.addElement("desc", content=desc) 318 file_elt.addElement("desc", content=desc)
319 file_transfer_elts.append(file_elt) 319 file_transfer_elts.append(file_elt)
320 320
321 file_transfer_elts.append(domish.Element((None, "range"))) 321 file_transfer_elts.append(domish.Element((None, "range")))
322 322
323 sid, offer_d = self._si.proposeStream( 323 sid, offer_d = self._si.propose_stream(
324 client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts 324 client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts
325 ) 325 )
326 args = [filepath, sid, size, client] 326 args = [filepath, sid, size, client]
327 offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args) 327 offer_d.addCallbacks(self._file_cb, self._file_eb, args, None, args)
328 return sid 328 return sid
329 329
330 def _fileCb(self, result_tuple, filepath, sid, size, client): 330 def _file_cb(self, result_tuple, filepath, sid, size, client):
331 iq_elt, si_elt = result_tuple 331 iq_elt, si_elt = result_tuple
332 332
333 try: 333 try:
334 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) 334 feature_elt = self.host.plugins["XEP-0020"].get_feature_elt(si_elt)
335 except exceptions.NotFound: 335 except exceptions.NotFound:
336 log.warning("No <feature/> element found in result while expected") 336 log.warning("No <feature/> element found in result while expected")
337 return 337 return
338 338
339 choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions( 339 choosed_options = self.host.plugins["XEP-0020"].get_choosed_options(
340 feature_elt, namespace=None 340 feature_elt, namespace=None
341 ) 341 )
342 try: 342 try:
343 stream_method = choosed_options["stream-method"] 343 stream_method = choosed_options["stream-method"]
344 except KeyError: 344 except KeyError:
348 try: 348 try:
349 file_elt = next(si_elt.elements(NS_SI_FT, "file")) 349 file_elt = next(si_elt.elements(NS_SI_FT, "file"))
350 except StopIteration: 350 except StopIteration:
351 pass 351 pass
352 else: 352 else:
353 range_, range_offset, range_length = self._parseRange(file_elt, size) 353 range_, range_offset, range_length = self._parse_range(file_elt, size)
354 354
355 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 355 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
356 plugin = self.host.plugins["XEP-0065"] 356 plugin = self.host.plugins["XEP-0065"]
357 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 357 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
358 plugin = self.host.plugins["XEP-0047"] 358 plugin = self.host.plugins["XEP-0047"]
361 return 361 return
362 362
363 stream_object = stream.FileStreamObject( 363 stream_object = stream.FileStreamObject(
364 self.host, client, filepath, uid=sid, size=size 364 self.host, client, filepath, uid=sid, size=size
365 ) 365 )
366 d = plugin.startStream(client, stream_object, client.jid, 366 d = plugin.start_stream(client, stream_object, client.jid,
367 jid.JID(iq_elt["from"]), sid) 367 jid.JID(iq_elt["from"]), sid)
368 d.addCallback(self._sendCb, client, sid, stream_object) 368 d.addCallback(self._send_cb, client, sid, stream_object)
369 d.addErrback(self._sendEb, client, sid, stream_object) 369 d.addErrback(self._send_eb, client, sid, stream_object)
370 370
371 def _fileEb(self, failure, filepath, sid, size, client): 371 def _file_eb(self, failure, filepath, sid, size, client):
372 if failure.check(error.StanzaError): 372 if failure.check(error.StanzaError):
373 stanza_err = failure.value 373 stanza_err = failure.value
374 if stanza_err.code == "403" and stanza_err.condition == "forbidden": 374 if stanza_err.code == "403" and stanza_err.condition == "forbidden":
375 from_s = stanza_err.stanza["from"] 375 from_s = stanza_err.stanza["from"]
376 log.info("File transfer refused by {}".format(from_s)) 376 log.info("File transfer refused by {}".format(from_s))
377 msg = D_("The contact {} has refused your file").format(from_s) 377 msg = D_("The contact {} has refused your file").format(from_s)
378 title = D_("File refused") 378 title = D_("File refused")
379 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) 379 xml_tools.quick_note(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO)
380 else: 380 else:
381 log.warning(_("Error during file transfer")) 381 log.warning(_("Error during file transfer"))
382 msg = D_( 382 msg = D_(
383 "Something went wrong during the file transfer session initialisation: {reason}" 383 "Something went wrong during the file transfer session initialisation: {reason}"
384 ).format(reason=str(stanza_err)) 384 ).format(reason=str(stanza_err))
385 title = D_("File transfer error") 385 title = D_("File transfer error")
386 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR) 386 xml_tools.quick_note(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR)
387 elif failure.check(exceptions.DataError): 387 elif failure.check(exceptions.DataError):
388 log.warning("Invalid stanza received") 388 log.warning("Invalid stanza received")
389 else: 389 else:
390 log.error("Error while proposing stream: {}".format(failure)) 390 log.error("Error while proposing stream: {}".format(failure))
391 391
392 def _sendCb(self, __, client, sid, stream_object): 392 def _send_cb(self, __, client, sid, stream_object):
393 log.info( 393 log.info(
394 _("transfer {sid} successfuly finished [{profile}]").format( 394 _("transfer {sid} successfuly finished [{profile}]").format(
395 sid=sid, profile=client.profile 395 sid=sid, profile=client.profile
396 ) 396 )
397 ) 397 )
398 stream_object.close() 398 stream_object.close()
399 399
400 def _sendEb(self, failure, client, sid, stream_object): 400 def _send_eb(self, failure, client, sid, stream_object):
401 log.warning( 401 log.warning(
402 _("transfer {sid} failed [{profile}]: {reason}").format( 402 _("transfer {sid} failed [{profile}]: {reason}").format(
403 sid=sid, profile=client.profile, reason=str(failure.value) 403 sid=sid, profile=client.profile, reason=str(failure.value)
404 ) 404 )
405 ) 405 )