comparison libervia/backend/plugins/plugin_misc_upload.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/plugins/plugin_misc_upload.py@524856bd7b19
children 0d7bb4df2343
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # SAT plugin for uploading files
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
5
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
15
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import os
20 import os.path
21 from pathlib import Path
22 from typing import Optional, Tuple, Union
23
24 from twisted.internet import defer
25 from twisted.words.protocols.jabber import jid
26 from twisted.words.protocols.jabber import error as jabber_error
27
28 from libervia.backend.core import exceptions
29 from libervia.backend.core.constants import Const as C
30 from libervia.backend.core.core_types import SatXMPPEntity
31 from libervia.backend.core.i18n import D_, _
32 from libervia.backend.core.log import getLogger
33 from libervia.backend.tools import xml_tools
34 from libervia.backend.tools.common import data_format
35
36 log = getLogger(__name__)
37
38
39 PLUGIN_INFO = {
40 C.PI_NAME: "File Upload",
41 C.PI_IMPORT_NAME: "UPLOAD",
42 C.PI_TYPE: C.PLUG_TYPE_MISC,
43 C.PI_MODES: C.PLUG_MODE_BOTH,
44 C.PI_MAIN: "UploadPlugin",
45 C.PI_HANDLER: "no",
46 C.PI_DESCRIPTION: _("""File upload management"""),
47 }
48
49
50 UPLOADING = D_("Please select a file to upload")
51 UPLOADING_TITLE = D_("File upload")
52
53
54 class UploadPlugin(object):
55 # TODO: plugin unload
56
57 def __init__(self, host):
58 log.info(_("plugin Upload initialization"))
59 self.host = host
60 host.bridge.add_method(
61 "file_upload",
62 ".plugin",
63 in_sign="sssss",
64 out_sign="a{ss}",
65 method=self._file_upload,
66 async_=True,
67 )
68 self._upload_callbacks = []
69
70 def _file_upload(
71 self, filepath, filename, upload_jid_s="", options='', profile=C.PROF_KEY_NONE
72 ):
73 client = self.host.get_client(profile)
74 upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None
75 options = data_format.deserialise(options)
76
77 return defer.ensureDeferred(self.file_upload(
78 client, filepath, filename or None, upload_jid, options
79 ))
80
81 async def file_upload(self, client, filepath, filename, upload_jid, options):
82 """Send a file using best available method
83
84 parameters are the same as for [upload]
85 @return (dict): action dictionary, with progress id in case of success, else xmlui
86 message
87 """
88 try:
89 progress_id, __ = await self.upload(
90 client, filepath, filename, upload_jid, options)
91 except Exception as e:
92 if (isinstance(e, jabber_error.StanzaError)
93 and e.condition == 'not-acceptable'):
94 reason = e.text
95 else:
96 reason = str(e)
97 msg = D_("Can't upload file: {reason}").format(reason=reason)
98 log.warning(msg)
99 return {
100 "xmlui": xml_tools.note(
101 msg, D_("Can't upload file"), C.XMLUI_DATA_LVL_WARNING
102 ).toXml()
103 }
104 else:
105 return {"progress": progress_id}
106
107 async def upload(
108 self,
109 client: SatXMPPEntity,
110 filepath: Union[Path, str],
111 filename: Optional[str] = None,
112 upload_jid: Optional[jid.JID] = None,
113 extra: Optional[dict]=None
114 ) -> Tuple[str, defer.Deferred]:
115 """Send a file using best available method
116
117 @param filepath: absolute path to the file
118 @param filename: name to use for the upload
119 None to use basename of the path
120 @param upload_jid: upload capable entity jid,
121 or None to use autodetected, if possible
122 @param extra: extra data/options to use for the upload, may be:
123 - ignore_tls_errors(bool): True to ignore SSL/TLS certificate verification
124 used only if HTTPS transport is needed
125 - progress_id(str): id to use for progression
126 if not specified, one will be generated
127 @param profile: %(doc_profile)s
128 @return: progress_id and a Deferred which fire download URL when upload is
129 finished
130 """
131 if extra is None:
132 extra = {}
133 if not os.path.isfile(filepath):
134 raise exceptions.DataError("The given path doesn't link to a file")
135 for method_name, available_cb, upload_cb, priority in self._upload_callbacks:
136 if upload_jid is None:
137 try:
138 upload_jid = await available_cb(client, upload_jid)
139 except exceptions.NotFound:
140 continue # no entity managing this extension found
141
142 log.info(
143 "{name} method will be used to upload the file".format(name=method_name)
144 )
145 progress_id, download_d = await upload_cb(
146 client, filepath, filename, upload_jid, extra
147 )
148 return progress_id, download_d
149
150 raise exceptions.NotFound("Can't find any method to upload a file")
151
152 def register(self, method_name, available_cb, upload_cb, priority=0):
153 """Register a fileUploading method
154
155 @param method_name(unicode): short name for the method, must be unique
156 @param available_cb(callable): method to call to check if this method is usable
157 the callback must take two arguments: upload_jid (can be None) and profile
158 the callback must return the first entity found (being upload_jid or one of its
159 components)
160 exceptions.NotFound must be raised if no entity has been found
161 @param upload_cb(callable): method to upload a file
162 must have the same signature as [file_upload]
163 must return a tuple with progress_id and a Deferred which fire download URL
164 when upload is finished
165 @param priority(int): pririoty of this method, the higher available will be used
166 """
167 assert method_name
168 for data in self._upload_callbacks:
169 if method_name == data[0]:
170 raise exceptions.ConflictError(
171 "A method with this name is already registered"
172 )
173 self._upload_callbacks.append((method_name, available_cb, upload_cb, priority))
174 self._upload_callbacks.sort(key=lambda data: data[3], reverse=True)
175
176 def unregister(self, method_name):
177 for idx, data in enumerate(self._upload_callbacks):
178 if data[0] == method_name:
179 del [idx]
180 return
181 raise exceptions.NotFound("The name to unregister doesn't exist")