comparison src/plugins/plugin_xep_0096.py @ 538:2c4016921403

core, frontends, bridgen plugins: fixed methods which were unproperly managing multi-profiles - added profile argument to askConfirmation, actionResult, actionResultExt, entityDataUpdated, confirmationAnswer, getProgress - core, frontends: fixed calls/signals according to new bridge API - user of proper profile namespace for progression indicators and dialogs - memory: getParam* now return bool when param type is bool - memory: added getStringParam* to return string instead of typed value - core, memory, storage, quick_frontend: getHistory now manage properly multi-profiles - plugins XEP-0047, XEP-0054, XEP-0065, XEP-0077, XEP-0096; multi-profiles proper handling
author Goffi <goffi@goffi.org>
date Sat, 10 Nov 2012 16:38:16 +0100
parents a31abb97310d
children 47e45a577ab7
comparison
equal deleted inserted replaced
537:28cddc96c4ed 538:2c4016921403
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 from logging import debug, info, warning, error 22 from logging import debug, info, warning, error
23 from twisted.words.xish import domish 23 from twisted.words.xish import domish
24 from twisted.internet import protocol 24 from twisted.words.protocols.jabber import jid
25 from twisted.words.protocols.jabber import client, jid
26 from twisted.words.protocols.jabber import error as jab_error 25 from twisted.words.protocols.jabber import error as jab_error
27 import os, os.path 26 import os, os.path
28 from twisted.internet import reactor 27 from twisted.internet import reactor
29 import pdb 28 from sat.core.exceptions import ProfileNotInCacheError
30 29
31 from zope.interface import implements 30
32 31 from wokkel import data_form
33 from wokkel import disco, iwokkel, data_form
34 32
35 IQ_SET = '/iq[@type="set"]' 33 IQ_SET = '/iq[@type="set"]'
36 PROFILE_NAME = "file-transfer" 34 PROFILE_NAME = "file-transfer"
37 PROFILE = "http://jabber.org/protocol/si/profile/" + PROFILE_NAME 35 PROFILE = "http://jabber.org/protocol/si/profile/" + PROFILE_NAME
38 36
50 class XEP_0096(): 48 class XEP_0096():
51 49
52 def __init__(self, host): 50 def __init__(self, host):
53 info(_("Plugin XEP_0096 initialization")) 51 info(_("Plugin XEP_0096 initialization"))
54 self.host = host 52 self.host = host
55 self._waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout, 53 self.managed_stream_m = [#self.host.plugins["XEP-0065"].NAMESPACE,
56 # current stream method, [failed stream methods], profile]
57 self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
58 self.host.plugins["XEP-0047"].NAMESPACE] #Stream methods managed 54 self.host.plugins["XEP-0047"].NAMESPACE] #Stream methods managed
59 self.host.plugins["XEP-0095"].registerSIProfile(PROFILE_NAME, self.transferRequest) 55 self.host.plugins["XEP-0095"].registerSIProfile(PROFILE_NAME, self.transferRequest)
60 host.bridge.addMethod("sendFile", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self.sendFile) 56 host.bridge.addMethod("sendFile", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self.sendFile)
61 57
62 def _kill_id(self, approval_id): 58 def profileConnected(self, profile):
59 client = self.host.getClient(profile)
60 client._xep_0096_waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout,
61 # current stream method, [failed stream methods], profile]
62
63 def _kill_id(self, approval_id, profile):
63 """Delete a waiting_for_approval id, called after timeout 64 """Delete a waiting_for_approval id, called after timeout
64 @param approval_id: id of _waiting_for_approval""" 65 @param approval_id: id of _xep_0096_waiting_for_approval"""
65 info(_("SI File Transfer: TimeOut reached for id %s") % approval_id); 66 info(_("SI File Transfer: TimeOut reached for id %s") % approval_id);
66 try: 67 try:
67 del self._waiting_for_approval[approval_id] 68 client = self.host.getClient(profile)
69 del client._xep_0096_waiting_for_approval[approval_id]
68 except KeyError: 70 except KeyError:
69 warning(_("kill id called on a non existant approval id")) 71 warning(_("kill id called on a non existant approval id"))
70 72
71 def transferRequest(self, iq_id, from_jid, si_id, si_mime_type, si_el, profile): 73 def transferRequest(self, iq_id, from_jid, si_id, si_mime_type, si_el, profile):
72 """Called when a file transfer is requested 74 """Called when a file transfer is requested
76 @param si_mime_type: Mime type of the file (or default "application/octet-stream" if unknown) 78 @param si_mime_type: Mime type of the file (or default "application/octet-stream" if unknown)
77 @param si_el: domish.Element of the request 79 @param si_el: domish.Element of the request
78 @param profile: %(doc_profile)s""" 80 @param profile: %(doc_profile)s"""
79 info (_("XEP-0096 file transfer requested")) 81 info (_("XEP-0096 file transfer requested"))
80 debug(si_el.toXml()) 82 debug(si_el.toXml())
83 client = self.host.getClient(profile)
84 if not client:
85 raise ProfileNotInCacheError
81 filename = "" 86 filename = ""
82 file_size = "" 87 file_size = ""
83 file_date = None 88 file_date = None
84 file_hash = None 89 file_hash = None
85 file_desc = "" 90 file_desc = ""
122 self.host.plugins["XEP-0095"].sendBadRequestError(iq_id, from_jid, profile) 127 self.host.plugins["XEP-0095"].sendBadRequestError(iq_id, from_jid, profile)
123 return 128 return
124 129
125 #if we are here, the transfer can start, we just need user's agreement 130 #if we are here, the transfer can start, we just need user's agreement
126 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) } 131 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) }
127 self._waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id), stream_method, [], profile] 132 client._xep_0096_waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id, profile), stream_method, []]
128 133
129 self.host.askConfirmation(si_id, "FILE_TRANSFER", data, self.confirmationCB) 134 self.host.askConfirmation(si_id, "FILE_TRANSFER", data, self.confirmationCB, profile)
130 135
131 136
132 def _getFileObject(self, dest_path, can_range = False): 137 def _getFileObject(self, dest_path, can_range = False):
133 """Open file, put file pointer to the end if the file if needed 138 """Open file, put file pointer to the end if the file if needed
134 @param dest_path: path of the destination file 139 @param dest_path: path of the destination file
135 @param can_range: True if the file pointer can be moved 140 @param can_range: True if the file pointer can be moved
136 @return: File Object""" 141 @return: File Object"""
137 return open(dest_path, "ab" if can_range else "wb") 142 return open(dest_path, "ab" if can_range else "wb")
138 143
139 def confirmationCB(self, sid, accepted, frontend_data): 144 def confirmationCB(self, sid, accepted, frontend_data, profile):
140 """Called on confirmation answer 145 """Called on confirmation answer
141 @param sid: file transfer session id 146 @param sid: file transfer session id
142 @param accepted: True if file transfer is accepted 147 @param accepted: True if file transfer is accepted
143 @param frontend_data: data sent by frontend""" 148 @param frontend_data: data sent by frontend"""
144 data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid] 149 client = self.host.getClient(profile)
150 if not client:
151 raise ProfileNotInCacheError
152 data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
145 can_range = data['can_range'] == "True" 153 can_range = data['can_range'] == "True"
146 range_offset = 0 154 range_offset = 0
147 if accepted: 155 if accepted:
148 if timeout.active(): 156 if timeout.active():
149 timeout.cancel() 157 timeout.cancel()
150 try: 158 try:
151 dest_path = frontend_data['dest_path'] 159 dest_path = frontend_data['dest_path']
152 except KeyError: 160 except KeyError:
153 error(_('dest path not found in frontend_data')) 161 error(_('dest path not found in frontend_data'))
154 del(self._waiting_for_approval[sid]) 162 del(client._xep_0096_waiting_for_approval[sid])
155 return 163 return
156 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE: 164 if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
157 file_obj = self._getFileObject(dest_path, can_range) 165 file_obj = self._getFileObject(dest_path, can_range)
158 range_offset = file_obj.tell() 166 range_offset = file_obj.tell()
159 self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed) 167 self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed, profile)
160 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: 168 elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
161 file_obj = self._getFileObject(dest_path, can_range) 169 file_obj = self._getFileObject(dest_path, can_range)
162 range_offset = file_obj.tell() 170 range_offset = file_obj.tell()
163 self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed) 171 self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed, profile)
164 else: 172 else:
165 error(_("Unknown stream method, this should not happen at this stage, cancelling transfer")) 173 error(_("Unknown stream method, this should not happen at this stage, cancelling transfer"))
166 del(self._waiting_for_approval[sid]) 174 del(client._xep_0096_waiting_for_approval[sid])
167 return 175 return
168 176
169 #we can send the iq result 177 #we can send the iq result
170 feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method':stream_method}) 178 feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method':stream_method})
171 misc_elts = [] 179 misc_elts = []
177 misc_elts.append(range_elt) 185 misc_elts.append(range_elt)
178 self.host.plugins["XEP-0095"].acceptStream(data["id"], data['from'], feature_elt, misc_elts, profile) 186 self.host.plugins["XEP-0095"].acceptStream(data["id"], data['from'], feature_elt, misc_elts, profile)
179 else: 187 else:
180 debug (_("Transfer [%s] refused"), sid) 188 debug (_("Transfer [%s] refused"), sid)
181 self.host.plugins["XEP-0095"].sendRejectedError (data["id"], data['from'], profile=profile) 189 self.host.plugins["XEP-0095"].sendRejectedError (data["id"], data['from'], profile=profile)
182 del(self._waiting_for_approval[sid]) 190 del(client._xep_0096_waiting_for_approval[sid])
183 191
184 def _transferSucceeded(self, sid, file_obj, stream_method): 192 def _transferSucceeded(self, sid, file_obj, stream_method, profile):
185 """Called by the stream method when transfer successfuly finished 193 """Called by the stream method when transfer successfuly finished
186 @param id: stream id""" 194 @param id: stream id"""
195 client = self.host.getClient(profile)
196 if not client:
197 raise ProfileNotInCacheError
187 file_obj.close() 198 file_obj.close()
188 info(_('Transfer %s successfuly finished') % sid) 199 info(_('Transfer %s successfuly finished') % sid)
189 del(self._waiting_for_approval[sid]) 200 del(client._xep_0096_waiting_for_approval[sid])
190 201
191 def _transferFailed(self, sid, file_obj, stream_method, reason): 202 def _transferFailed(self, sid, file_obj, stream_method, reason, profile):
192 """Called when something went wrong with the transfer 203 """Called when something went wrong with the transfer
193 @param id: stream id 204 @param id: stream id
194 @param reason: can be TIMEOUT, IO_ERROR, PROTOCOL_ERROR""" 205 @param reason: can be TIMEOUT, IO_ERROR, PROTOCOL_ERROR"""
195 data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid] 206 client = self.host.getClient(profile)
196 warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid, 207 if not client:
197 's_method': stream_method }) 208 raise ProfileNotInCacheError
209 data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
210 warning(_('Transfer %(id)s failed with stream method %(s_method)s: %(reason)s') % { 'id': sid,
211 's_method': stream_method,
212 'reason': reason})
198 filepath = file_obj.name 213 filepath = file_obj.name
199 file_obj.close() 214 file_obj.close()
200 os.remove(filepath) 215 os.remove(filepath)
201 #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 216 #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
202 warning(_("All stream methods failed, can't transfer the file")) 217 warning(_("All stream methods failed, can't transfer the file"))
203 del(self._waiting_for_approval[sid]) 218 del(client._xep_0096_waiting_for_approval[sid])
204 219
205 def fileCb(self, profile, filepath, sid, size, IQ): 220 def fileCb(self, filepath, sid, size, profile, IQ):
206 if IQ['type'] == "error": 221 if IQ['type'] == "error":
207 stanza_err = jab_error.exceptionFromStanza(IQ) 222 stanza_err = jab_error.exceptionFromStanza(IQ)
208 if stanza_err.code == '403' and stanza_err.condition == 'forbidden': 223 if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
209 debug(_("File transfer refused by %s") % IQ['from']) 224 debug(_("File transfer refused by %s") % IQ['from'])
210 self.host.bridge.newAlert(_("The contact %s refused your file") % IQ['from'], _("File refused"), "INFO", profile) 225 self.host.bridge.newAlert(_("The contact %s refused your file") % IQ['from'], _("File refused"), "INFO", profile)
250 file_obj.seek(range_offset) 265 file_obj.seek(range_offset)
251 self.host.plugins["XEP-0047"].startStream(file_obj, jid.JID(IQ['from']), sid, range_length, self.sendSuccessCb, self.sendFailureCb, size, profile) 266 self.host.plugins["XEP-0047"].startStream(file_obj, jid.JID(IQ['from']), sid, range_length, self.sendSuccessCb, self.sendFailureCb, size, profile)
252 else: 267 else:
253 warning(_("Invalid stream method received")) 268 warning(_("Invalid stream method received"))
254 269
255 def sendFile(self, to_jid, filepath, data={}, profile_key='@DEFAULT@'): 270 def sendFile(self, to_jid, filepath, data={}, profile_key='@NONE@'):
256 """send a file using XEP-0096 271 """send a file using XEP-0096
257 @to_jid: recipient 272 @to_jid: recipient
258 @filepath: absolute path to the file to send 273 @filepath: absolute path to the file to send
259 @data: dictionnary with the optional following keys: 274 @data: dictionnary with the optional following keys:
260 - "description": description of the file 275 - "description": description of the file
276 file_transfer_elts.append(file_elt) 291 file_transfer_elts.append(file_elt)
277 292
278 file_transfer_elts.append(domish.Element((None,'range'))) 293 file_transfer_elts.append(domish.Element((None,'range')))
279 294
280 sid, offer = self.host.plugins["XEP-0095"].proposeStream(jid.JID(to_jid), PROFILE, feature_elt, file_transfer_elts, profile_key = profile) 295 sid, offer = self.host.plugins["XEP-0095"].proposeStream(jid.JID(to_jid), PROFILE, feature_elt, file_transfer_elts, profile_key = profile)
281 offer.addCallback(self.fileCb, profile, filepath, sid, size) 296 offer.addCallback(self.fileCb, filepath, sid, size, profile)
282 return sid 297 return sid
283 298
284 def sendSuccessCb(self, sid, file_obj, stream_method): 299 def sendSuccessCb(self, sid, file_obj, stream_method, profile):
285 info(_('Transfer %s successfuly finished') % sid) 300 info(_('Transfer %s successfuly finished [%s]') % (sid, profile))
286 file_obj.close() 301 file_obj.close()
287 302
288 def sendFailureCb(self, sid, file_obj, stream_method, reason): 303 def sendFailureCb(self, sid, file_obj, stream_method, reason, profile):
289 file_obj.close() 304 file_obj.close()
290 warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid, "s_method": stream_method }) 305 warning(_('Transfer %(id)s failed with stream method %(s_method)s: %(reason)s [%(profile)s') % { 'id': sid, "s_method": stream_method, 'reason': reason, 'profile': profile })