comparison src/plugins/plugin_misc_file.py @ 2502:7ad5f2c4e34a

XEP-0065,XEP-0096,XEP-0166,XEP-0235,XEP-0300: file transfer improvments: huge patch sorry :) many things are improved by this patch, notably: - updated to last protocol changes (urn:xmpp:jingle:apps:file-transfer:5 and urn:xmpp:hashes:2) - XEP-0234: file request implementation - XEP-0234: methods to parse and generate <file> element (can be used by other plugins easily) - XEP-0234: range data is now in a namedtuple - path and namespace can be specified when sending/requesting a file (not standard, but needed for file sharing) - better error/termination handling - trigger points to handle file requests by other plugins - preparation to use file plugins with components
author Goffi <goffi@goffi.org>
date Wed, 28 Feb 2018 18:28:39 +0100
parents e2a7bb875957
children
comparison
equal deleted inserted replaced
2501:3b67fe672206 2502:7ad5f2c4e34a
32 32
33 PLUGIN_INFO = { 33 PLUGIN_INFO = {
34 C.PI_NAME: "File Tansfer", 34 C.PI_NAME: "File Tansfer",
35 C.PI_IMPORT_NAME: "FILE", 35 C.PI_IMPORT_NAME: "FILE",
36 C.PI_TYPE: C.PLUG_TYPE_MISC, 36 C.PI_TYPE: C.PLUG_TYPE_MISC,
37 C.PI_MODES: C.PLUG_MODE_BOTH,
37 C.PI_MAIN: "FilePlugin", 38 C.PI_MAIN: "FilePlugin",
38 C.PI_HANDLER: "no", 39 C.PI_HANDLER: "no",
39 C.PI_DESCRIPTION: _("""File Tansfer Management: 40 C.PI_DESCRIPTION: _("""File Tansfer Management:
40 This plugin manage the various ways of sending a file, and choose the best one.""") 41 This plugin manage the various ways of sending a file, and choose the best one.""")
41 } 42 }
56 File=stream.SatFile 57 File=stream.SatFile
57 58
58 def __init__(self, host): 59 def __init__(self, host):
59 log.info(_("plugin File initialization")) 60 log.info(_("plugin File initialization"))
60 self.host = host 61 self.host = host
61 host.bridge.addMethod("fileSend", ".plugin", in_sign='sssss', out_sign='a{ss}', method=self._fileSend, async=True) 62 host.bridge.addMethod("fileSend", ".plugin", in_sign='ssssa{ss}s', out_sign='a{ss}', method=self._fileSend, async=True)
62 self._file_callbacks = [] 63 self._file_callbacks = []
63 host.importMenu((D_("Action"), D_("send file")), self._fileSendMenu, security_limit=10, help_string=D_("Send a file"), type_=C.MENU_SINGLE) 64 host.importMenu((D_("Action"), D_("send file")), self._fileSendMenu, security_limit=10, help_string=D_("Send a file"), type_=C.MENU_SINGLE)
64 65
65 def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", profile=C.PROF_KEY_NONE): 66 def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE):
66 client = self.host.getClient(profile) 67 client = self.host.getClient(profile)
67 return self.fileSend(client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None) 68 return self.fileSend(client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra)
68 69
69 @defer.inlineCallbacks 70 @defer.inlineCallbacks
70 def fileSend(self, client, peer_jid, filepath, filename=None, file_desc=None): 71 def fileSend(self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None):
71 """Send a file using best available method 72 """Send a file using best available method
72 73
73 @param peer_jid(jid.JID): jid of the destinee 74 @param peer_jid(jid.JID): jid of the destinee
74 @param filepath(str): absolute path to the file 75 @param filepath(str): absolute path to the file
75 @param filename(unicode, None): name to use, or None to find it from filepath 76 @param filename(unicode, None): name to use, or None to find it from filepath
83 filename = os.path.basename(filepath) or '_' 84 filename = os.path.basename(filepath) or '_'
84 for namespace, callback, priority, method_name in self._file_callbacks: 85 for namespace, callback, priority, method_name in self._file_callbacks:
85 has_feature = yield self.host.hasFeature(client, namespace, peer_jid) 86 has_feature = yield self.host.hasFeature(client, namespace, peer_jid)
86 if has_feature: 87 if has_feature:
87 log.info(u"{name} method will be used to send the file".format(name=method_name)) 88 log.info(u"{name} method will be used to send the file".format(name=method_name))
88 progress_id = yield callback(client, peer_jid, filepath, filename, file_desc) 89 progress_id = yield callback(client, peer_jid, filepath, filename, file_desc, extra)
89 defer.returnValue({'progress': progress_id}) 90 defer.returnValue({'progress': progress_id})
90 msg = u"Can't find any method to send file to {jid}".format(jid=peer_jid.full()) 91 msg = u"Can't find any method to send file to {jid}".format(jid=peer_jid.full())
91 log.warning(msg) 92 log.warning(msg)
92 defer.returnValue({'xmlui': xml_tools.note(u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()}) 93 defer.returnValue({'xmlui': xml_tools.note(u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()})
93 94
141 raise exceptions.NotFound(u"The namespace to unregister doesn't exist") 142 raise exceptions.NotFound(u"The namespace to unregister doesn't exist")
142 143
143 # Dialogs with user 144 # Dialogs with user
144 # the overwrite check is done here 145 # the overwrite check is done here
145 146
146 def _openFileWrite(self, client, file_path, transfer_data, file_data, stream_object): 147 def openFileWrite(self, client, file_path, transfer_data, file_data, stream_object):
148 """create SatFile or FileStremaObject for the requested file and fill suitable data
149 """
147 if stream_object: 150 if stream_object:
148 assert 'stream_object' not in transfer_data 151 assert 'stream_object' not in transfer_data
149 transfer_data['stream_object'] = stream.FileStreamObject( 152 transfer_data['stream_object'] = stream.FileStreamObject(
150 self.host, 153 self.host,
151 client, 154 client,
186 189
187 # we manage case where file already exists 190 # we manage case where file already exists
188 if os.path.exists(file_path): 191 if os.path.exists(file_path):
189 def check_overwrite(overwrite): 192 def check_overwrite(overwrite):
190 if overwrite: 193 if overwrite:
191 self._openFileWrite(client, file_path, transfer_data, file_data, stream_object) 194 self.openFileWrite(client, file_path, transfer_data, file_data, stream_object)
192 return True 195 return True
193 else: 196 else:
194 return self.getDestDir(client, peer_jid, transfer_data, file_data) 197 return self.getDestDir(client, peer_jid, transfer_data, file_data)
195 198
196 exists_d = xml_tools.deferConfirm( 199 exists_d = xml_tools.deferConfirm(
204 security_limit=SECURITY_LIMIT, 207 security_limit=SECURITY_LIMIT,
205 profile=client.profile) 208 profile=client.profile)
206 exists_d.addCallback(check_overwrite) 209 exists_d.addCallback(check_overwrite)
207 return exists_d 210 return exists_d
208 211
209 self._openFileWrite(client, file_path, transfer_data, file_data, stream_object) 212 self.openFileWrite(client, file_path, transfer_data, file_data, stream_object)
210 return True 213 return True
211 214
212 def getDestDir(self, client, peer_jid, transfer_data, file_data, stream_object=False): 215 def getDestDir(self, client, peer_jid, transfer_data, file_data, stream_object=False):
213 """Request confirmation and destination dir to user 216 """Request confirmation and destination dir to user
214 217
234 "size_human" will also be added with human readable file size 237 "size_human" will also be added with human readable file size
235 @param stream_object(bool): if True, a stream_object will be used instead of file_obj 238 @param stream_object(bool): if True, a stream_object will be used instead of file_obj
236 a stream.FileStreamObject will be used 239 a stream.FileStreamObject will be used
237 return (defer.Deferred): True if transfer is accepted 240 return (defer.Deferred): True if transfer is accepted
238 """ 241 """
242 cont,ret_value = self.host.trigger.returnPoint("FILE_getDestDir", client, peer_jid, transfer_data, file_data, stream_object)
243 if not cont:
244 return ret_value
239 filename = file_data['name'] 245 filename = file_data['name']
240 assert filename and not '/' in filename 246 assert filename and not '/' in filename
241 assert PROGRESS_ID_KEY in file_data 247 assert PROGRESS_ID_KEY in file_data
242 # human readable size 248 # human readable size
243 file_data['size_human'] = u'{:.6n} Mio'.format(float(file_data['size'])/(1024**2)) 249 file_data['size_human'] = u'{:.6n} Mio'.format(float(file_data['size'])/(1024**2))