Mercurial > libervia-backend
comparison sat/plugins/plugin_misc_file.py @ 3403:404d4b29de52
plugin file, XEP-0234: registering is now done by class + use of async:
- instead of registering a callback, a file sending manager now register itself and must
implement some well known method (`fileSend`, `canHandleFileSend`) and optionally a
`name` attribute
- `utils.asDeferred` is now used for callbacks, so all type of methods including
coroutines can be used.
- feature checking is now handled by `canHandleFileSend` method instead of simple
namespace check, this allows to use a method when namespace can't be checked (this is
the case when a file is sent to a bare jid with jingle)
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 12 Nov 2020 14:53:15 +0100 |
parents | f2bb57348587 |
children | be6d91572633 |
comparison
equal
deleted
inserted
replaced
3402:08a3e34aead1 | 3403:404d4b29de52 |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # 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/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 import os | |
21 import os.path | |
22 from functools import partial | |
23 from twisted.internet import defer | |
24 from twisted.words.protocols.jabber import jid | |
20 from sat.core.i18n import _, D_ | 25 from sat.core.i18n import _, D_ |
21 from sat.core.constants import Const as C | 26 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 27 from sat.core.log import getLogger |
23 | |
24 log = getLogger(__name__) | |
25 from sat.core import exceptions | 28 from sat.core import exceptions |
26 from sat.tools import xml_tools | 29 from sat.tools import xml_tools |
27 from sat.tools import stream | 30 from sat.tools import stream |
28 from twisted.internet import defer | 31 from sat.tools import utils |
29 from twisted.words.protocols.jabber import jid | 32 |
30 import os | 33 |
31 import os.path | 34 log = getLogger(__name__) |
32 | 35 |
33 | 36 |
34 PLUGIN_INFO = { | 37 PLUGIN_INFO = { |
35 C.PI_NAME: "File Tansfer", | 38 C.PI_NAME: "File Tansfer", |
36 C.PI_IMPORT_NAME: "FILE", | 39 C.PI_IMPORT_NAME: "FILE", |
71 in_sign="ssssa{ss}s", | 74 in_sign="ssssa{ss}s", |
72 out_sign="a{ss}", | 75 out_sign="a{ss}", |
73 method=self._fileSend, | 76 method=self._fileSend, |
74 async_=True, | 77 async_=True, |
75 ) | 78 ) |
76 self._file_callbacks = [] | 79 self._file_managers = [] |
77 host.importMenu( | 80 host.importMenu( |
78 (D_("Action"), D_("send file")), | 81 (D_("Action"), D_("send file")), |
79 self._fileSendMenu, | 82 self._fileSendMenu, |
80 security_limit=10, | 83 security_limit=10, |
81 help_string=D_("Send a file"), | 84 help_string=D_("Send a file"), |
83 ) | 86 ) |
84 | 87 |
85 def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, | 88 def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, |
86 profile=C.PROF_KEY_NONE): | 89 profile=C.PROF_KEY_NONE): |
87 client = self.host.getClient(profile) | 90 client = self.host.getClient(profile) |
88 return self.fileSend( | 91 return defer.ensureDeferred(self.fileSend( |
89 client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra | 92 client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra |
90 ) | 93 )) |
91 | 94 |
92 @defer.inlineCallbacks | 95 async def fileSend( |
93 def fileSend( | |
94 self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None | 96 self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None |
95 ): | 97 ): |
96 """Send a file using best available method | 98 """Send a file using best available method |
97 | 99 |
98 @param peer_jid(jid.JID): jid of the destinee | 100 @param peer_jid(jid.JID): jid of the destinee |
105 """ | 107 """ |
106 if not os.path.isfile(filepath): | 108 if not os.path.isfile(filepath): |
107 raise exceptions.DataError("The given path doesn't link to a file") | 109 raise exceptions.DataError("The given path doesn't link to a file") |
108 if not filename: | 110 if not filename: |
109 filename = os.path.basename(filepath) or "_" | 111 filename = os.path.basename(filepath) or "_" |
110 for namespace, callback, priority, method_name in self._file_callbacks: | 112 for manager, priority in self._file_managers: |
111 has_feature = yield self.host.hasFeature(client, namespace, peer_jid) | 113 if await utils.asDeferred(manager.canHandleFileSend, |
112 if has_feature: | 114 client, peer_jid, filepath): |
115 try: | |
116 method_name = manager.name | |
117 except AttributeError: | |
118 method_name = manager.__class__.__name__ | |
113 log.info( | 119 log.info( |
114 "{name} method will be used to send the file".format( | 120 _("{name} method will be used to send the file").format( |
115 name=method_name | 121 name=method_name |
116 ) | 122 ) |
117 ) | 123 ) |
118 progress_id = yield callback( | 124 try: |
119 client, peer_jid, filepath, filename, file_desc, extra | 125 progress_id = await utils.asDeferred( |
120 ) | 126 manager.fileSend, client, peer_jid, filepath, filename, file_desc, |
121 defer.returnValue({"progress": progress_id}) | 127 extra |
128 ) | |
129 except Exception as e: | |
130 log.warning( | |
131 _("Can't send {filepath} to {peer_jid} with {method_name}: " | |
132 "{reason}").format( | |
133 filepath=filepath, | |
134 peer_jid=peer_jid, | |
135 method_name=method_name, | |
136 reason=e | |
137 ) | |
138 ) | |
139 continue | |
140 return {"progress": progress_id} | |
122 msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full()) | 141 msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full()) |
123 log.warning(msg) | 142 log.warning(msg) |
124 defer.returnValue( | 143 return { |
125 { | 144 "xmlui": xml_tools.note( |
126 "xmlui": xml_tools.note( | 145 "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING |
127 "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING | 146 ).toXml() |
128 ).toXml() | 147 } |
129 } | 148 |
130 ) | 149 def _onFileChoosed(self, peer_jid, data, profile): |
131 | 150 client = self.host.getClient(profile) |
132 def _onFileChoosed(self, client, peer_jid, data): | |
133 cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE)) | 151 cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE)) |
134 if cancelled: | 152 if cancelled: |
135 return | 153 return |
136 path = data["path"] | 154 path = data["path"] |
137 return self.fileSend(client, peer_jid, path) | 155 return self.fileSend(client, peer_jid, path) |
145 jid_ = jid.JID(data["jid"]) | 163 jid_ = jid.JID(data["jid"]) |
146 except RuntimeError: | 164 except RuntimeError: |
147 raise exceptions.DataError(_("Invalid JID")) | 165 raise exceptions.DataError(_("Invalid JID")) |
148 | 166 |
149 file_choosed_id = self.host.registerCallback( | 167 file_choosed_id = self.host.registerCallback( |
150 lambda data, profile: self._onFileChoosed( | 168 partial(self._onFileChoosed, jid_), |
151 self.host.getClient(profile), jid_, data | |
152 ), | |
153 with_data=True, | 169 with_data=True, |
154 one_shot=True, | 170 one_shot=True, |
155 ) | 171 ) |
156 xml_ui = xml_tools.XMLUI( | 172 xml_ui = xml_tools.XMLUI( |
157 C.XMLUI_DIALOG, | 173 C.XMLUI_DIALOG, |
163 submit_id=file_choosed_id, | 179 submit_id=file_choosed_id, |
164 ) | 180 ) |
165 | 181 |
166 return {"xmlui": xml_ui.toXml()} | 182 return {"xmlui": xml_ui.toXml()} |
167 | 183 |
168 def register(self, namespace, callback, priority=0, method_name=None): | 184 def register(self, manager, priority: int = 0) -> None: |
169 """Register a fileSending method | 185 """Register a fileSending manager |
170 | 186 |
171 @param namespace(unicode): XEP namespace | 187 @param manager: object implementing canHandleFileSend, and fileSend methods |
172 @param callback(callable): method to call (must have the same signature as [fileSend]) | 188 @param priority: pririoty of this manager, the higher available will be used |
173 @param priority(int): pririoty of this method, the higher available will be used | 189 """ |
174 @param method_name(unicode): short name for the method, namespace will be used if None | 190 m_data = (manager, priority) |
175 """ | 191 if m_data in self._file_managers: |
176 for data in self._file_callbacks: | 192 raise exceptions.ConflictError( |
177 if namespace == data[0]: | 193 f"Manager {manager} is already registered" |
178 raise exceptions.ConflictError( | 194 ) |
179 "A method with this namespace is already registered" | 195 if not hasattr(manager, "canHandleFileSend") or not hasattr(manager, "fileSend"): |
180 ) | 196 raise ValueError( |
181 self._file_callbacks.append( | 197 f'{manager} must have both "canHandleFileSend" and "fileSend" methods to ' |
182 (namespace, callback, priority, method_name or namespace) | 198 'be registered') |
183 ) | 199 self._file_managers.append(m_data) |
184 self._file_callbacks.sort(key=lambda data: data[2], reverse=True) | 200 self._file_managers.sort(key=lambda m: m[1], reverse=True) |
185 | 201 |
186 def unregister(self, namespace): | 202 def unregister(self, manager): |
187 for idx, data in enumerate(self._file_callbacks): | 203 for idx, data in enumerate(self._file_managers): |
188 if data[0] == namespace: | 204 if data[0] == manager: |
189 del [idx] | 205 break |
190 return | 206 else: |
191 raise exceptions.NotFound("The namespace to unregister doesn't exist") | 207 raise exceptions.NotFound("The file manager {manager} is not registered") |
208 del self._file_managers[idx] | |
192 | 209 |
193 # Dialogs with user | 210 # Dialogs with user |
194 # the overwrite check is done here | 211 # the overwrite check is done here |
195 | 212 |
196 def openFileWrite(self, client, file_path, transfer_data, file_data, stream_object): | 213 def openFileWrite(self, client, file_path, transfer_data, file_data, stream_object): |