comparison src/plugins/plugin_xep_0096.py @ 1577:d04d7402b8e9

plugins XEP-0020, XEP-0065, XEP-0095, XEP-0096: fixed file copy with Stream Initiation: /!\ range is not working yet /!\ pipe plugin is broken for now
author Goffi <goffi@goffi.org>
date Wed, 11 Nov 2015 18:19:49 +0100
parents 7cc29634b6ef
children d46aae87c03a
comparison
equal deleted inserted replaced
1576:d5f59ba166fe 1577:d04d7402b8e9
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
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 log = getLogger(__name__) 23 log = getLogger(__name__)
24 from sat.core import exceptions
24 from twisted.words.xish import domish 25 from twisted.words.xish import domish
25 from twisted.words.protocols.jabber import jid 26 from twisted.words.protocols.jabber import jid
26 from twisted.words.protocols import jabber 27 from twisted.words.protocols.jabber import error
27 import os 28 import os
28 from twisted.internet import reactor 29
29 from twisted.python import failure 30
30 31 NS_SI_FT = "http://jabber.org/protocol/si/profile/file-transfer"
31 from wokkel import data_form
32
33 IQ_SET = '/iq[@type="set"]' 32 IQ_SET = '/iq[@type="set"]'
34 PROFILE_NAME = "file-transfer" 33 SI_PROFILE_NAME = "file-transfer"
35 PROFILE = "http://jabber.org/protocol/si/profile/" + PROFILE_NAME 34 SI_PROFILE = "http://jabber.org/protocol/si/profile/" + SI_PROFILE_NAME
36 35
37 PLUGIN_INFO = { 36 PLUGIN_INFO = {
38 "name": "XEP-0096 Plugin", 37 "name": "XEP-0096 Plugin",
39 "import_name": "XEP-0096", 38 "import_name": "XEP-0096",
40 "type": "XEP", 39 "type": "XEP",
51 def __init__(self, host): 50 def __init__(self, host):
52 log.info(_("Plugin XEP_0096 initialization")) 51 log.info(_("Plugin XEP_0096 initialization"))
53 self.host = host 52 self.host = host
54 self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE, 53 self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
55 self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed 54 self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed
56 self.host.plugins["XEP-0095"].registerSIProfile(PROFILE_NAME, self.transferRequest) 55 self._f = self.host.plugins["FILE"]
57 host.bridge.addMethod("sendFile", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self.sendFile) 56 self._si = self.host.plugins["XEP-0095"]
58 57 self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest)
59 def profileConnected(self, profile): 58 host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile)
60 client = self.host.getClient(profile) 59
61 client._xep_0096_waiting_for_approval = {} # key = id, value = [transfer data, IdelayedCall Reactor timeout, 60 def unload(self):
62 # current stream method, [failed stream methods], profile] 61 self._si.unregisterSIProfile(SI_PROFILE_NAME)
63 62
64 def _kill_id(self, approval_id, profile): 63 def _badRequest(self, iq_elt, message=None, profile=C.PROF_KEY_NONE):
65 """Delete a waiting_for_approval id, called after timeout 64 """Send a bad-request error
66 @param approval_id: id of _xep_0096_waiting_for_approval""" 65
67 log.info(_("SI File Transfer: TimeOut reached for id %s") % approval_id) 66 @param iq_elt(domish.Element): initial <IQ> element of the SI request
68 try: 67 @param message(None, unicode): informational message to display in the logs
69 client = self.host.getClient(profile) 68 @param profile: %(doc_profile)s
70 del client._xep_0096_waiting_for_approval[approval_id] 69 """
70 if message is not None:
71 log.warning(message)
72 self._si.sendError(iq_elt, 'bad-request', profile)
73
74 def _parseRange(self, parent_elt, file_size):
75 """find and parse <range/> element
76
77 @param parent_elt(domish.Element): direct parent of the <range/> element
78 @return (tuple[bool, int, int]): a tuple with
79 - True if range is required
80 - range_offset
81 - range_length
82 """
83 try:
84 range_elt = parent_elt.elements(NS_SI_FT, 'range').next()
85 except StopIteration:
86 range_ = False
87 range_offset = None
88 range_length = None
89 else:
90 range_ = True
91
92 try:
93 range_offset = int(range_elt['offset'])
94 except KeyError:
95 range_offset = 0
96
97 try:
98 range_length = int(range_elt['length'])
99 except KeyError:
100 range_length = file_size
101
102 if range_offset != 0 or range_length != file_size:
103 raise NotImplementedError # FIXME
104
105 return range_, range_offset, range_length
106
107 def _transferRequest(self, iq_elt, si_id, si_mime_type, si_elt, profile):
108 """Called when a file transfer is requested
109
110 @param iq_elt(domish.Element): initial <IQ> element of the SI request
111 @param si_id(unicode): Stream Initiation session id
112 @param si_mime_type("unicode"): Mime type of the file (or default "application/octet-stream" if unknown)
113 @param si_elt(domish.Element): request
114 @param profile: %(doc_profile)s
115 """
116 log.info(_("XEP-0096 file transfer requested"))
117 peer_jid = jid.JID(iq_elt['from'])
118
119 try:
120 file_elt = si_elt.elements(NS_SI_FT, "file").next()
121 except StopIteration:
122 return self._badRequest(iq_elt, "No <file/> element found in SI File Transfer request", profile)
123
124 try:
125 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
126 except exceptions.NotFound:
127 return self._badRequest(iq_elt, "No <feature/> element found in SI File Transfer request", profile)
128
129 try:
130 filename = file_elt["name"]
131 file_size = int(file_elt["size"])
132 except (KeyError, ValueError):
133 return self._badRequest(iq_elt, "Malformed SI File Transfer request", profile)
134
135 file_date = file_elt.getAttribute("date")
136 file_hash = file_elt.getAttribute("hash")
137
138 log.info(u"File proposed: name=[{name}] size={size}".format(name=filename, size=file_size))
139
140 try:
141 file_desc = unicode(file_elt.elements(NS_SI_FT, 'desc').next())
142 except StopIteration:
143 file_desc = ''
144
145 try:
146 range_, range_offset, range_length = self._parseRange(file_elt, file_size)
147 except ValueError:
148 return self._badRequest(iq_elt, "Malformed SI File Transfer request", profile)
149
150 try:
151 stream_method = self.host.plugins["XEP-0020"].negotiate(feature_elt, 'stream-method', self.managed_stream_m, namespace=None)
71 except KeyError: 152 except KeyError:
72 log.warning(_("kill id called on a non existant approval id")) 153 return self._badRequest(iq_elt, "No stream method found", profile)
73 154
74 def transferRequest(self, iq_id, from_jid, si_id, si_mime_type, si_el, profile): 155 if stream_method:
75 """Called when a file transfer is requested 156 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
76 @param iq_id: id of the iq request 157 plugin = self.host.plugins["XEP-0065"]
77 @param from_jid: jid of the sender 158 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
78 @param si_id: Stream Initiation session id 159 plugin = self.host.plugins["XEP-0047"]
79 @param si_mime_type: Mime type of the file (or default "application/octet-stream" if unknown) 160 else:
80 @param si_el: domish.Element of the request 161 log.error(_("Unknown stream method, this should not happen at this stage, cancelling transfer"))
81 @param profile: %(doc_profile)s""" 162 else:
82 log.info(_("XEP-0096 file transfer requested")) 163 log.warning(_("Can't find a valid stream method"))
83 log.debug(si_el.toXml()) 164 self._si.sendError(iq_elt, 'not-acceptable', profile)
84 client = self.host.getClient(profile)
85 filename = ""
86 file_size = ""
87 file_date = None
88 file_hash = None
89 file_desc = ""
90 can_range = False
91 file_elts = filter(lambda elt: elt.name == 'file', si_el.elements())
92 feature_elts = self.host.plugins["XEP-0020"].getFeatureElt(si_el)
93
94 if file_elts:
95 file_el = file_elts[0]
96 filename = file_el["name"]
97 file_size = file_el["size"]
98 file_date = file_el.getAttribute("date", "")
99 file_hash = file_el.getAttribute("hash", "")
100 log.info(_(u"File proposed: name=[%(name)s] size=%(size)s") % {'name': filename, 'size': file_size})
101 for file_child_el in file_el.elements():
102 if file_child_el.name == "desc":
103 file_desc = unicode(file_child_el)
104 elif file_child_el.name == "range":
105 can_range = True
106 else:
107 log.warning(_("No file element found"))
108 self.host.plugins["XEP-0095"].sendBadRequestError(iq_id, from_jid, profile)
109 return
110
111 if feature_elts:
112 feature_el = feature_elts[0]
113 data_form.Form.fromElement(feature_el.firstChildElement())
114 try:
115 stream_method = self.host.plugins["XEP-0020"].negociate(feature_el, 'stream-method', self.managed_stream_m)
116 except KeyError:
117 log.warning(_("No stream method found"))
118 self.host.plugins["XEP-0095"].sendBadRequestError(iq_id, from_jid, profile)
119 return
120 if not stream_method:
121 log.warning(_("Can't find a valid stream method"))
122 self.host.plugins["XEP-0095"].sendFailedError(iq_id, from_jid, profile)
123 return
124 else:
125 log.warning(_("No feature element found"))
126 self.host.plugins["XEP-0095"].sendBadRequestError(iq_id, from_jid, profile)
127 return 165 return
128 166
129 #if we are here, the transfer can start, we just need user's agreement 167 #if we are here, the transfer can start, we just need user's agreement
130 data = {"filename": filename, "id": iq_id, "from": from_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc, "can_range": str(can_range)} 168 data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc,
131 client._xep_0096_waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id, profile), stream_method, []] 169 "range": range_, "range_offset": range_offset, "range_length": range_length,
132 170 "si_id": si_id, "stream_method": stream_method, "stream_plugin": plugin}
133 self.host.askConfirmation(si_id, "FILE_TRANSFER", data, self.confirmationCB, profile) 171
172 d = self._f.getDestDir(peer_jid, data, data, profile)
173 d.addCallback(self.confirmationCb, iq_elt, data, profile)
134 174
135 def _getFileObject(self, dest_path, can_range=False): 175 def _getFileObject(self, dest_path, can_range=False):
136 """Open file, put file pointer to the end if the file if needed 176 """Open file, put file pointer to the end if the file if needed
137 @param dest_path: path of the destination file 177 @param dest_path: path of the destination file
138 @param can_range: True if the file pointer can be moved 178 @param can_range: True if the file pointer can be moved
139 @return: File Object""" 179 @return: File Object"""
140 return open(dest_path, "ab" if can_range else "wb") 180 return open(dest_path, "ab" if can_range else "wb")
141 181
142 def confirmationCB(self, sid, accepted, frontend_data, profile): 182 def confirmationCb(self, accepted, iq_elt, data, profile):
143 """Called on confirmation answer 183 """Called on confirmation answer
144 @param sid: file transfer session id 184
145 @param accepted: True if file transfer is accepted 185 @param accepted(bool): True if file transfer is accepted
146 @param frontend_data: data sent by frontend""" 186 @param iq_elt(domish.Element): initial SI request
187 @param data(dict): session data
188 @param profile: %(doc_profile)s
189 """
190 if not accepted:
191 log.info(u"File transfer declined")
192 self._si.sendError(iq_elt, 'forbidden', profile)
193 return
194 # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
195 # can_range = data['can_range'] == "True"
196 # range_offset = 0
197 # if timeout.active():
198 # timeout.cancel()
199 # try:
200 # dest_path = frontend_data['dest_path']
201 # except KeyError:
202 # log.error(_('dest path not found in frontend_data'))
203 # del client._xep_0096_waiting_for_approval[sid]
204 # return
205 # if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
206 # plugin = self.host.plugins["XEP-0065"]
207 # elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
208 # plugin = self.host.plugins["XEP-0047"]
209 # else:
210 # log.error(_("Unknown stream method, this should not happen at this stage, cancelling transfer"))
211 # del client._xep_0096_waiting_for_approval[sid]
212 # return
213
214 # file_obj = self._getFileObject(dest_path, can_range)
215 # range_offset = file_obj.tell()
216 d = data['stream_plugin'].createSession(data['file_obj'], data['peer_jid'], data['si_id'], profile=profile)
217 d.addCallback(self._transferCb, data, profile)
218 d.addErrback(self._transferEb, data, profile)
219
220 #we can send the iq result
221 feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': data['stream_method']}, namespace=None)
222 misc_elts = []
223 misc_elts.append(domish.Element((SI_PROFILE, "file")))
224 # if can_range:
225 # range_elt = domish.Element((None, "range"))
226 # range_elt['offset'] = str(range_offset)
227 # #TODO: manage range length
228 # misc_elts.append(range_elt)
229 self._si.acceptStream(iq_elt, feature_elt, misc_elts, profile)
230
231 def _transferCb(self, dummy, data, profile):
232 """Called by the stream method when transfer successfuly finished
233
234 @param data: session data
235 @param profile: %(doc_profile)s
236 """
237 #TODO: check hash
238 data['file_obj'].close()
239 log.info(u'Transfer {si_id} successfuly finished'.format(**data))
240
241 def _transferEb(self, failure, data, profile):
242 """Called when something went wrong with the transfer
243
244 @param id: stream id
245 @param data: session data
246 @param profile: %(doc_profile)s
247 """
248 log.warning(u'Transfer {si_id} failed: {reason}'.format(reason=unicode(failure.condition), **data))
249 data['file_obj'].close()
250
251 def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE):
252 return self.sendFile(jid.JID(peer_jid_s), filepath, name or None, desc or None, profile)
253
254 def sendFile(self, peer_jid, filepath, name=None, desc=None, profile=C.PROF_KEY_NONE):
255 """Send a file using XEP-0096
256
257 @param peer_jid(jid.JID): recipient
258 @param filepath(str): absolute path to the file to send
259 @param name(unicode): name of the file to send
260 name must not contain "/" characters
261 @param desc: description of the file
262 @param profile: %(doc_profile)s
263 @return: an unique id to identify the transfer
264 """
147 client = self.host.getClient(profile) 265 client = self.host.getClient(profile)
148 data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid] 266 feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}, namespace=None)
149 can_range = data['can_range'] == "True" 267
150 range_offset = 0 268 file_transfer_elts = []
151 if accepted: 269
152 if timeout.active(): 270 statinfo = os.stat(filepath)
153 timeout.cancel() 271 file_elt = domish.Element((SI_PROFILE, 'file'))
154 try: 272 file_elt['name'] = name or os.path.basename(filepath)
155 dest_path = frontend_data['dest_path'] 273 assert '/' not in file_elt['name']
156 except KeyError: 274 size = statinfo.st_size
157 log.error(_('dest path not found in frontend_data')) 275 file_elt['size'] = str(size)
158 del client._xep_0096_waiting_for_approval[sid] 276 if desc:
159 return 277 file_elt.addElement('desc', content=desc)
160 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 278 file_transfer_elts.append(file_elt)
161 plugin = self.host.plugins["XEP-0065"] 279
162 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 280 file_transfer_elts.append(domish.Element((None, 'range')))
163 plugin = self.host.plugins["XEP-0047"] 281
164 else: 282 sid, offer_d = self._si.proposeStream(peer_jid, SI_PROFILE, feature_elt, file_transfer_elts, profile=client.profile)
165 log.error(_("Unknown stream method, this should not happen at this stage, cancelling transfer")) 283 args = [filepath, sid, size, client]
166 del client._xep_0096_waiting_for_approval[sid] 284 offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args)
167 return 285 return sid
168 286
169 file_obj = self._getFileObject(dest_path, can_range) 287 def _fileCb(self, result_tuple, filepath, sid, size, client):
170 range_offset = file_obj.tell() 288 iq_elt, si_elt = result_tuple
171 d = plugin.createSession(file_obj, jid.JID(data['from']), sid, int(data["size"]), profile) 289
172 d.addCallback(self._transferSucceeded, sid, file_obj, stream_method, profile) 290 try:
173 d.addErrback(self._transferFailed, sid, file_obj, stream_method, profile) 291 feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
174 292 except exceptions.NotFound:
175 #we can send the iq result 293 log.warning(u"No <feature/> element found in result while expected")
176 feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': stream_method}) 294 return
177 misc_elts = [] 295
178 misc_elts.append(domish.Element((PROFILE, "file"))) 296 choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elt, namespace=None)
179 if can_range:
180 range_elt = domish.Element((None, "range"))
181 range_elt['offset'] = str(range_offset)
182 #TODO: manage range length
183 misc_elts.append(range_elt)
184 self.host.plugins["XEP-0095"].acceptStream(data["id"], data['from'], feature_elt, misc_elts, profile)
185 else:
186 log.debug(_(u"Transfer [%s] refused") % sid)
187 self.host.plugins["XEP-0095"].sendRejectedError(data["id"], data['from'], profile=profile)
188 del(client._xep_0096_waiting_for_approval[sid])
189
190 def _transferSucceeded(self, dummy, sid, file_obj, stream_method, profile):
191 self.transferSucceeded(sid, file_obj, stream_method, profile)
192
193 def transferSucceeded(self, dummy, sid, file_obj, stream_method, profile):
194 """Called by the stream method when transfer successfuly finished
195 @param id: stream id"""
196 client = self.host.getClient(profile)
197 file_obj.close()
198 log.info(_('Transfer %s successfuly finished') % sid)
199 del(client._xep_0096_waiting_for_approval[sid])
200
201 def _transferFailed(self, sid, file_obj, stream_method, reason, profile):
202 self.transferFailed(failure.Failure(Exception(reason)), sid, file_obj, stream_method, profile)
203
204 def transferFailed(self, failure, sid, file_obj, stream_method, profile):
205 """Called when something went wrong with the transfer
206
207 @param id: stream id
208 """
209 client = self.host.getClient(profile)
210 data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
211 log.warning(_(u'Transfer %(id)s failed with stream method %(s_method)s: %(reason)s') % {
212 'id': sid,
213 's_method': stream_method,
214 'reason': unicode(failure)})
215 filepath = file_obj.name
216 file_obj.close()
217 os.remove(filepath)
218 #TODO: session remenber (within a time limit) when a stream method fail, and avoid that stream method with full jid for the rest of the session
219 log.warning(_("All stream methods failed, can't transfer the file"))
220 del(client._xep_0096_waiting_for_approval[sid])
221
222 def fileCb(self, filepath, sid, size, profile, IQ):
223 if IQ['type'] == "error":
224 stanza_err = jabber.error.exceptionFromStanza(IQ)
225 if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
226 log.debug(_(u"File transfer refused by %s") % IQ['from'])
227 self.host.bridge.newAlert(_("The contact %s refused your file") % IQ['from'], _("File refused"), "INFO", profile)
228 else:
229 log.warning(_(u"Error during file transfer with %s") % IQ['from'])
230 self.host.bridge.newAlert(_("Something went wrong during the file transfer session intialisation with %s") % IQ['from'], _("File transfer error"), "ERROR", profile)
231 return
232
233 si_elt = IQ.firstChildElement()
234
235 if IQ['type'] != "result" or not si_elt or si_elt.name != "si":
236 log.error(_("Protocol error during file transfer"))
237 return
238
239 feature_elts = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
240 if not feature_elts:
241 log.warning(_("No feature element"))
242 return
243
244 choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elts[0])
245 try: 297 try:
246 stream_method = choosed_options["stream-method"] 298 stream_method = choosed_options["stream-method"]
247 except KeyError: 299 except KeyError:
248 log.warning(_("No stream method choosed")) 300 log.warning(u"No stream method choosed")
249 return 301 return
250 302
251 range_offset = 0 303 try:
252 # range_length = None 304 file_elt = si_elt.elements(NS_SI_FT, "file").next()
253 range_elts = filter(lambda elt: elt.name == 'range', si_elt.elements()) 305 except StopIteration:
254 if range_elts: 306 pass
255 range_elt = range_elts[0] 307 else:
256 range_offset = range_elt.getAttribute("offset", 0) 308 range_, range_offset, range_length = self._parseRange(file_elt, size)
257 # range_length = range_elt.getAttribute("length")
258 309
259 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 310 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
260 plugin = self.host.plugins["XEP-0065"] 311 plugin = self.host.plugins["XEP-0065"]
261 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 312 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
262 plugin = self.host.plugins["XEP-0047"] 313 plugin = self.host.plugins["XEP-0047"]
263 else: 314 else:
264 log.error(u"Invalid stream method received") 315 log.warning(u"Invalid stream method received")
265 return 316 return
266 317
267 file_obj = open(filepath, 'r') 318 file_obj = self._f.File(self.host,
268 if range_offset: 319 filepath,
269 file_obj.seek(range_offset) 320 size=size,
270 d = plugin.startStream(file_obj, jid.JID(IQ['from']), sid, profile=profile) 321 profile=client.profile
271 d.addCallback(self.sendSuccessCb, sid, file_obj, stream_method, profile) 322 )
272 d.addErrback(self.sendFailureCb, sid, file_obj, stream_method, profile) 323 d = plugin.startStream(file_obj, jid.JID(iq_elt['from']), sid, profile=client.profile)
273 324 d.addCallback(self._sendCb, sid, file_obj, client.profile)
274 def sendFile(self, to_jid, filepath, data={}, profile_key=C.PROF_KEY_NONE): 325 d.addErrback(self._sendEb, sid, file_obj, client.profile)
275 """send a file using XEP-0096 326
276 @to_jid: recipient 327 def _fileEb(self, failure, filepath, sid, size, client):
277 @filepath: absolute path to the file to send 328 if failure.check(error.StanzaError):
278 @data: dictionnary with the optional following keys: 329 stanza_err = failure.value
279 - "description": description of the file 330 if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
280 @param profile_key: %(doc_profile_key)s 331 from_s = stanza_err.stanza['from']
281 @return: an unique id to identify the transfer 332 log.info(u"File transfer refused by {}".format(from_s))
282 """ 333 self.host.bridge.newAlert(_("The contact {} has refused your file").format(from_s), _("File refused"), "INFO", client.profile)
283 profile = self.host.memory.getProfileName(profile_key) 334 else:
284 if not profile: 335 log.warning(_(u"Error during file transfer"))
285 log.warning(_("Trying to send a file from an unknown profile")) 336 self.host.bridge.newAlert(_(u"Something went wrong during the file transfer session intialisation: {reason}").format(reason=unicode(stanza_err.condition)), _("File transfer error"), "ERROR", client.profile)
286 return "" 337 elif failure.check(exceptions.DataError):
287 feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}) 338 log.warning(u'Invalid stanza received')
288 339 else:
289 file_transfer_elts = [] 340 log.error(u'Error while proposing stream: {}'.format(failure))
290 341
291 statinfo = os.stat(filepath) 342 def _sendCb(self, dummy, sid, file_obj, profile):
292 file_elt = domish.Element((PROFILE, 'file')) 343 log.info(_(u'transfer {sid} successfuly finished [{profile}]').format(
293 file_elt['name'] = os.path.basename(filepath) 344 sid=sid,
294 size = file_elt['size'] = str(statinfo.st_size) 345 profile=profile))
295 file_transfer_elts.append(file_elt)
296
297 file_transfer_elts.append(domish.Element((None, 'range')))
298
299 sid, offer = self.host.plugins["XEP-0095"].proposeStream(jid.JID(to_jid), PROFILE, feature_elt, file_transfer_elts, profile_key=profile)
300 offer.addCallback(self.fileCb, filepath, sid, size, profile)
301 return sid
302
303 def _sendSuccessCb(self, sid, file_obj, stream_method, profile):
304 self.sendSuccessCb(sid, file_obj, stream_method, profile)
305
306 def sendSuccessCb(self, dummy, sid, file_obj, stream_method, profile):
307 log.info(_(u'Transfer %(sid)s successfuly finished [%(profile)s]')
308 % {"sid": sid, "profile": profile})
309 file_obj.close() 346 file_obj.close()
310 347
311 def _sendFailureCb(self, sid, file_obj, stream_method, reason, profile): 348 def _sendEb(self, failure, sid, file_obj, profile):
312 self.sendFailureCb(failure.Failure(Exception(reason)), sid, file_obj, stream_method, profile) 349 log.warning(_(u'transfer {sid} failed [{profile}]: {reason}').format(
313 350 sid=sid,
314 def sendFailureCb(self, failure, sid, file_obj, stream_method, profile): 351 profile=profile,
352 reason=unicode(failure.condition),
353 ))
315 file_obj.close() 354 file_obj.close()
316 log.warning(_(u'Transfer %(id)s failed with stream method %(s_method)s: %(reason)s [%(profile)s]') % {'id': sid, "s_method": stream_method, 'reason': unicode(failure), 'profile': profile})