dbus-fast 3.1.2__cp310-cp310-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbus_fast/__init__.py +82 -0
- dbus_fast/__version__.py +10 -0
- dbus_fast/_private/__init__.py +1 -0
- dbus_fast/_private/_cython_compat.py +14 -0
- dbus_fast/_private/address.cpython-310-darwin.so +0 -0
- dbus_fast/_private/address.pxd +15 -0
- dbus_fast/_private/address.py +119 -0
- dbus_fast/_private/constants.py +20 -0
- dbus_fast/_private/marshaller.cpython-310-darwin.so +0 -0
- dbus_fast/_private/marshaller.pxd +110 -0
- dbus_fast/_private/marshaller.py +231 -0
- dbus_fast/_private/unmarshaller.cpython-310-darwin.so +0 -0
- dbus_fast/_private/unmarshaller.pxd +261 -0
- dbus_fast/_private/unmarshaller.py +904 -0
- dbus_fast/_private/util.py +177 -0
- dbus_fast/aio/__init__.py +5 -0
- dbus_fast/aio/message_bus.py +578 -0
- dbus_fast/aio/message_reader.cpython-310-darwin.so +0 -0
- dbus_fast/aio/message_reader.pxd +13 -0
- dbus_fast/aio/message_reader.py +51 -0
- dbus_fast/aio/proxy_object.py +208 -0
- dbus_fast/auth.py +125 -0
- dbus_fast/constants.py +152 -0
- dbus_fast/errors.py +81 -0
- dbus_fast/glib/__init__.py +3 -0
- dbus_fast/glib/message_bus.py +513 -0
- dbus_fast/glib/proxy_object.py +318 -0
- dbus_fast/introspection.py +686 -0
- dbus_fast/message.cpython-310-darwin.so +0 -0
- dbus_fast/message.pxd +76 -0
- dbus_fast/message.py +389 -0
- dbus_fast/message_bus.cpython-310-darwin.so +0 -0
- dbus_fast/message_bus.pxd +75 -0
- dbus_fast/message_bus.py +1332 -0
- dbus_fast/proxy_object.py +357 -0
- dbus_fast/py.typed +0 -0
- dbus_fast/send_reply.py +61 -0
- dbus_fast/service.cpython-310-darwin.so +0 -0
- dbus_fast/service.pxd +50 -0
- dbus_fast/service.py +727 -0
- dbus_fast/signature.cpython-310-darwin.so +0 -0
- dbus_fast/signature.pxd +31 -0
- dbus_fast/signature.py +484 -0
- dbus_fast/unpack.cpython-310-darwin.so +0 -0
- dbus_fast/unpack.pxd +13 -0
- dbus_fast/unpack.py +28 -0
- dbus_fast/validators.py +199 -0
- dbus_fast-3.1.2.dist-info/METADATA +262 -0
- dbus_fast-3.1.2.dist-info/RECORD +51 -0
- dbus_fast-3.1.2.dist-info/WHEEL +6 -0
- dbus_fast-3.1.2.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import array
|
|
4
|
+
import asyncio
|
|
5
|
+
import contextlib
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
import socket
|
|
9
|
+
from collections import deque
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from copy import copy
|
|
12
|
+
from functools import partial
|
|
13
|
+
from typing import Any
|
|
14
|
+
from warnings import warn
|
|
15
|
+
|
|
16
|
+
from .. import introspection as intr
|
|
17
|
+
from ..auth import Authenticator, AuthExternal
|
|
18
|
+
from ..constants import (
|
|
19
|
+
BusType,
|
|
20
|
+
MessageFlag,
|
|
21
|
+
MessageType,
|
|
22
|
+
NameFlag,
|
|
23
|
+
ReleaseNameReply,
|
|
24
|
+
RequestNameReply,
|
|
25
|
+
)
|
|
26
|
+
from ..errors import AuthError
|
|
27
|
+
from ..message import Message
|
|
28
|
+
from ..message_bus import BaseMessageBus, _block_unexpected_reply
|
|
29
|
+
from ..service import ServiceInterface, _Method
|
|
30
|
+
from .message_reader import build_message_reader
|
|
31
|
+
from .proxy_object import ProxyObject
|
|
32
|
+
|
|
33
|
+
NO_REPLY_EXPECTED_VALUE = MessageFlag.NO_REPLY_EXPECTED.value
|
|
34
|
+
|
|
35
|
+
_LOGGER = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _generate_hello_serialized(next_serial: int) -> bytes:
|
|
39
|
+
return bytes(
|
|
40
|
+
Message(
|
|
41
|
+
destination="org.freedesktop.DBus",
|
|
42
|
+
path="/org/freedesktop/DBus",
|
|
43
|
+
interface="org.freedesktop.DBus",
|
|
44
|
+
member="Hello",
|
|
45
|
+
serial=next_serial,
|
|
46
|
+
)._marshall(False)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
HELLO_1_SERIALIZED = _generate_hello_serialized(1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _future_set_exception(fut: asyncio.Future, exc: Exception) -> None:
|
|
54
|
+
if fut is not None and not fut.done():
|
|
55
|
+
fut.set_exception(exc)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _future_set_result(fut: asyncio.Future, result: Any) -> None:
|
|
59
|
+
if fut is not None and not fut.done():
|
|
60
|
+
fut.set_result(result)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _MessageWriter:
|
|
64
|
+
"""A class to handle writing messages to the message bus."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, bus: MessageBus) -> None:
|
|
67
|
+
"""A class to handle writing messages to the message bus."""
|
|
68
|
+
self.messages: deque[
|
|
69
|
+
tuple[bytearray, list[int] | None, asyncio.Future | None]
|
|
70
|
+
] = deque()
|
|
71
|
+
self.negotiate_unix_fd = bus._negotiate_unix_fd
|
|
72
|
+
self.bus = bus
|
|
73
|
+
self.sock = bus._sock
|
|
74
|
+
self.loop = bus._loop
|
|
75
|
+
self.buf: memoryview | None = None
|
|
76
|
+
self.fd = bus._fd
|
|
77
|
+
self.offset = 0
|
|
78
|
+
self.unix_fds: list[int] | None = None
|
|
79
|
+
self.fut: asyncio.Future | None = None
|
|
80
|
+
|
|
81
|
+
def write_callback(self, remove_writer: bool = True) -> None:
|
|
82
|
+
"""The callback to write messages to the message bus."""
|
|
83
|
+
sock = self.sock
|
|
84
|
+
try:
|
|
85
|
+
while True:
|
|
86
|
+
if self.buf is None:
|
|
87
|
+
# If there is no buffer, get the next message
|
|
88
|
+
if not self.messages:
|
|
89
|
+
# nothing more to write
|
|
90
|
+
if remove_writer:
|
|
91
|
+
self.loop.remove_writer(self.fd)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Get the next message
|
|
95
|
+
buf, unix_fds, fut = self.messages.popleft()
|
|
96
|
+
self.unix_fds = unix_fds
|
|
97
|
+
self.buf = memoryview(buf)
|
|
98
|
+
self.offset = 0
|
|
99
|
+
self.fut = fut
|
|
100
|
+
|
|
101
|
+
if self.unix_fds and self.negotiate_unix_fd:
|
|
102
|
+
ancdata = [
|
|
103
|
+
(
|
|
104
|
+
socket.SOL_SOCKET,
|
|
105
|
+
socket.SCM_RIGHTS,
|
|
106
|
+
array.array("i", self.unix_fds),
|
|
107
|
+
)
|
|
108
|
+
]
|
|
109
|
+
self.offset += sock.sendmsg([self.buf[self.offset :]], ancdata)
|
|
110
|
+
self.unix_fds = None
|
|
111
|
+
else:
|
|
112
|
+
self.offset += sock.send(self.buf[self.offset :])
|
|
113
|
+
|
|
114
|
+
if self.offset < len(self.buf):
|
|
115
|
+
# wait for writable
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# finished writing
|
|
119
|
+
self.buf = None
|
|
120
|
+
_future_set_result(self.fut, None)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
if self.bus._user_disconnect:
|
|
123
|
+
_future_set_result(self.fut, None)
|
|
124
|
+
else:
|
|
125
|
+
_future_set_exception(self.fut, e)
|
|
126
|
+
self.bus._finalize(e)
|
|
127
|
+
|
|
128
|
+
def buffer_message(
|
|
129
|
+
self, msg: Message, future: asyncio.Future | None = None
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Buffer a message to be sent later."""
|
|
132
|
+
unix_fds = msg.unix_fds
|
|
133
|
+
self.messages.append(
|
|
134
|
+
(
|
|
135
|
+
msg._marshall(self.negotiate_unix_fd),
|
|
136
|
+
copy(unix_fds) if unix_fds else None,
|
|
137
|
+
future,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _write_without_remove_writer(self) -> None:
|
|
142
|
+
"""Call the write callback without removing the writer."""
|
|
143
|
+
self.write_callback(remove_writer=False)
|
|
144
|
+
|
|
145
|
+
def schedule_write(
|
|
146
|
+
self, msg: Message | None = None, future: asyncio.Future | None = None
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Schedule a message to be written."""
|
|
149
|
+
queue_is_empty = not self.messages
|
|
150
|
+
if msg is not None:
|
|
151
|
+
self.buffer_message(msg, future)
|
|
152
|
+
|
|
153
|
+
if self.bus.unique_name:
|
|
154
|
+
# Optimization: try to send now if the queue
|
|
155
|
+
# is empty. With bleak this usually means we
|
|
156
|
+
# can send right away 99% of the time which
|
|
157
|
+
# is a huge improvement in latency.
|
|
158
|
+
if queue_is_empty:
|
|
159
|
+
self._write_without_remove_writer()
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
self.buf is not None
|
|
163
|
+
or self.messages
|
|
164
|
+
or not self.fut
|
|
165
|
+
or not self.fut.done()
|
|
166
|
+
):
|
|
167
|
+
self.loop.add_writer(self.fd, self.write_callback)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class MessageBus(BaseMessageBus):
|
|
171
|
+
"""The message bus implementation for use with asyncio.
|
|
172
|
+
|
|
173
|
+
The message bus class is the entry point into all the features of the
|
|
174
|
+
library. It sets up a connection to the DBus daemon and exposes an
|
|
175
|
+
interface to send and receive messages and expose services.
|
|
176
|
+
|
|
177
|
+
You must call :func:`connect() <dbus_fast.aio.MessageBus.connect>` before
|
|
178
|
+
using this message bus.
|
|
179
|
+
|
|
180
|
+
:param bus_type: The type of bus to connect to. Affects the search path for
|
|
181
|
+
the bus address.
|
|
182
|
+
:type bus_type: :class:`BusType <dbus_fast.BusType>`
|
|
183
|
+
:param bus_address: A specific bus address to connect to. Should not be
|
|
184
|
+
used under normal circumstances.
|
|
185
|
+
:param auth: The authenticator to use, defaults to an instance of
|
|
186
|
+
:class:`AuthExternal <dbus_fast.auth.AuthExternal>`.
|
|
187
|
+
:type auth: :class:`Authenticator <dbus_fast.auth.Authenticator>`
|
|
188
|
+
:param negotiate_unix_fd: Allow the bus to send and receive Unix file
|
|
189
|
+
descriptors (DBus type 'h'). This must be supported by the transport.
|
|
190
|
+
:type negotiate_unix_fd: bool
|
|
191
|
+
|
|
192
|
+
:ivar unique_name: The unique name of the message bus connection. It will
|
|
193
|
+
be :class:`None` until the message bus connects.
|
|
194
|
+
:vartype unique_name: str
|
|
195
|
+
:ivar connected: True if this message bus is expected to be able to send
|
|
196
|
+
and receive messages.
|
|
197
|
+
:vartype connected: bool
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
__slots__ = ("_auth", "_disconnect_future", "_loop", "_pending_futures", "_writer")
|
|
201
|
+
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
bus_address: str | None = None,
|
|
205
|
+
bus_type: BusType = BusType.SESSION,
|
|
206
|
+
auth: Authenticator | None = None,
|
|
207
|
+
negotiate_unix_fd: bool = False,
|
|
208
|
+
) -> None:
|
|
209
|
+
super().__init__(bus_address, bus_type, ProxyObject, negotiate_unix_fd)
|
|
210
|
+
self._loop = asyncio.get_running_loop()
|
|
211
|
+
|
|
212
|
+
self._writer = _MessageWriter(self)
|
|
213
|
+
|
|
214
|
+
if auth is None:
|
|
215
|
+
self._auth = AuthExternal()
|
|
216
|
+
else:
|
|
217
|
+
self._auth = auth
|
|
218
|
+
|
|
219
|
+
self._disconnect_future = self._loop.create_future()
|
|
220
|
+
self._pending_futures: set[asyncio.Future] = set()
|
|
221
|
+
|
|
222
|
+
async def connect(self) -> MessageBus:
|
|
223
|
+
"""Connect this message bus to the DBus daemon.
|
|
224
|
+
|
|
225
|
+
This method must be called before the message bus can be used.
|
|
226
|
+
|
|
227
|
+
:returns: This message bus for convenience.
|
|
228
|
+
:rtype: :class:`MessageBus <dbus_fast.aio.MessageBus>`
|
|
229
|
+
|
|
230
|
+
:raises:
|
|
231
|
+
- :class:`AuthError <dbus_fast.AuthError>` - If authorization to \
|
|
232
|
+
the DBus daemon failed.
|
|
233
|
+
- :class:`Exception` - If there was a connection error.
|
|
234
|
+
"""
|
|
235
|
+
await self._authenticate()
|
|
236
|
+
|
|
237
|
+
future = self._loop.create_future()
|
|
238
|
+
|
|
239
|
+
self._loop.add_reader(
|
|
240
|
+
self._fd,
|
|
241
|
+
build_message_reader(
|
|
242
|
+
self._sock,
|
|
243
|
+
self._process_message,
|
|
244
|
+
self._finalize,
|
|
245
|
+
self._negotiate_unix_fd,
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def on_hello(reply, err):
|
|
250
|
+
try:
|
|
251
|
+
if err:
|
|
252
|
+
raise err
|
|
253
|
+
self.unique_name = reply.body[0]
|
|
254
|
+
self._writer.schedule_write()
|
|
255
|
+
_future_set_result(future, self)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
_future_set_exception(future, e)
|
|
258
|
+
self.disconnect()
|
|
259
|
+
self._finalize(err)
|
|
260
|
+
|
|
261
|
+
next_serial = self.next_serial()
|
|
262
|
+
self._method_return_handlers[next_serial] = on_hello
|
|
263
|
+
if next_serial == 1:
|
|
264
|
+
serialized = HELLO_1_SERIALIZED
|
|
265
|
+
else:
|
|
266
|
+
serialized = _generate_hello_serialized(next_serial)
|
|
267
|
+
self._stream.write(serialized)
|
|
268
|
+
self._stream.flush()
|
|
269
|
+
return await future
|
|
270
|
+
|
|
271
|
+
async def introspect(
|
|
272
|
+
self,
|
|
273
|
+
bus_name: str,
|
|
274
|
+
path: str,
|
|
275
|
+
timeout: float = 30.0,
|
|
276
|
+
validate_property_names: bool = True,
|
|
277
|
+
) -> intr.Node:
|
|
278
|
+
"""Get introspection data for the node at the given path from the given
|
|
279
|
+
bus name.
|
|
280
|
+
|
|
281
|
+
Calls the standard ``org.freedesktop.DBus.Introspectable.Introspect``
|
|
282
|
+
on the bus for the path.
|
|
283
|
+
|
|
284
|
+
:param bus_name: The name to introspect.
|
|
285
|
+
:type bus_name: str
|
|
286
|
+
:param path: The path to introspect.
|
|
287
|
+
:type path: str
|
|
288
|
+
:param timeout: The timeout to introspect.
|
|
289
|
+
:type timeout: float
|
|
290
|
+
:param validate_property_names: Whether to validate property names or not.
|
|
291
|
+
:type validate_property_names: bool
|
|
292
|
+
|
|
293
|
+
:returns: The introspection data for the name at the path.
|
|
294
|
+
:rtype: :class:`Node <dbus_fast.introspection.Node>`
|
|
295
|
+
|
|
296
|
+
:raises:
|
|
297
|
+
- :class:`InvalidObjectPathError <dbus_fast.InvalidObjectPathError>` \
|
|
298
|
+
- If the given object path is not valid.
|
|
299
|
+
- :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
|
|
300
|
+
the given bus name is not valid.
|
|
301
|
+
- :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
|
|
302
|
+
an error for the method call or returned an invalid result.
|
|
303
|
+
- :class:`Exception` - If a connection error occurred.
|
|
304
|
+
- :class:`asyncio.TimeoutError` - Waited for future but time run out.
|
|
305
|
+
"""
|
|
306
|
+
future = self._loop.create_future()
|
|
307
|
+
|
|
308
|
+
super().introspect(
|
|
309
|
+
bus_name,
|
|
310
|
+
path,
|
|
311
|
+
partial(self._reply_handler, future),
|
|
312
|
+
check_callback_type=False,
|
|
313
|
+
validate_property_names=validate_property_names,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
timer_handle = self._loop.call_later(
|
|
317
|
+
timeout, _future_set_exception, future, asyncio.TimeoutError
|
|
318
|
+
)
|
|
319
|
+
try:
|
|
320
|
+
return await future
|
|
321
|
+
finally:
|
|
322
|
+
timer_handle.cancel()
|
|
323
|
+
|
|
324
|
+
async def request_name(
|
|
325
|
+
self, name: str, flags: NameFlag = NameFlag.NONE
|
|
326
|
+
) -> RequestNameReply:
|
|
327
|
+
"""Request that this message bus owns the given name.
|
|
328
|
+
|
|
329
|
+
:param name: The name to request.
|
|
330
|
+
:type name: str
|
|
331
|
+
:param flags: Name flags that affect the behavior of the name request.
|
|
332
|
+
:type flags: :class:`NameFlag <dbus_fast.NameFlag>`
|
|
333
|
+
|
|
334
|
+
:returns: The reply to the name request.
|
|
335
|
+
:rtype: :class:`RequestNameReply <dbus_fast.RequestNameReply>`
|
|
336
|
+
|
|
337
|
+
:raises:
|
|
338
|
+
- :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
|
|
339
|
+
the given bus name is not valid.
|
|
340
|
+
- :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
|
|
341
|
+
an error for the method call or returned an invalid result.
|
|
342
|
+
- :class:`Exception` - If a connection error occurred.
|
|
343
|
+
"""
|
|
344
|
+
future = self._loop.create_future()
|
|
345
|
+
|
|
346
|
+
super().request_name(
|
|
347
|
+
name, flags, partial(self._reply_handler, future), check_callback_type=False
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return await future
|
|
351
|
+
|
|
352
|
+
async def release_name(self, name: str) -> ReleaseNameReply:
|
|
353
|
+
"""Request that this message bus release the given name.
|
|
354
|
+
|
|
355
|
+
:param name: The name to release.
|
|
356
|
+
:type name: str
|
|
357
|
+
|
|
358
|
+
:returns: The reply to the release request.
|
|
359
|
+
:rtype: :class:`ReleaseNameReply <dbus_fast.ReleaseNameReply>`
|
|
360
|
+
|
|
361
|
+
:raises:
|
|
362
|
+
- :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
|
|
363
|
+
the given bus name is not valid.
|
|
364
|
+
- :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
|
|
365
|
+
an error for the method call or returned an invalid result.
|
|
366
|
+
- :class:`Exception` - If a connection error occurred.
|
|
367
|
+
"""
|
|
368
|
+
future = self._loop.create_future()
|
|
369
|
+
|
|
370
|
+
super().release_name(
|
|
371
|
+
name, partial(self._reply_handler, future), check_callback_type=False
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return await future
|
|
375
|
+
|
|
376
|
+
async def call(self, msg: Message) -> Message:
|
|
377
|
+
"""Send a method call and wait for a reply from the DBus daemon.
|
|
378
|
+
|
|
379
|
+
:param msg: The method call message to send.
|
|
380
|
+
:type msg: :class:`Message <dbus_fast.Message>`
|
|
381
|
+
|
|
382
|
+
:returns: A message in reply to the method call sent.
|
|
383
|
+
:rtype: :class:`Message <dbus_fast.Message>`
|
|
384
|
+
|
|
385
|
+
:raises:
|
|
386
|
+
- :class:`Exception` - If a connection error occurred.
|
|
387
|
+
|
|
388
|
+
.. versionchanged:: 3.1.0
|
|
389
|
+
Using this to call methods that do not expect a reply or messages
|
|
390
|
+
that are not a method call is deprecated. Use :func:`send()
|
|
391
|
+
<dbus_fast.aio.MessageBus.send>` instead for those cases.
|
|
392
|
+
"""
|
|
393
|
+
if (
|
|
394
|
+
msg.flags.value & NO_REPLY_EXPECTED_VALUE
|
|
395
|
+
or msg.message_type is not MessageType.METHOD_CALL
|
|
396
|
+
):
|
|
397
|
+
warn(
|
|
398
|
+
"Using this to call methods that do not expect a reply or messages that are not a method call is deprecated. Use send() instead.",
|
|
399
|
+
DeprecationWarning,
|
|
400
|
+
stacklevel=2,
|
|
401
|
+
)
|
|
402
|
+
await self.send(msg)
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
future = self._loop.create_future()
|
|
406
|
+
|
|
407
|
+
self._call(msg, partial(self._reply_handler, future))
|
|
408
|
+
|
|
409
|
+
await future
|
|
410
|
+
|
|
411
|
+
return future.result()
|
|
412
|
+
|
|
413
|
+
def send(self, msg: Message) -> asyncio.Future:
|
|
414
|
+
"""Asynchronously send a message on the message bus.
|
|
415
|
+
|
|
416
|
+
.. note:: This method may change to a couroutine function in the 1.0
|
|
417
|
+
release of the library.
|
|
418
|
+
|
|
419
|
+
:param msg: The message to send.
|
|
420
|
+
:type msg: :class:`Message <dbus_fast.Message>`
|
|
421
|
+
|
|
422
|
+
:returns: A future that resolves when the message is sent or a
|
|
423
|
+
connection error occurs.
|
|
424
|
+
:rtype: :class:`Future <asyncio.Future>`
|
|
425
|
+
"""
|
|
426
|
+
if not msg.serial:
|
|
427
|
+
msg.serial = self.next_serial()
|
|
428
|
+
|
|
429
|
+
future = self._loop.create_future()
|
|
430
|
+
self._writer.schedule_write(msg, future)
|
|
431
|
+
return future
|
|
432
|
+
|
|
433
|
+
def get_proxy_object(
|
|
434
|
+
self, bus_name: str, path: str, introspection: intr.Node
|
|
435
|
+
) -> ProxyObject:
|
|
436
|
+
return super().get_proxy_object(bus_name, path, introspection)
|
|
437
|
+
|
|
438
|
+
async def wait_for_disconnect(self):
|
|
439
|
+
"""Wait for the message bus to disconnect.
|
|
440
|
+
|
|
441
|
+
:returns: :class:`None` when the message bus has disconnected.
|
|
442
|
+
:rtype: :class:`None`
|
|
443
|
+
|
|
444
|
+
:raises:
|
|
445
|
+
- :class:`Exception` - If connection was terminated unexpectedly or \
|
|
446
|
+
an internal error occurred in the library.
|
|
447
|
+
"""
|
|
448
|
+
return await self._disconnect_future
|
|
449
|
+
|
|
450
|
+
def _future_exception_no_reply(self, fut: asyncio.Future) -> None:
|
|
451
|
+
"""Log an exception from a future that was not expected."""
|
|
452
|
+
self._pending_futures.discard(fut)
|
|
453
|
+
try:
|
|
454
|
+
fut.result()
|
|
455
|
+
except asyncio.CancelledError:
|
|
456
|
+
pass
|
|
457
|
+
except Exception as e:
|
|
458
|
+
_LOGGER.exception("unexpected exception in future", exc_info=e)
|
|
459
|
+
|
|
460
|
+
def _make_method_handler(
|
|
461
|
+
self, interface: ServiceInterface, method: _Method
|
|
462
|
+
) -> Callable[[Message, Callable[[Message], None]], None]:
|
|
463
|
+
if not inspect.iscoroutinefunction(method.fn):
|
|
464
|
+
return super()._make_method_handler(interface, method)
|
|
465
|
+
|
|
466
|
+
negotiate_unix_fd = self._negotiate_unix_fd
|
|
467
|
+
msg_body_to_args = ServiceInterface._msg_body_to_args
|
|
468
|
+
fn_result_to_body = ServiceInterface._fn_result_to_body
|
|
469
|
+
|
|
470
|
+
def _coroutine_method_handler(
|
|
471
|
+
msg: Message, send_reply: Callable[[Message], None]
|
|
472
|
+
) -> None:
|
|
473
|
+
"""A coroutine method handler."""
|
|
474
|
+
args = msg_body_to_args(msg) if msg.unix_fds else msg.body
|
|
475
|
+
fut: asyncio.Future = asyncio.ensure_future(method.fn(interface, *args))
|
|
476
|
+
# Hold a strong reference to the future to ensure
|
|
477
|
+
# it is not garbage collected before it is done.
|
|
478
|
+
self._pending_futures.add(fut)
|
|
479
|
+
if (
|
|
480
|
+
send_reply is _block_unexpected_reply
|
|
481
|
+
or msg.flags.value & NO_REPLY_EXPECTED_VALUE
|
|
482
|
+
):
|
|
483
|
+
fut.add_done_callback(self._future_exception_no_reply)
|
|
484
|
+
return
|
|
485
|
+
|
|
486
|
+
# We only create the closure function if we are actually going to reply
|
|
487
|
+
def _done(fut: asyncio.Future) -> None:
|
|
488
|
+
"""The callback for when the method is done."""
|
|
489
|
+
with send_reply:
|
|
490
|
+
result = fut.result()
|
|
491
|
+
body, unix_fds = fn_result_to_body(
|
|
492
|
+
result, method.out_signature_tree, replace_fds=negotiate_unix_fd
|
|
493
|
+
)
|
|
494
|
+
send_reply(
|
|
495
|
+
Message.new_method_return(
|
|
496
|
+
msg, method.out_signature, body, unix_fds
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
fut.add_done_callback(_done)
|
|
501
|
+
# Discard the future only after running the done callback
|
|
502
|
+
fut.add_done_callback(self._pending_futures.discard)
|
|
503
|
+
|
|
504
|
+
return _coroutine_method_handler
|
|
505
|
+
|
|
506
|
+
async def _auth_readline(self) -> str:
|
|
507
|
+
buf = b""
|
|
508
|
+
while buf[-2:] != b"\r\n":
|
|
509
|
+
# The auth protocol is line based, so we can read until we get a
|
|
510
|
+
# newline.
|
|
511
|
+
buf += await self._loop.sock_recv(self._sock, 1024)
|
|
512
|
+
return buf[:-2].decode()
|
|
513
|
+
|
|
514
|
+
async def _authenticate(self) -> None:
|
|
515
|
+
await self._loop.sock_sendall(self._sock, b"\0")
|
|
516
|
+
|
|
517
|
+
first_line = self._auth._authentication_start(
|
|
518
|
+
negotiate_unix_fd=self._negotiate_unix_fd
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
if first_line is not None:
|
|
522
|
+
if type(first_line) is not str:
|
|
523
|
+
raise AuthError("authenticator gave response not type str")
|
|
524
|
+
await self._loop.sock_sendall(
|
|
525
|
+
self._sock, Authenticator._format_line(first_line)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
while True:
|
|
529
|
+
response = self._auth._receive_line(await self._auth_readline())
|
|
530
|
+
if response is not None:
|
|
531
|
+
await self._loop.sock_sendall(
|
|
532
|
+
self._sock, Authenticator._format_line(response)
|
|
533
|
+
)
|
|
534
|
+
self._stream.flush()
|
|
535
|
+
if response == "BEGIN":
|
|
536
|
+
# The first octet received by the server after the \r\n of the BEGIN command
|
|
537
|
+
# from the client must be the first octet of the authenticated/encrypted stream
|
|
538
|
+
# of D-Bus messages.
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
def _finalize(self, err: Exception | None = None) -> None:
|
|
542
|
+
try:
|
|
543
|
+
self._loop.remove_reader(self._fd)
|
|
544
|
+
except Exception:
|
|
545
|
+
_LOGGER.warning("could not remove message reader", exc_info=True)
|
|
546
|
+
try:
|
|
547
|
+
self._loop.remove_writer(self._fd)
|
|
548
|
+
except Exception:
|
|
549
|
+
_LOGGER.warning("could not remove message writer", exc_info=True)
|
|
550
|
+
|
|
551
|
+
had_handlers = bool(self._method_return_handlers or self._user_message_handlers)
|
|
552
|
+
|
|
553
|
+
super()._finalize(err)
|
|
554
|
+
|
|
555
|
+
if self._disconnect_future.done():
|
|
556
|
+
return
|
|
557
|
+
|
|
558
|
+
if err and not self._user_disconnect:
|
|
559
|
+
_future_set_exception(self._disconnect_future, err)
|
|
560
|
+
# If this happens during a reply, the message handlers
|
|
561
|
+
# will have the exception set and wait_for_disconnect will
|
|
562
|
+
# never be called so we need to manually set the exception
|
|
563
|
+
# as retrieved to avoid asyncio warnings when the future
|
|
564
|
+
# is garbage collected.
|
|
565
|
+
if had_handlers:
|
|
566
|
+
with contextlib.suppress(Exception):
|
|
567
|
+
self._disconnect_future.exception()
|
|
568
|
+
else:
|
|
569
|
+
_future_set_result(self._disconnect_future, None)
|
|
570
|
+
|
|
571
|
+
def _reply_handler(
|
|
572
|
+
self, future: asyncio.Future, reply: Any | None, err: Exception | None
|
|
573
|
+
) -> None:
|
|
574
|
+
"""The reply handler for method calls."""
|
|
575
|
+
if err:
|
|
576
|
+
_future_set_exception(future, err)
|
|
577
|
+
else:
|
|
578
|
+
_future_set_result(future, reply)
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# cython: freethreading_compatible = True
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import socket
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from functools import partial
|
|
9
|
+
|
|
10
|
+
from .._private.unmarshaller import Unmarshaller
|
|
11
|
+
from ..message import Message
|
|
12
|
+
|
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _message_reader(
|
|
17
|
+
unmarshaller: Unmarshaller,
|
|
18
|
+
process: Callable[[Message], None],
|
|
19
|
+
finalize: Callable[[Exception | None], None],
|
|
20
|
+
negotiate_unix_fd: bool,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Reads messages from the unmarshaller and passes them to the process function."""
|
|
23
|
+
try:
|
|
24
|
+
while True:
|
|
25
|
+
message = unmarshaller._unmarshall()
|
|
26
|
+
if message is None:
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
process(message)
|
|
30
|
+
except Exception:
|
|
31
|
+
_LOGGER.error("Unexpected error processing message: %s", exc_info=True)
|
|
32
|
+
# If we are not negotiating unix fds, we can stop reading as soon as we have
|
|
33
|
+
# the buffer is empty as asyncio will call us again when there is more data.
|
|
34
|
+
if (
|
|
35
|
+
not negotiate_unix_fd
|
|
36
|
+
and not unmarshaller._has_another_message_in_buffer()
|
|
37
|
+
):
|
|
38
|
+
return
|
|
39
|
+
except Exception as e:
|
|
40
|
+
finalize(e)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_message_reader(
|
|
44
|
+
sock: socket.socket | None,
|
|
45
|
+
process: Callable[[Message], None],
|
|
46
|
+
finalize: Callable[[Exception | None], None],
|
|
47
|
+
negotiate_unix_fd: bool,
|
|
48
|
+
) -> Callable[[], None]:
|
|
49
|
+
"""Build a callable that reads messages from the unmarshaller and passes them to the process function."""
|
|
50
|
+
unmarshaller = Unmarshaller(None, sock, negotiate_unix_fd)
|
|
51
|
+
return partial(_message_reader, unmarshaller, process, finalize, negotiate_unix_fd)
|