Skip to content

Commit

Permalink
Index BlueZ advertisement_callbacks by adapter
Browse files Browse the repository at this point in the history
- Instead of doing a linear search of all the callbacks
  to find the one for the adapter, store them in a dict
  so the adapter path can be looked up
- Remove unused arg from _run_advertisement_callbacks
  • Loading branch information
bdraco committed Aug 21, 2024
1 parent 74a937c commit fbf9d1c
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
Changed
-------
* In bleak.backends.winrt.util the SetTimer, KillTimer and CoGetApartmentType functions define their own prototype and don't change ctypes' global state anymore
* Improved performance of BlueZ backend when there are many adapters.

`0.22.2`_ (2024-06-01)
======================
Expand Down
62 changes: 25 additions & 37 deletions bleak/backends/bluezdbus/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
import contextlib
import logging
import os
from collections import defaultdict
from typing import (
Any,
Callable,
Coroutine,
Dict,
Iterable,
List,
MutableMapping,
NamedTuple,
Expand Down Expand Up @@ -55,22 +55,6 @@
"""


class CallbackAndState(NamedTuple):
"""
Encapsulates an :data:`AdvertisementCallback` and some state.
"""

callback: AdvertisementCallback
"""
The callback.
"""

adapter_path: str
"""
The D-Bus object path of the adapter associated with the callback.
"""


DevicePropertiesChangedCallback = Callable[[Optional[Any]], None]
"""
A callback that is called when the properties of a device change in BlueZ.
Expand Down Expand Up @@ -194,7 +178,9 @@ def __init__(self):
# map of characteristic d-bus object paths to set of descriptor d-bus object paths
self._descriptor_map: Dict[str, Set[str]] = {}

self._advertisement_callbacks: List[CallbackAndState] = []
self._advertisement_callbacks: defaultdict[str, List[AdvertisementCallback]] = (
defaultdict(list)
)
self._device_removed_callbacks: List[DeviceRemovedCallbackAndState] = []
self._device_watchers: Dict[str, Set[DeviceWatcher]] = {}
self._condition_callbacks: Dict[str, Set[DeviceConditionCallback]] = {}
Expand Down Expand Up @@ -404,8 +390,9 @@ async def active_scan(
# error message.
self._check_adapter(adapter_path)

callback_and_state = CallbackAndState(advertisement_callback, adapter_path)
self._advertisement_callbacks.append(callback_and_state)
self._advertisement_callbacks.setdefault(adapter_path, []).append(
advertisement_callback
)

device_removed_callback_and_state = DeviceRemovedCallbackAndState(
device_removed_callback, adapter_path
Expand Down Expand Up @@ -441,7 +428,9 @@ async def stop() -> None:
# need to remove callbacks first, otherwise we get TxPower
# and RSSI properties removed during stop which causes
# incorrect advertisement data callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(
device_removed_callback_and_state
)
Expand Down Expand Up @@ -478,7 +467,9 @@ async def stop() -> None:
return stop
except BaseException:
# if starting scanning failed, don't leak the callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(device_removed_callback_and_state)
raise

Expand Down Expand Up @@ -512,8 +503,7 @@ async def passive_scan(
# error message.
self._check_adapter(adapter_path)

callback_and_state = CallbackAndState(advertisement_callback, adapter_path)
self._advertisement_callbacks.append(callback_and_state)
self._advertisement_callbacks[adapter_path].append(advertisement_callback)

device_removed_callback_and_state = DeviceRemovedCallbackAndState(
device_removed_callback, adapter_path
Expand Down Expand Up @@ -556,7 +546,9 @@ async def stop() -> None:
# need to remove callbacks first, otherwise we get TxPower
# and RSSI properties removed during stop which causes
# incorrect advertisement data callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(
device_removed_callback_and_state
)
Expand All @@ -580,7 +572,9 @@ async def stop() -> None:

except BaseException:
# if starting scanning failed, don't leak the callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(device_removed_callback_and_state)
raise

Expand Down Expand Up @@ -926,7 +920,7 @@ def _parse_msg(self, message: Message) -> None:
# devices that only advertise once and then go to sleep for a while.
elif interface == defs.DEVICE_INTERFACE:
self._run_advertisement_callbacks(
obj_path, cast(Device1, unpacked_props), unpacked_props.keys()
obj_path, cast(Device1, unpacked_props)
)
elif message.member == "InterfacesRemoved":
obj_path, interfaces = message.body
Expand Down Expand Up @@ -1003,7 +997,7 @@ def _parse_msg(self, message: Message) -> None:
device_path = message_path

self._run_advertisement_callbacks(
device_path, cast(Device1, self_interface), changed.keys()
device_path, cast(Device1, self_interface)
)

# handle device condition watchers
Expand Down Expand Up @@ -1035,22 +1029,16 @@ def _parse_msg(self, message: Message) -> None:
message_path, new_value
)

def _run_advertisement_callbacks(
self, device_path: str, device: Device1, changed: Iterable[str]
) -> None:
def _run_advertisement_callbacks(self, device_path: str, device: Device1) -> None:
"""
Runs any registered advertisement callbacks.
Args:
device_path: The D-Bus object path of the remote device.
device: The current D-Bus properties of the device.
changed: A list of properties that have changed since the last call.
"""
for callback, adapter_path in self._advertisement_callbacks:
# filter messages from other adapters
if adapter_path != device["Adapter"]:
continue

adapter_path = device["Adapter"]
for callback in self._advertisement_callbacks[adapter_path]:
callback(device_path, device.copy())


Expand Down

0 comments on commit fbf9d1c

Please sign in to comment.