Mercurial > libervia-backend
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}) |