comparison src/plugins/plugin_xep_0234.py @ 1571:c668081eba1c

plugins XEP-0234, XEP-0260, XEP-0261: jingle session termination is managed by application (XEP-0234) instead of transport
author Goffi <goffi@goffi.org>
date Sun, 08 Nov 2015 14:48:04 +0100
parents 1f7a34d499e0
children babd97d80049
comparison
equal deleted inserted replaced
1570:37d4be4a9fed 1571:c668081eba1c
29 import os.path 29 import os.path
30 from twisted.words.xish import domish 30 from twisted.words.xish import domish
31 from twisted.words.protocols.jabber import jid 31 from twisted.words.protocols.jabber import jid
32 from twisted.python import failure 32 from twisted.python import failure
33 from twisted.words.protocols.jabber.xmlstream import XMPPHandler 33 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
34 from twisted.internet import defer
34 35
35 36
36 NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:4' 37 NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:4'
37 CONFIRM = D_(u'{entity} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?') 38 CONFIRM = D_(u'{entity} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?')
38 CONFIRM_TITLE = D_(u'Confirm file transfer') 39 CONFIRM_TITLE = D_(u'Confirm file transfer')
50 "description": _("""Implementation of Jingle File Transfer""") 51 "description": _("""Implementation of Jingle File Transfer""")
51 } 52 }
52 53
53 54
54 class XEP_0234(object): 55 class XEP_0234(object):
56 # TODO: assure everything is closed when file is sent or session terminate is received
55 57
56 def __init__(self, host): 58 def __init__(self, host):
57 log.info(_("plugin Jingle File Transfer initialization")) 59 log.info(_("plugin Jingle File Transfer initialization"))
58 self.host = host 60 self.host = host
59 self._j = host.plugins["XEP-0166"] # shortcut to access jingle 61 self._j = host.plugins["XEP-0166"] # shortcut to access jingle
79 81
80 82
81 # Dialogs with user 83 # Dialogs with user
82 # the overwrite check is done here 84 # the overwrite check is done here
83 85
84 def _getDestDir(self, session, content_data, profile): 86 def _getDestDir(self, session, content_name, content_data, profile):
85 """Request confirmation and destination dir to user 87 """Request confirmation and destination dir to user
86 88
87 if transfer is confirmed, session is filled 89 if transfer is confirmed, session is filled
88 @param session(dict): jingle session data 90 @param session(dict): jingle session data
91 @param content_name(unicode): name of the jingle content
89 @param content_data(dict): content informations 92 @param content_data(dict): content informations
90 @param profile: %(doc_profile)s 93 @param profile: %(doc_profile)s
91 return (defer.Deferred): True if transfer is accepted 94 return (defer.Deferred): True if transfer is accepted
92 """ 95 """
93 application_data = content_data['application_data'] 96 application_data = content_data['application_data']
96 _(CONFIRM).format(entity=session['peer_jid'].full(), **file_data), 99 _(CONFIRM).format(entity=session['peer_jid'].full(), **file_data),
97 _(CONFIRM_TITLE), 100 _(CONFIRM_TITLE),
98 type_=C.XMLUI_DIALOG_FILE, 101 type_=C.XMLUI_DIALOG_FILE,
99 options={C.XMLUI_DATA_FILETYPE: C.XMLUI_DATA_FILETYPE_DIR}, 102 options={C.XMLUI_DATA_FILETYPE: C.XMLUI_DATA_FILETYPE_DIR},
100 profile=profile) 103 profile=profile)
101 d.addCallback(self._gotConfirmation, session, content_data, application_data, profile) 104 d.addCallback(self._gotConfirmation, session, content_name, content_data, application_data, profile)
102 return d 105 return d
103 106
104 def _openFileWrite(self, content_data, file_path, file_data, profile): 107 def _openFileWrite(self, session, content_name, content_data, file_path, file_data, profile):
105 assert 'file_obj' not in content_data 108 assert 'file_obj' not in content_data
106 content_data['file_obj'] = self._f.File( 109 file_obj = content_data['file_obj'] = self._f.File(
107 self.host, 110 self.host,
108 file_path, 111 file_path,
109 'w', 112 'w',
110 size=file_data['size'], 113 size=file_data['size'],
111 profile=profile, 114 profile=profile,
112 ) 115 )
113 116 finished_d = content_data['finished_d'] = defer.Deferred()
114 def _gotConfirmation(self, data, session, content_data, application_data, profile): 117 args = [file_obj, session, content_name, content_data, profile]
118 finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
119
120 def _gotConfirmation(self, data, session, content_name, content_data, application_data, profile):
115 """Called when the permission and dest path have been received 121 """Called when the permission and dest path have been received
116 122
117 @param data(dict): xmlui data received from file dialog 123 @param data(dict): xmlui data received from file dialog
124 @param session(dict): jingle session data
125 @param content_name(unicode): name of the jingle content
126 @param content_data(dict): content session_data
127 @param content_data(dict): application session data
128 @param profile: %(doc_profile)s
118 return (bool): True if copy is wanted and OK 129 return (bool): True if copy is wanted and OK
119 False if user wants to cancel 130 False if user wants to cancel
120 if fill exists ask confirmation and call again self._getDestDir if needed 131 if fill exists ask confirmation and call again self._getDestDir if needed
121 """ 132 """
122 if data.get('cancelled', False): 133 if data.get('cancelled', False):
128 139
129 # we manage case where file already exists 140 # we manage case where file already exists
130 if os.path.exists(file_path): 141 if os.path.exists(file_path):
131 def check_overwrite(overwrite): 142 def check_overwrite(overwrite):
132 if overwrite: 143 if overwrite:
133 self._openFileWrite(content_data, file_path, file_data, profile) 144 self._openFileWrite(session, content_name, content_data, file_path, file_data, profile)
134 return True 145 return True
135 else: 146 else:
136 return self._getDestDir(session, content_data, profile) 147 return self._getDestDir(session, content_name, content_data, profile)
137 148
138 exists_d = xml_tools.deferConfirm( 149 exists_d = xml_tools.deferConfirm(
139 self.host, 150 self.host,
140 _(CONFIRM_OVERWRITE).format(file_path), 151 _(CONFIRM_OVERWRITE).format(file_path),
141 _(CONFIRM_OVERWRITE_TITLE), 152 _(CONFIRM_OVERWRITE_TITLE),
142 profile=profile) 153 profile=profile)
143 exists_d.addCallback(check_overwrite) 154 exists_d.addCallback(check_overwrite)
144 return exists_d 155 return exists_d
145 156
146 self._openFileWrite(content_data, file_path, file_data, profile) 157 self._openFileWrite(session, content_name, content_data, file_path, file_data, profile)
147 return True 158 return True
148 159
149 # jingle callbacks 160 # jingle callbacks
150 161
151 def jingleSessionInit(self, session, content_name, filepath, name=None, file_desc=None, profile=C.PROF_KEY_NONE): 162 def jingleSessionInit(self, session, content_name, filepath, name=None, file_desc=None, profile=C.PROF_KEY_NONE):
201 # TODO: parse hash using plugin XEP-0300 212 # TODO: parse hash using plugin XEP-0300
202 213
203 content_data['application_data']['file_data'] = file_data 214 content_data['application_data']['file_data'] = file_data
204 215
205 # now we actualy request permission to user 216 # now we actualy request permission to user
206 return self._getDestDir(session, content_data, profile) 217 return self._getDestDir(session, content_name, content_data, profile)
207 218
208 219
209 def jingleHandler(self, action, session, content_name, desc_elt, profile): 220 def jingleHandler(self, action, session, content_name, desc_elt, profile):
210 content_data = session['contents'][content_name] 221 content_data = session['contents'][content_name]
211 application_data = content_data['application_data'] 222 application_data = content_data['application_data']
226 file_obj = content_data['file_obj'] = self._f.File(self.host, 237 file_obj = content_data['file_obj'] = self._f.File(self.host,
227 file_path, 238 file_path,
228 size=size, 239 size=size,
229 profile=profile 240 profile=profile
230 ) 241 )
231 file_obj.eof.addCallback(lambda dummy: file_obj.close()) 242 finished_d = content_data['finished_d'] = defer.Deferred()
243 args = [file_obj, session, content_name, content_data, profile]
244 finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
232 else: 245 else:
233 log.warning(u"FIXME: unmanaged action {}".format(action)) 246 log.warning(u"FIXME: unmanaged action {}".format(action))
234 return desc_elt 247 return desc_elt
235 248
249 def _finishedCb(self, dummy, file_obj, session, content_name, content_data, profile):
250 log.debug(u"File transfer completed successfuly")
251 if content_data['senders'] != session['role']:
252 # we terminate the session only if we are the received,
253 # as recommanded in XEP-0234 ยง2 (after example 6)
254 self._j.contentTerminate(session, content_name, profile=profile)
255 file_obj.close()
256
257 def _finishedEb(self, failure, file_obj, session, content_name, content_data, profile):
258 log.warning(u"Error while streaming through s5b: {}".format(failure))
259 file_obj.close()
260 self._j.contentTerminate(session, content_name, reason=self._j.REASON_FAILED_TRANSPORT, profile=profile)
261
236 262
237 class XEP_0234_handler(XMPPHandler): 263 class XEP_0234_handler(XMPPHandler):
238 implements(iwokkel.IDisco) 264 implements(iwokkel.IDisco)
239 265
240 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 266 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):