comparison libervia/backend/plugins/plugin_misc_file.py @ 4231:e11b13418ba6

plugin XEP-0353, XEP-0234, jingle: WebRTC data channel signaling implementation: Implement XEP-0343: Signaling WebRTC Data Channels in Jingle. The current version of the XEP (0.3.1) has no implementation and contains some flaws. After discussing this on xsf@, Daniel (from Conversations) mentioned that they had a sprint with Larma (from Dino) to work on another version and provided me with this link: https://gist.github.com/iNPUTmice/6c56f3e948cca517c5fb129016d99e74 . I have used it for my implementation. This implementation reuses work done on Jingle A/V call (notably XEP-0176 and XEP-0167 plugins), with adaptations. When used, XEP-0234 will not handle the file itself as it normally does. This is because WebRTC has several implementations (browser for web interface, GStreamer for others), and file/data must be handled directly by the frontend. This is particularly important for web frontends, as the file is not sent from the backend but from the end-user's browser device. Among the changes, there are: - XEP-0343 implementation. - `file_send` bridge method now use serialised dict as output. - New `BaseTransportHandler.is_usable` method which get content data and returns a boolean (default to `True`) to tell if this transport can actually be used in this context (when we are initiator). Used in webRTC case to see if call data are available. - Support of `application` media type, and everything necessary to handle data channels. - Better confirmation message, with file name, size and description when available. - When file is accepted in preflight, it is specified in following `action_new` signal for actual file transfer. This way, frontend can avoid the display or 2 confirmation messages. - XEP-0166: when not specified, default `content` name is now its index number instead of a UUID. This follows the behaviour of browsers. - XEP-0353: better handling of events such as call taken by another device. - various other updates. rel 441
author Goffi <goffi@goffi.org>
date Sat, 06 Apr 2024 12:57:23 +0200
parents 4b842c1fb686
children
comparison
equal deleted inserted replaced
4230:314d3c02bb67 4231:e11b13418ba6
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 from functools import partial
20 import os 21 import os
21 import os.path 22 import os.path
22 from functools import partial 23 from pathlib import Path
24
23 from twisted.internet import defer 25 from twisted.internet import defer
24 from twisted.words.protocols.jabber import jid 26 from twisted.words.protocols.jabber import jid
25 from libervia.backend.core.i18n import _, D_ 27
28 from libervia.backend.core import exceptions
26 from libervia.backend.core.constants import Const as C 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_, _
27 from libervia.backend.core.log import getLogger 32 from libervia.backend.core.log import getLogger
28 from libervia.backend.core import exceptions
29 from libervia.backend.tools import xml_tools 33 from libervia.backend.tools import xml_tools
30 from libervia.backend.tools import stream 34 from libervia.backend.tools import stream
31 from libervia.backend.tools import utils 35 from libervia.backend.tools import utils
32 from libervia.backend.tools.common import data_format, utils as common_utils 36 from libervia.backend.tools.common import data_format, utils as common_utils
33 37
71 self.host = host 75 self.host = host
72 host.bridge.add_method( 76 host.bridge.add_method(
73 "file_send", 77 "file_send",
74 ".plugin", 78 ".plugin",
75 in_sign="ssssss", 79 in_sign="ssssss",
76 out_sign="a{ss}", 80 out_sign="s",
77 method=self._file_send, 81 method=self._file_send,
78 async_=True, 82 async_=True,
79 ) 83 )
80 self._file_managers = [] 84 self._file_managers = []
81 host.import_menu( 85 host.import_menu(
94 file_desc: str, 98 file_desc: str,
95 extra_s: str, 99 extra_s: str,
96 profile: str = C.PROF_KEY_NONE 100 profile: str = C.PROF_KEY_NONE
97 ) -> defer.Deferred: 101 ) -> defer.Deferred:
98 client = self.host.get_client(profile) 102 client = self.host.get_client(profile)
99 return defer.ensureDeferred(self.file_send( 103 d = defer.ensureDeferred(self.file_send(
100 client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, 104 client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None,
101 data_format.deserialise(extra_s) 105 data_format.deserialise(extra_s)
102 )) 106 ))
107 d.addCallback(data_format.serialise)
108 return d
103 109
104 async def file_send( 110 async def file_send(
105 self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None 111 self,
106 ): 112 client: SatXMPPEntity,
113 peer_jid: jid.JID,
114 filepath: str|Path,
115 filename: str|None=None,
116 file_desc: str|None=None,
117 extra: dict|None=None
118 ) -> dict:
107 """Send a file using best available method 119 """Send a file using best available method
108 120
109 @param peer_jid(jid.JID): jid of the destinee 121 @param peer_jid: jid of the destinee
110 @param filepath(str): absolute path to the file 122 @param filepath: absolute path to the file
111 @param filename(unicode, None): name to use, or None to find it from filepath 123 @param filename: name to use, or None to find it from filepath
112 @param file_desc(unicode, None): description of the file 124 @param file_desc: description of the file
113 @param profile: %(doc_profile)s 125 @param profile: %(doc_profile)s
114 @return (dict): action dictionary, with progress id in case of success, else 126 @return: action dictionary, with progress id in case of success, else
115 xmlui message 127 xmlui message
116 """ 128 """
117 if not os.path.isfile(filepath): 129 filepath = Path(filepath)
130 if not filepath.is_file():
118 raise exceptions.DataError("The given path doesn't link to a file") 131 raise exceptions.DataError("The given path doesn't link to a file")
119 if not filename: 132 if not filename:
120 filename = os.path.basename(filepath) or "_" 133 filename = filepath.name
121 for manager, priority in self._file_managers: 134 for manager, __ in self._file_managers:
122 if await utils.as_deferred(manager.can_handle_file_send, 135 if await utils.as_deferred(manager.can_handle_file_send,
123 client, peer_jid, filepath): 136 client, peer_jid, str(filepath)):
124 try: 137 try:
125 method_name = manager.name 138 method_name = manager.name
126 except AttributeError: 139 except AttributeError:
127 method_name = manager.__class__.__name__ 140 method_name = manager.__class__.__name__
128 log.info( 141 log.info(
129 _("{name} method will be used to send the file").format( 142 _("{name} method will be used to send the file").format(
130 name=method_name 143 name=method_name
131 ) 144 )
132 ) 145 )
133 try: 146 try:
134 progress_id = await utils.as_deferred( 147 file_data= await utils.as_deferred(
135 manager.file_send, client, peer_jid, filepath, filename, file_desc, 148 manager.file_send, client, peer_jid, str(filepath), filename, file_desc,
136 extra 149 extra
137 ) 150 )
138 except Exception as e: 151 except Exception as e:
139 log.warning( 152 log.warning(
140 _("Can't send {filepath} to {peer_jid} with {method_name}: " 153 _("Can't send {filepath} to {peer_jid} with {method_name}: "
144 method_name=method_name, 157 method_name=method_name,
145 reason=e 158 reason=e
146 ) 159 )
147 ) 160 )
148 continue 161 continue
149 return {"progress": progress_id} 162 if "progress" not in file_data:
163 raise exceptions.InternalError(
164 '"progress" should be set in "file_send" returned file data dict.'
165 )
166 return file_data
150 msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full()) 167 msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full())
151 log.warning(msg) 168 log.warning(msg)
152 return { 169 return {
153 "xmlui": xml_tools.note( 170 "xmlui": xml_tools.note(
154 "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING 171 "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING