comparison libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_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_core_template.py@524856bd7b19
children
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # Libervia 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 from types import MethodType
20 from functools import partialmethod
21 from twisted.internet import defer, reactor
22 from libervia.backend.core.i18n import _
23 from libervia.backend.core.log import getLogger
24 from libervia.backend.core.exceptions import BridgeInitError
25 from libervia.backend.tools import config
26 from txdbus import client, objects, error
27 from txdbus.interface import DBusInterface, Method, Signal
28
29
30 log = getLogger(__name__)
31
32 # Interface prefix
33 const_INT_PREFIX = config.config_get(
34 config.parse_main_conf(),
35 "",
36 "bridge_dbus_int_prefix",
37 "org.libervia.Libervia")
38 const_ERROR_PREFIX = const_INT_PREFIX + ".error"
39 const_OBJ_PATH = "/org/libervia/Libervia/bridge"
40 const_CORE_SUFFIX = ".core"
41 const_PLUGIN_SUFFIX = ".plugin"
42
43
44 class ParseError(Exception):
45 pass
46
47
48 class DBusException(Exception):
49 pass
50
51
52 class MethodNotRegistered(DBusException):
53 dbusErrorName = const_ERROR_PREFIX + ".MethodNotRegistered"
54
55
56 class GenericException(DBusException):
57 def __init__(self, twisted_error):
58 """
59
60 @param twisted_error (Failure): instance of twisted Failure
61 error message is used to store a repr of message and condition in a tuple,
62 so it can be evaluated by the frontend bridge.
63 """
64 try:
65 # twisted_error.value is a class
66 class_ = twisted_error.value().__class__
67 except TypeError:
68 # twisted_error.value is an instance
69 class_ = twisted_error.value.__class__
70 data = twisted_error.getErrorMessage()
71 try:
72 data = (data, twisted_error.value.condition)
73 except AttributeError:
74 data = (data,)
75 else:
76 data = (str(twisted_error),)
77 self.dbusErrorName = ".".join(
78 (const_ERROR_PREFIX, class_.__module__, class_.__name__)
79 )
80 super(GenericException, self).__init__(repr(data))
81
82 @classmethod
83 def create_and_raise(cls, exc):
84 raise cls(exc)
85
86
87 class DBusObject(objects.DBusObject):
88
89 core_iface = DBusInterface(
90 const_INT_PREFIX + const_CORE_SUFFIX,
91 ##METHODS_DECLARATIONS_PART##
92 ##SIGNALS_DECLARATIONS_PART##
93 )
94 plugin_iface = DBusInterface(
95 const_INT_PREFIX + const_PLUGIN_SUFFIX
96 )
97
98 dbusInterfaces = [core_iface, plugin_iface]
99
100 def __init__(self, path):
101 super().__init__(path)
102 log.debug("Init DBusObject...")
103 self.cb = {}
104
105 def register_method(self, name, cb):
106 self.cb[name] = cb
107
108 def _callback(self, name, *args, **kwargs):
109 """Call the callback if it exists, raise an exception else"""
110 try:
111 cb = self.cb[name]
112 except KeyError:
113 raise MethodNotRegistered
114 else:
115 d = defer.maybeDeferred(cb, *args, **kwargs)
116 d.addErrback(GenericException.create_and_raise)
117 return d
118
119 ##METHODS_PART##
120
121 class bridge:
122
123 def __init__(self):
124 log.info("Init DBus...")
125 self._obj = DBusObject(const_OBJ_PATH)
126
127 async def post_init(self):
128 try:
129 conn = await client.connect(reactor)
130 except error.DBusException as e:
131 if e.errName == "org.freedesktop.DBus.Error.NotSupported":
132 log.error(
133 _(
134 "D-Bus is not launched, please see README to see instructions on "
135 "how to launch it"
136 )
137 )
138 raise BridgeInitError(str(e))
139
140 conn.exportObject(self._obj)
141 await conn.requestBusName(const_INT_PREFIX)
142
143 ##SIGNALS_PART##
144 def register_method(self, name, callback):
145 log.debug(f"registering DBus bridge method [{name}]")
146 self._obj.register_method(name, callback)
147
148 def emit_signal(self, name, *args):
149 self._obj.emitSignal(name, *args)
150
151 def add_method(
152 self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={}
153 ):
154 """Dynamically add a method to D-Bus bridge"""
155 # FIXME: doc parameter is kept only temporary, the time to remove it from calls
156 log.debug(f"Adding method {name!r} to D-Bus bridge")
157 self._obj.plugin_iface.addMethod(
158 Method(name, arguments=in_sign, returns=out_sign)
159 )
160 # we have to create a method here instead of using partialmethod, because txdbus
161 # uses __func__ which doesn't work with partialmethod
162 def caller(self_, *args, **kwargs):
163 return self_._callback(name, *args, **kwargs)
164 setattr(self._obj, f"dbus_{name}", MethodType(caller, self._obj))
165 self.register_method(name, method)
166
167 def add_signal(self, name, int_suffix, signature, doc={}):
168 """Dynamically add a signal to D-Bus bridge"""
169 log.debug(f"Adding signal {name!r} to D-Bus bridge")
170 self._obj.plugin_iface.addSignal(Signal(name, signature))
171 setattr(bridge, name, partialmethod(bridge.emit_signal, name))