Mercurial > libervia-backend
comparison sat/plugins/plugin_misc_upload.py @ 3089:e75024e41f81
plugin upload, XEP-0363: code modernisation + preparation for extension:
- use of async/await syntax
- fileUpload's options are now serialised, allowing non string values
- (XEP-0363) Slot is now a dataclass, so it can be modified by other plugins
- (XEP-0363) Moved SSL related code to the new tools.web module
- (XEP-0363) added `XEP-0363_upload_size` and `XEP-0363_upload` trigger points
- a Deferred is not used anymore for `progress_id`, the value is directly returned
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 20 Dec 2019 12:28:04 +0100 |
parents | ab2696e34d29 |
children | 9d0df638c8b4 |
comparison
equal
deleted
inserted
replaced
3088:d1464548055a | 3089:e75024e41f81 |
---|---|
1 #!/usr/bin/env python3 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | |
3 | 2 |
4 # SAT plugin for file tansfer | 3 # SAT plugin for uploading files |
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) | 4 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
6 | 5 |
7 # This program is free software: you can redistribute it and/or modify | 6 # This program is free software: you can redistribute it and/or modify |
8 # it under the terms of the GNU Affero General Public License as published by | 7 # it under the terms of the GNU Affero General Public License as published by |
9 # the Free Software Foundation, either version 3 of the License, or | 8 # the Free Software Foundation, either version 3 of the License, or |
15 # GNU Affero General Public License for more details. | 14 # GNU Affero General Public License for more details. |
16 | 15 |
17 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 18 |
20 from sat.core.i18n import _, D_ | 19 import os |
21 from sat.core.constants import Const as C | 20 import os.path |
22 from sat.core.log import getLogger | |
23 | |
24 log = getLogger(__name__) | |
25 from sat.core import exceptions | |
26 from sat.tools import xml_tools | |
27 from twisted.internet import defer | 21 from twisted.internet import defer |
28 from twisted.words.protocols.jabber import jid | 22 from twisted.words.protocols.jabber import jid |
29 from twisted.words.protocols.jabber import error as jabber_error | 23 from twisted.words.protocols.jabber import error as jabber_error |
30 import os | 24 from sat.core.i18n import _, D_ |
31 import os.path | 25 from sat.core.constants import Const as C |
26 from sat.tools.common import data_format | |
27 from sat.core.log import getLogger | |
28 from sat.core import exceptions | |
29 from sat.tools import xml_tools | |
30 | |
31 log = getLogger(__name__) | |
32 | 32 |
33 | 33 |
34 PLUGIN_INFO = { | 34 PLUGIN_INFO = { |
35 C.PI_NAME: "File Upload", | 35 C.PI_NAME: "File Upload", |
36 C.PI_IMPORT_NAME: "UPLOAD", | 36 C.PI_IMPORT_NAME: "UPLOAD", |
41 } | 41 } |
42 | 42 |
43 | 43 |
44 UPLOADING = D_("Please select a file to upload") | 44 UPLOADING = D_("Please select a file to upload") |
45 UPLOADING_TITLE = D_("File upload") | 45 UPLOADING_TITLE = D_("File upload") |
46 BOOL_OPTIONS = ("ignore_tls_errors",) | |
47 | 46 |
48 | 47 |
49 class UploadPlugin(object): | 48 class UploadPlugin(object): |
50 # TODO: plugin unload | 49 # TODO: plugin unload |
51 | 50 |
53 log.info(_("plugin Upload initialization")) | 52 log.info(_("plugin Upload initialization")) |
54 self.host = host | 53 self.host = host |
55 host.bridge.addMethod( | 54 host.bridge.addMethod( |
56 "fileUpload", | 55 "fileUpload", |
57 ".plugin", | 56 ".plugin", |
58 in_sign="sssa{ss}s", | 57 in_sign="sssss", |
59 out_sign="a{ss}", | 58 out_sign="a{ss}", |
60 method=self._fileUpload, | 59 method=self._fileUpload, |
61 async_=True, | 60 async_=True, |
62 ) | 61 ) |
63 self._upload_callbacks = [] | 62 self._upload_callbacks = [] |
64 | 63 |
65 def _fileUpload( | 64 def _fileUpload( |
66 self, filepath, filename, upload_jid_s="", options=None, profile=C.PROF_KEY_NONE | 65 self, filepath, filename, upload_jid_s="", options='', profile=C.PROF_KEY_NONE |
67 ): | 66 ): |
68 client = self.host.getClient(profile) | 67 client = self.host.getClient(profile) |
69 upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None | 68 upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None |
70 if options is None: | 69 options = data_format.deserialise(options) |
71 options = {} | |
72 # we convert values that are well-known booleans | |
73 for bool_option in BOOL_OPTIONS: | |
74 try: | |
75 options[bool_option] = C.bool(options[bool_option]) | |
76 except KeyError: | |
77 pass | |
78 | 70 |
79 return self.fileUpload( | 71 return defer.ensureDeferred(self.fileUpload( |
80 client, filepath, filename or None, upload_jid, options or None | 72 client, filepath, filename or None, upload_jid, options |
81 ) | 73 )) |
82 | 74 |
83 def fileUpload(self, client, filepath, filename, upload_jid, options): | 75 async def fileUpload(self, client, filepath, filename, upload_jid, options): |
84 """Send a file using best available method | 76 """Send a file using best available method |
85 | 77 |
86 parameters are the same as for [upload] | 78 parameters are the same as for [upload] |
87 @return (dict): action dictionary, with progress id in case of success, else xmlui | 79 @return (dict): action dictionary, with progress id in case of success, else xmlui |
88 message | 80 message |
89 """ | 81 """ |
90 | 82 |
91 def uploadCb(data): | 83 try: |
92 progress_id, __ = data | 84 progress_id, __ = await self.upload( |
93 return {"progress": progress_id} | 85 client, filepath, filename, upload_jid, options) |
94 | 86 except Exception as e: |
95 def uploadEb(fail): | 87 if (isinstance(e, jabber_error.StanzaError) |
96 if (isinstance(fail.value, jabber_error.StanzaError) | 88 and e.condition == 'not-acceptable'): |
97 and fail.value.condition == 'not-acceptable'): | 89 reason = e.text |
98 reason = fail.value.text | |
99 else: | 90 else: |
100 reason = str(fail.value) | 91 reason = str(e) |
101 msg = D_("Can't upload file: {reason}").format(reason=reason) | 92 msg = D_("Can't upload file: {reason}").format(reason=reason) |
102 log.warning(msg) | 93 log.warning(msg) |
103 return { | 94 return { |
104 "xmlui": xml_tools.note( | 95 "xmlui": xml_tools.note( |
105 msg, D_("Can't upload file"), C.XMLUI_DATA_LVL_WARNING | 96 msg, D_("Can't upload file"), C.XMLUI_DATA_LVL_WARNING |
106 ).toXml() | 97 ).toXml() |
107 } | 98 } |
99 else: | |
100 return {"progress": progress_id} | |
108 | 101 |
109 d = self.upload(client, filepath, filename, upload_jid, options) | 102 async def upload(self, client, filepath, filename=None, upload_jid=None, |
110 d.addCallback(uploadCb) | 103 options=None): |
111 d.addErrback(uploadEb) | |
112 return d | |
113 | |
114 @defer.inlineCallbacks | |
115 def upload(self, client, filepath, filename=None, upload_jid=None, options=None): | |
116 """Send a file using best available method | 104 """Send a file using best available method |
117 | 105 |
118 @param filepath(str): absolute path to the file | 106 @param filepath(str): absolute path to the file |
119 @param filename(None, unicode): name to use for the upload | 107 @param filename(None, unicode): name to use for the upload |
120 None to use basename of the path | 108 None to use basename of the path |
131 options = {} | 119 options = {} |
132 if not os.path.isfile(filepath): | 120 if not os.path.isfile(filepath): |
133 raise exceptions.DataError("The given path doesn't link to a file") | 121 raise exceptions.DataError("The given path doesn't link to a file") |
134 for method_name, available_cb, upload_cb, priority in self._upload_callbacks: | 122 for method_name, available_cb, upload_cb, priority in self._upload_callbacks: |
135 try: | 123 try: |
136 upload_jid = yield available_cb(upload_jid, client.profile) | 124 upload_jid = await available_cb(client, upload_jid) |
137 except exceptions.NotFound: | 125 except exceptions.NotFound: |
138 continue # no entity managing this extension found | 126 continue # no entity managing this extension found |
139 | 127 |
140 log.info( | 128 log.info( |
141 "{name} method will be used to upload the file".format(name=method_name) | 129 "{name} method will be used to upload the file".format(name=method_name) |
142 ) | 130 ) |
143 progress_id_d, download_d = yield upload_cb( | 131 progress_id, download_d = await upload_cb( |
144 filepath, filename, upload_jid, options, client.profile | 132 client, filepath, filename, upload_jid, options |
145 ) | 133 ) |
146 progress_id = yield progress_id_d | 134 return progress_id, download_d |
147 defer.returnValue((progress_id, download_d)) | |
148 | 135 |
149 raise exceptions.NotFound("Can't find any method to upload a file") | 136 raise exceptions.NotFound("Can't find any method to upload a file") |
150 | 137 |
151 def register(self, method_name, available_cb, upload_cb, priority=0): | 138 def register(self, method_name, available_cb, upload_cb, priority=0): |
152 """Register a fileUploading method | 139 """Register a fileUploading method |