comparison sat/plugins/plugin_xep_0096.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 26edcf3a30eb
children 378188abe941
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _, D_ 20 from sat.core.i18n import _, D_
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23
23 log = getLogger(__name__) 24 log = getLogger(__name__)
24 from sat.core import exceptions 25 from sat.core import exceptions
25 from sat.tools import xml_tools 26 from sat.tools import xml_tools
26 from sat.tools import stream 27 from sat.tools import stream
27 from twisted.words.xish import domish 28 from twisted.words.xish import domish
41 C.PI_TYPE: "XEP", 42 C.PI_TYPE: "XEP",
42 C.PI_PROTOCOLS: ["XEP-0096"], 43 C.PI_PROTOCOLS: ["XEP-0096"],
43 C.PI_DEPENDENCIES: ["XEP-0020", "XEP-0095", "XEP-0065", "XEP-0047", "FILE"], 44 C.PI_DEPENDENCIES: ["XEP-0020", "XEP-0095", "XEP-0065", "XEP-0047", "FILE"],
44 C.PI_MAIN: "XEP_0096", 45 C.PI_MAIN: "XEP_0096",
45 C.PI_HANDLER: "no", 46 C.PI_HANDLER: "no",
46 C.PI_DESCRIPTION: _("""Implementation of SI File Transfer""") 47 C.PI_DESCRIPTION: _("""Implementation of SI File Transfer"""),
47 } 48 }
48 49
49 50
50 class XEP_0096(object): 51 class XEP_0096(object):
51 # TODO: call self._f.unregister when unloading order will be managing (i.e. when depenencies will be unloaded at the end) 52 # TODO: call self._f.unregister when unloading order will be managing (i.e. when depenencies will be unloaded at the end)
52 53
53 def __init__(self, host): 54 def __init__(self, host):
54 log.info(_("Plugin XEP_0096 initialization")) 55 log.info(_("Plugin XEP_0096 initialization"))
55 self.host = host 56 self.host = host
56 self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE, 57 self.managed_stream_m = [
57 self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed 58 self.host.plugins["XEP-0065"].NAMESPACE,
59 self.host.plugins["XEP-0047"].NAMESPACE,
60 ] # Stream methods managed
58 self._f = self.host.plugins["FILE"] 61 self._f = self.host.plugins["FILE"]
59 self._f.register(NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation") 62 self._f.register(
63 NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation"
64 )
60 self._si = self.host.plugins["XEP-0095"] 65 self._si = self.host.plugins["XEP-0095"]
61 self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest) 66 self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest)
62 host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile) 67 host.bridge.addMethod(
68 "siSendFile", ".plugin", in_sign="sssss", out_sign="s", method=self._sendFile
69 )
63 70
64 def unload(self): 71 def unload(self):
65 self._si.unregisterSIProfile(SI_PROFILE_NAME) 72 self._si.unregisterSIProfile(SI_PROFILE_NAME)
66 73
67 def _badRequest(self, client, iq_elt, message=None): 74 def _badRequest(self, client, iq_elt, message=None):
70 @param iq_elt(domish.Element): initial <IQ> element of the SI request 77 @param iq_elt(domish.Element): initial <IQ> element of the SI request
71 @param message(None, unicode): informational message to display in the logs 78 @param message(None, unicode): informational message to display in the logs
72 """ 79 """
73 if message is not None: 80 if message is not None:
74 log.warning(message) 81 log.warning(message)
75 self._si.sendError(client, iq_elt, 'bad-request') 82 self._si.sendError(client, iq_elt, "bad-request")
76 83
77 def _parseRange(self, parent_elt, file_size): 84 def _parseRange(self, parent_elt, file_size):
78 """find and parse <range/> element 85 """find and parse <range/> element
79 86
80 @param parent_elt(domish.Element): direct parent of the <range/> element 87 @param parent_elt(domish.Element): direct parent of the <range/> element
82 - True if range is required 89 - True if range is required
83 - range_offset 90 - range_offset
84 - range_length 91 - range_length
85 """ 92 """
86 try: 93 try:
87 range_elt = parent_elt.elements(NS_SI_FT, 'range').next() 94 range_elt = parent_elt.elements(NS_SI_FT, "range").next()
88 except StopIteration: 95 except StopIteration:
89 range_ = False 96 range_ = False
90 range_offset = None 97 range_offset = None
91 range_length = None 98 range_length = None
92 else: 99 else:
93 range_ = True 100 range_ = True
94 101
95 try: 102 try:
96 range_offset = int(range_elt['offset']) 103 range_offset = int(range_elt["offset"])
97 except KeyError: 104 except KeyError:
98 range_offset = 0 105 range_offset = 0
99 106
100 try: 107 try:
101 range_length = int(range_elt['length']) 108 range_length = int(range_elt["length"])
102 except KeyError: 109 except KeyError:
103 range_length = file_size 110 range_length = file_size
104 111
105 if range_offset != 0 or range_length != file_size: 112 if range_offset != 0 or range_length != file_size:
106 raise NotImplementedError # FIXME 113 raise NotImplementedError # FIXME
107 114
108 return range_, range_offset, range_length 115 return range_, range_offset, range_length
109 116
110 def _transferRequest(self, client, iq_elt, si_id, si_mime_type, si_elt): 117 def _transferRequest(self, client, iq_elt, si_id, si_mime_type, si_elt):
111 """Called when a file transfer is requested 118 """Called when a file transfer is requested
114 @param si_id(unicode): Stream Initiation session id 121 @param si_id(unicode): Stream Initiation session id
115 @param si_mime_type("unicode"): Mime type of the file (or default "application/octet-stream" if unknown) 122 @param si_mime_type("unicode"): Mime type of the file (or default "application/octet-stream" if unknown)
116 @param si_elt(domish.Element): request 123 @param si_elt(domish.Element): request
117 """ 124 """
118 log.info(_("XEP-0096 file transfer requested")) 125 log.info(_("XEP-0096 file transfer requested"))
119 peer_jid = jid.JID(iq_elt['from']) 126 peer_jid = jid.JID(iq_elt["from"])
120 127
121 try: 128 try:
122 file_elt = si_elt.elements(NS_SI_FT, "file").next() 129 file_elt = si_elt.elements(NS_SI_FT, "file").next()
123 except StopIteration: 130 except StopIteration:
124 return self._badRequest(client, iq_elt, "No <file/> element found in SI File Transfer request") 131 return self._badRequest(
132 client, iq_elt, "No <file/> element found in SI File Transfer request"
133 )
125 134
126 try: 135 try:
127 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) 136 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
128 except exceptions.NotFound: 137 except exceptions.NotFound:
129 return self._badRequest(client, iq_elt, "No <feature/> element found in SI File Transfer request") 138 return self._badRequest(
139 client, iq_elt, "No <feature/> element found in SI File Transfer request"
140 )
130 141
131 try: 142 try:
132 filename = file_elt["name"] 143 filename = file_elt["name"]
133 file_size = int(file_elt["size"]) 144 file_size = int(file_elt["size"])
134 except (KeyError, ValueError): 145 except (KeyError, ValueError):
135 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") 146 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request")
136 147
137 file_date = file_elt.getAttribute("date") 148 file_date = file_elt.getAttribute("date")
138 file_hash = file_elt.getAttribute("hash") 149 file_hash = file_elt.getAttribute("hash")
139 150
140 log.info(u"File proposed: name=[{name}] size={size}".format(name=filename, size=file_size)) 151 log.info(
141 152 u"File proposed: name=[{name}] size={size}".format(
142 try: 153 name=filename, size=file_size
143 file_desc = unicode(file_elt.elements(NS_SI_FT, 'desc').next()) 154 )
155 )
156
157 try:
158 file_desc = unicode(file_elt.elements(NS_SI_FT, "desc").next())
144 except StopIteration: 159 except StopIteration:
145 file_desc = '' 160 file_desc = ""
146 161
147 try: 162 try:
148 range_, range_offset, range_length = self._parseRange(file_elt, file_size) 163 range_, range_offset, range_length = self._parseRange(file_elt, file_size)
149 except ValueError: 164 except ValueError:
150 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") 165 return self._badRequest(client, iq_elt, "Malformed SI File Transfer request")
151 166
152 try: 167 try:
153 stream_method = self.host.plugins["XEP-0020"].negotiate(feature_elt, 'stream-method', self.managed_stream_m, namespace=None) 168 stream_method = self.host.plugins["XEP-0020"].negotiate(
169 feature_elt, "stream-method", self.managed_stream_m, namespace=None
170 )
154 except KeyError: 171 except KeyError:
155 return self._badRequest(client, iq_elt, "No stream method found") 172 return self._badRequest(client, iq_elt, "No stream method found")
156 173
157 if stream_method: 174 if stream_method:
158 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 175 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
159 plugin = self.host.plugins["XEP-0065"] 176 plugin = self.host.plugins["XEP-0065"]
160 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 177 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
161 plugin = self.host.plugins["XEP-0047"] 178 plugin = self.host.plugins["XEP-0047"]
162 else: 179 else:
163 log.error(u"Unknown stream method, this should not happen at this stage, cancelling transfer") 180 log.error(
181 u"Unknown stream method, this should not happen at this stage, cancelling transfer"
182 )
164 else: 183 else:
165 log.warning(u"Can't find a valid stream method") 184 log.warning(u"Can't find a valid stream method")
166 self._si.sendError(client, iq_elt, 'not-acceptable') 185 self._si.sendError(client, iq_elt, "not-acceptable")
167 return 186 return
168 187
169 #if we are here, the transfer can start, we just need user's agreement 188 # if we are here, the transfer can start, we just need user's agreement
170 data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc, 189 data = {
171 "range": range_, "range_offset": range_offset, "range_length": range_length, 190 "name": filename,
172 "si_id": si_id, "progress_id": si_id, "stream_method": stream_method, "stream_plugin": plugin} 191 "peer_jid": peer_jid,
192 "size": file_size,
193 "date": file_date,
194 "hash": file_hash,
195 "desc": file_desc,
196 "range": range_,
197 "range_offset": range_offset,
198 "range_length": range_length,
199 "si_id": si_id,
200 "progress_id": si_id,
201 "stream_method": stream_method,
202 "stream_plugin": plugin,
203 }
173 204
174 d = self._f.getDestDir(client, peer_jid, data, data, stream_object=True) 205 d = self._f.getDestDir(client, peer_jid, data, data, stream_object=True)
175 d.addCallback(self.confirmationCb, client, iq_elt, data) 206 d.addCallback(self.confirmationCb, client, iq_elt, data)
176 207
177 def confirmationCb(self, accepted, client, iq_elt, data): 208 def confirmationCb(self, accepted, client, iq_elt, data):
181 @param iq_elt(domish.Element): initial SI request 212 @param iq_elt(domish.Element): initial SI request
182 @param data(dict): session data 213 @param data(dict): session data
183 """ 214 """
184 if not accepted: 215 if not accepted:
185 log.info(u"File transfer declined") 216 log.info(u"File transfer declined")
186 self._si.sendError(client, iq_elt, 'forbidden') 217 self._si.sendError(client, iq_elt, "forbidden")
187 return 218 return
188 # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid] 219 # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
189 # can_range = data['can_range'] == "True" 220 # can_range = data['can_range'] == "True"
190 # range_offset = 0 221 # range_offset = 0
191 # if timeout.active(): 222 # if timeout.active():
205 # del client._xep_0096_waiting_for_approval[sid] 236 # del client._xep_0096_waiting_for_approval[sid]
206 # return 237 # return
207 238
208 # file_obj = self._getFileObject(dest_path, can_range) 239 # file_obj = self._getFileObject(dest_path, can_range)
209 # range_offset = file_obj.tell() 240 # range_offset = file_obj.tell()
210 d = data['stream_plugin'].createSession(client, data['stream_object'], data['peer_jid'], data['si_id']) 241 d = data["stream_plugin"].createSession(
242 client, data["stream_object"], data["peer_jid"], data["si_id"]
243 )
211 d.addCallback(self._transferCb, client, data) 244 d.addCallback(self._transferCb, client, data)
212 d.addErrback(self._transferEb, client, data) 245 d.addErrback(self._transferEb, client, data)
213 246
214 #we can send the iq result 247 # we can send the iq result
215 feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': data['stream_method']}, namespace=None) 248 feature_elt = self.host.plugins["XEP-0020"].chooseOption(
249 {"stream-method": data["stream_method"]}, namespace=None
250 )
216 misc_elts = [] 251 misc_elts = []
217 misc_elts.append(domish.Element((SI_PROFILE, "file"))) 252 misc_elts.append(domish.Element((SI_PROFILE, "file")))
218 # if can_range: 253 # if can_range:
219 # range_elt = domish.Element((None, "range")) 254 # range_elt = domish.Element((None, "range"))
220 # range_elt['offset'] = str(range_offset) 255 # range_elt['offset'] = str(range_offset)
225 def _transferCb(self, dummy, client, data): 260 def _transferCb(self, dummy, client, data):
226 """Called by the stream method when transfer successfuly finished 261 """Called by the stream method when transfer successfuly finished
227 262
228 @param data: session data 263 @param data: session data
229 """ 264 """
230 #TODO: check hash 265 # TODO: check hash
231 data['stream_object'].close() 266 data["stream_object"].close()
232 log.info(u'Transfer {si_id} successfuly finished'.format(**data)) 267 log.info(u"Transfer {si_id} successfuly finished".format(**data))
233 268
234 def _transferEb(self, failure, client, data): 269 def _transferEb(self, failure, client, data):
235 """Called when something went wrong with the transfer 270 """Called when something went wrong with the transfer
236 271
237 @param id: stream id 272 @param id: stream id
238 @param data: session data 273 @param data: session data
239 """ 274 """
240 log.warning(u'Transfer {si_id} failed: {reason}'.format(reason=unicode(failure.value), **data)) 275 log.warning(
241 data['stream_object'].close() 276 u"Transfer {si_id} failed: {reason}".format(
277 reason=unicode(failure.value), **data
278 )
279 )
280 data["stream_object"].close()
242 281
243 def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE): 282 def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE):
244 client = self.host.getClient(profile) 283 client = self.host.getClient(profile)
245 return self.sendFile(client, jid.JID(peer_jid_s), filepath, name or None, desc or None) 284 return self.sendFile(
285 client, jid.JID(peer_jid_s), filepath, name or None, desc or None
286 )
246 287
247 def sendFile(self, client, peer_jid, filepath, name=None, desc=None, extra=None): 288 def sendFile(self, client, peer_jid, filepath, name=None, desc=None, extra=None):
248 """Send a file using XEP-0096 289 """Send a file using XEP-0096
249 290
250 @param peer_jid(jid.JID): recipient 291 @param peer_jid(jid.JID): recipient
253 name must not contain "/" characters 294 name must not contain "/" characters
254 @param desc: description of the file 295 @param desc: description of the file
255 @param extra: not used here 296 @param extra: not used here
256 @return: an unique id to identify the transfer 297 @return: an unique id to identify the transfer
257 """ 298 """
258 feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}, namespace=None) 299 feature_elt = self.host.plugins["XEP-0020"].proposeFeatures(
300 {"stream-method": self.managed_stream_m}, namespace=None
301 )
259 302
260 file_transfer_elts = [] 303 file_transfer_elts = []
261 304
262 statinfo = os.stat(filepath) 305 statinfo = os.stat(filepath)
263 file_elt = domish.Element((SI_PROFILE, 'file')) 306 file_elt = domish.Element((SI_PROFILE, "file"))
264 file_elt['name'] = name or os.path.basename(filepath) 307 file_elt["name"] = name or os.path.basename(filepath)
265 assert '/' not in file_elt['name'] 308 assert "/" not in file_elt["name"]
266 size = statinfo.st_size 309 size = statinfo.st_size
267 file_elt['size'] = str(size) 310 file_elt["size"] = str(size)
268 if desc: 311 if desc:
269 file_elt.addElement('desc', content=desc) 312 file_elt.addElement("desc", content=desc)
270 file_transfer_elts.append(file_elt) 313 file_transfer_elts.append(file_elt)
271 314
272 file_transfer_elts.append(domish.Element((None, 'range'))) 315 file_transfer_elts.append(domish.Element((None, "range")))
273 316
274 sid, offer_d = self._si.proposeStream(client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts) 317 sid, offer_d = self._si.proposeStream(
318 client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts
319 )
275 args = [filepath, sid, size, client] 320 args = [filepath, sid, size, client]
276 offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args) 321 offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args)
277 return sid 322 return sid
278 323
279 def _fileCb(self, result_tuple, filepath, sid, size, client): 324 def _fileCb(self, result_tuple, filepath, sid, size, client):
283 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) 328 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
284 except exceptions.NotFound: 329 except exceptions.NotFound:
285 log.warning(u"No <feature/> element found in result while expected") 330 log.warning(u"No <feature/> element found in result while expected")
286 return 331 return
287 332
288 choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elt, namespace=None) 333 choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(
334 feature_elt, namespace=None
335 )
289 try: 336 try:
290 stream_method = choosed_options["stream-method"] 337 stream_method = choosed_options["stream-method"]
291 except KeyError: 338 except KeyError:
292 log.warning(u"No stream method choosed") 339 log.warning(u"No stream method choosed")
293 return 340 return
305 plugin = self.host.plugins["XEP-0047"] 352 plugin = self.host.plugins["XEP-0047"]
306 else: 353 else:
307 log.warning(u"Invalid stream method received") 354 log.warning(u"Invalid stream method received")
308 return 355 return
309 356
310 stream_object = stream.FileStreamObject(self.host, 357 stream_object = stream.FileStreamObject(
311 client, 358 self.host, client, filepath, uid=sid, size=size
312 filepath, 359 )
313 uid=sid, 360 d = plugin.startStream(client, stream_object, jid.JID(iq_elt["from"]), sid)
314 size=size,
315 )
316 d = plugin.startStream(client, stream_object, jid.JID(iq_elt['from']), sid)
317 d.addCallback(self._sendCb, client, sid, stream_object) 361 d.addCallback(self._sendCb, client, sid, stream_object)
318 d.addErrback(self._sendEb, client, sid, stream_object) 362 d.addErrback(self._sendEb, client, sid, stream_object)
319 363
320 def _fileEb(self, failure, filepath, sid, size, client): 364 def _fileEb(self, failure, filepath, sid, size, client):
321 if failure.check(error.StanzaError): 365 if failure.check(error.StanzaError):
322 stanza_err = failure.value 366 stanza_err = failure.value
323 if stanza_err.code == '403' and stanza_err.condition == 'forbidden': 367 if stanza_err.code == "403" and stanza_err.condition == "forbidden":
324 from_s = stanza_err.stanza['from'] 368 from_s = stanza_err.stanza["from"]
325 log.info(u"File transfer refused by {}".format(from_s)) 369 log.info(u"File transfer refused by {}".format(from_s))
326 msg = D_(u"The contact {} has refused your file").format(from_s) 370 msg = D_(u"The contact {} has refused your file").format(from_s)
327 title = D_(u"File refused") 371 title = D_(u"File refused")
328 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) 372 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO)
329 else: 373 else:
330 log.warning(_(u"Error during file transfer")) 374 log.warning(_(u"Error during file transfer"))
331 msg = D_(u"Something went wrong during the file transfer session initialisation: {reason}").format(reason=unicode(stanza_err)) 375 msg = D_(
376 u"Something went wrong during the file transfer session initialisation: {reason}"
377 ).format(reason=unicode(stanza_err))
332 title = D_(u"File transfer error") 378 title = D_(u"File transfer error")
333 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR) 379 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR)
334 elif failure.check(exceptions.DataError): 380 elif failure.check(exceptions.DataError):
335 log.warning(u'Invalid stanza received') 381 log.warning(u"Invalid stanza received")
336 else: 382 else:
337 log.error(u'Error while proposing stream: {}'.format(failure)) 383 log.error(u"Error while proposing stream: {}".format(failure))
338 384
339 def _sendCb(self, dummy, client, sid, stream_object): 385 def _sendCb(self, dummy, client, sid, stream_object):
340 log.info(_(u'transfer {sid} successfuly finished [{profile}]').format( 386 log.info(
341 sid=sid, 387 _(u"transfer {sid} successfuly finished [{profile}]").format(
342 profile=client.profile)) 388 sid=sid, profile=client.profile
389 )
390 )
343 stream_object.close() 391 stream_object.close()
344 392
345 def _sendEb(self, failure, client, sid, stream_object): 393 def _sendEb(self, failure, client, sid, stream_object):
346 log.warning(_(u'transfer {sid} failed [{profile}]: {reason}').format( 394 log.warning(
347 sid=sid, 395 _(u"transfer {sid} failed [{profile}]: {reason}").format(
348 profile=client.profile, 396 sid=sid, profile=client.profile, reason=unicode(failure.value)
349 reason=unicode(failure.value), 397 )
350 )) 398 )
351 stream_object.close() 399 stream_object.close()