Mercurial > libervia-backend
comparison libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.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/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py@524856bd7b19 |
children | 0d7bb4df2343 |
comparison
equal
deleted
inserted
replaced
4070:d10748475025 | 4071:4b842c1fb686 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # SàT communication bridge | |
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 asyncio | |
20 import dbus | |
21 import ast | |
22 from libervia.backend.core.i18n import _ | |
23 from libervia.backend.tools import config | |
24 from libervia.backend.core.log import getLogger | |
25 from libervia.backend.core.exceptions import BridgeExceptionNoService, BridgeInitError | |
26 from dbus.mainloop.glib import DBusGMainLoop | |
27 from .bridge_frontend import BridgeException | |
28 | |
29 | |
30 DBusGMainLoop(set_as_default=True) | |
31 log = getLogger(__name__) | |
32 | |
33 | |
34 # Interface prefix | |
35 const_INT_PREFIX = config.config_get( | |
36 config.parse_main_conf(), | |
37 "", | |
38 "bridge_dbus_int_prefix", | |
39 "org.libervia.Libervia") | |
40 const_ERROR_PREFIX = const_INT_PREFIX + ".error" | |
41 const_OBJ_PATH = '/org/libervia/Libervia/bridge' | |
42 const_CORE_SUFFIX = ".core" | |
43 const_PLUGIN_SUFFIX = ".plugin" | |
44 const_TIMEOUT = 120 | |
45 | |
46 | |
47 def dbus_to_bridge_exception(dbus_e): | |
48 """Convert a DBusException to a BridgeException. | |
49 | |
50 @param dbus_e (DBusException) | |
51 @return: BridgeException | |
52 """ | |
53 full_name = dbus_e.get_dbus_name() | |
54 if full_name.startswith(const_ERROR_PREFIX): | |
55 name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1:] | |
56 else: | |
57 name = full_name | |
58 # XXX: dbus_e.args doesn't contain the original DBusException args, but we | |
59 # receive its serialized form in dbus_e.args[0]. From that we can rebuild | |
60 # the original arguments list thanks to ast.literal_eval (secure eval). | |
61 message = dbus_e.get_dbus_message() # similar to dbus_e.args[0] | |
62 try: | |
63 message, condition = ast.literal_eval(message) | |
64 except (SyntaxError, ValueError, TypeError): | |
65 condition = '' | |
66 return BridgeException(name, message, condition) | |
67 | |
68 | |
69 class bridge: | |
70 | |
71 def bridge_connect(self, callback, errback): | |
72 try: | |
73 self.sessions_bus = dbus.SessionBus() | |
74 self.db_object = self.sessions_bus.get_object(const_INT_PREFIX, | |
75 const_OBJ_PATH) | |
76 self.db_core_iface = dbus.Interface(self.db_object, | |
77 dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX) | |
78 self.db_plugin_iface = dbus.Interface(self.db_object, | |
79 dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX) | |
80 except dbus.exceptions.DBusException as e: | |
81 if e._dbus_error_name in ('org.freedesktop.DBus.Error.ServiceUnknown', | |
82 'org.freedesktop.DBus.Error.Spawn.ExecFailed'): | |
83 errback(BridgeExceptionNoService()) | |
84 elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported': | |
85 log.error(_("D-Bus is not launched, please see README to see instructions on how to launch it")) | |
86 errback(BridgeInitError) | |
87 else: | |
88 errback(e) | |
89 else: | |
90 callback() | |
91 #props = self.db_core_iface.getProperties() | |
92 | |
93 def register_signal(self, functionName, handler, iface="core"): | |
94 if iface == "core": | |
95 self.db_core_iface.connect_to_signal(functionName, handler) | |
96 elif iface == "plugin": | |
97 self.db_plugin_iface.connect_to_signal(functionName, handler) | |
98 else: | |
99 log.error(_('Unknown interface')) | |
100 | |
101 def __getattribute__(self, name): | |
102 """ usual __getattribute__ if the method exists, else try to find a plugin method """ | |
103 try: | |
104 return object.__getattribute__(self, name) | |
105 except AttributeError: | |
106 # The attribute is not found, we try the plugin proxy to find the requested method | |
107 | |
108 def get_plugin_method(*args, **kwargs): | |
109 # We first check if we have an async call. We detect this in two ways: | |
110 # - if we have the 'callback' and 'errback' keyword arguments | |
111 # - or if the last two arguments are callable | |
112 | |
113 async_ = False | |
114 args = list(args) | |
115 | |
116 if kwargs: | |
117 if 'callback' in kwargs: | |
118 async_ = True | |
119 _callback = kwargs.pop('callback') | |
120 _errback = kwargs.pop('errback', lambda failure: log.error(str(failure))) | |
121 try: | |
122 args.append(kwargs.pop('profile')) | |
123 except KeyError: | |
124 try: | |
125 args.append(kwargs.pop('profile_key')) | |
126 except KeyError: | |
127 pass | |
128 # at this point, kwargs should be empty | |
129 if kwargs: | |
130 log.warning("unexpected keyword arguments, they will be ignored: {}".format(kwargs)) | |
131 elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]): | |
132 async_ = True | |
133 _errback = args.pop() | |
134 _callback = args.pop() | |
135 | |
136 method = getattr(self.db_plugin_iface, name) | |
137 | |
138 if async_: | |
139 kwargs['timeout'] = const_TIMEOUT | |
140 kwargs['reply_handler'] = _callback | |
141 kwargs['error_handler'] = lambda err: _errback(dbus_to_bridge_exception(err)) | |
142 | |
143 try: | |
144 return method(*args, **kwargs) | |
145 except ValueError as e: | |
146 if e.args[0].startswith("Unable to guess signature"): | |
147 # XXX: if frontend is started too soon after backend, the | |
148 # inspection misses methods (notably plugin dynamically added | |
149 # methods). The following hack works around that by redoing the | |
150 # cache of introspected methods signatures. | |
151 log.debug("using hack to work around inspection issue") | |
152 proxy = self.db_plugin_iface.proxy_object | |
153 IN_PROGRESS = proxy.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS | |
154 proxy._introspect_state = IN_PROGRESS | |
155 proxy._Introspect() | |
156 return self.db_plugin_iface.get_dbus_method(name)(*args, **kwargs) | |
157 raise e | |
158 | |
159 return get_plugin_method | |
160 | |
161 ##METHODS_PART## | |
162 | |
163 class AIOBridge(bridge): | |
164 | |
165 def register_signal(self, functionName, handler, iface="core"): | |
166 loop = asyncio.get_running_loop() | |
167 async_handler = lambda *args: asyncio.run_coroutine_threadsafe(handler(*args), loop) | |
168 return super().register_signal(functionName, async_handler, iface) | |
169 | |
170 def __getattribute__(self, name): | |
171 """ usual __getattribute__ if the method exists, else try to find a plugin method """ | |
172 try: | |
173 return object.__getattribute__(self, name) | |
174 except AttributeError: | |
175 # The attribute is not found, we try the plugin proxy to find the requested method | |
176 def get_plugin_method(*args, **kwargs): | |
177 loop = asyncio.get_running_loop() | |
178 fut = loop.create_future() | |
179 method = getattr(self.db_plugin_iface, name) | |
180 reply_handler = lambda ret=None: loop.call_soon_threadsafe( | |
181 fut.set_result, ret) | |
182 error_handler = lambda err: loop.call_soon_threadsafe( | |
183 fut.set_exception, dbus_to_bridge_exception(err)) | |
184 try: | |
185 method( | |
186 *args, | |
187 **kwargs, | |
188 timeout=const_TIMEOUT, | |
189 reply_handler=reply_handler, | |
190 error_handler=error_handler | |
191 ) | |
192 except ValueError as e: | |
193 if e.args[0].startswith("Unable to guess signature"): | |
194 # same hack as for bridge.__getattribute__ | |
195 log.warning("using hack to work around inspection issue") | |
196 proxy = self.db_plugin_iface.proxy_object | |
197 IN_PROGRESS = proxy.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS | |
198 proxy._introspect_state = IN_PROGRESS | |
199 proxy._Introspect() | |
200 self.db_plugin_iface.get_dbus_method(name)( | |
201 *args, | |
202 **kwargs, | |
203 timeout=const_TIMEOUT, | |
204 reply_handler=reply_handler, | |
205 error_handler=error_handler | |
206 ) | |
207 | |
208 else: | |
209 raise e | |
210 return fut | |
211 | |
212 return get_plugin_method | |
213 | |
214 def bridge_connect(self): | |
215 loop = asyncio.get_running_loop() | |
216 fut = loop.create_future() | |
217 super().bridge_connect( | |
218 callback=lambda: loop.call_soon_threadsafe(fut.set_result, None), | |
219 errback=lambda e: loop.call_soon_threadsafe(fut.set_exception, e) | |
220 ) | |
221 return fut | |
222 | |
223 ##ASYNC_METHODS_PART## |