#
# Client support for DBus proxies
#
# Copyright (C) 2019 Red Hat, Inc. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
from abc import ABCMeta, abstractmethod
from threading import Lock
from dasbus.client.handler import ClientObjectHandler
from dasbus.client.property import PropertyProxy
from dasbus.specification import DBusSpecificationError
__all__ = [
"AbstractObjectProxy",
"ObjectProxy",
"InterfaceProxy",
"get_object_path",
"disconnect_proxy"
]
def get_object_handler(proxy):
"""Get an object handler of the DBus proxy.
:param proxy: a DBus proxy
:return: a DBus proxy handler
"""
if not isinstance(proxy, AbstractObjectProxy):
raise TypeError("Invalid type '{}'.".format(type(proxy).__name__))
return getattr(proxy, "_handler")
[docs]def get_object_path(proxy):
"""Get an object path of the remote DBus object.
:param proxy: a DBus proxy
:return: a DBus path
"""
handler = get_object_handler(proxy)
return handler.object_path
[docs]def disconnect_proxy(proxy):
"""Disconnect the DBus proxy from the remote object.
:param proxy: a DBus proxy
"""
handler = get_object_handler(proxy)
handler.disconnect_members()
[docs]class AbstractObjectProxy(metaclass=ABCMeta):
"""Abstract proxy of a remote DBus object."""
__slots__ = [
"_handler",
"_members",
"_lock",
"__weakref__"
]
# Set of local instance attributes.
_locals = {*__slots__}
def __init__(self, message_bus, service_name, object_path,
handler_factory=ClientObjectHandler, **handler_arguments):
"""Create a new proxy.
:param message_bus: a message bus
:param service_name: a DBus name of the service
:param object_path: a DBus path the object
:param handler_factory: a factory of a DBus client object handler
:param handler_arguments: additional arguments for the handler factory
"""
self._handler = handler_factory(
message_bus,
service_name,
object_path,
**handler_arguments
)
self._members = {}
self._lock = Lock()
@abstractmethod
def _get_interface(self, member_name):
"""Get the DBus interface of the member.
:param member_name: a member name
:return: an interface name
"""
pass
def _get_member(self, *key):
"""Find a member of the DBus object.
If the member doesn't exist, we will acquire
a lock and ask a handler to create it.
This method is thread-safe.
:param key: a member key
:return: a member
:raise: AttributeError if invalid
"""
try:
return self._members[key]
except KeyError:
pass
return self._create_member(*key)
def _create_member(self, *key):
"""Create a member of the DBus object.
If the member doesn't exist, ask a handler
to create it.
This method is thread-safe.
:param key: a member key
:return: a member
:raise: DBusSpecificationError if invalid
"""
with self._lock:
try:
return self._members[key]
except KeyError:
pass
try:
member = self._handler.create_member(*key)
except DBusSpecificationError as e:
raise AttributeError(str(e)) from None
self._members[key] = member
return member
def __getattr__(self, name):
"""Get the attribute.
Called when an attribute lookup has not found
the attribute in the usual places. Always call
the DBus handler in this case.
"""
member = self._get_member(self._get_interface(name), name)
if isinstance(member, PropertyProxy):
return member.get()
return member
def __setattr__(self, name, value):
"""Set the attribute.
Called when an attribute assignment is attempted.
Call the DBus handler if the name is not a
name of an instance attribute defined in _locals.
"""
if name in self._locals:
return super().__setattr__(name, value)
member = self._get_member(self._get_interface(name), name)
if isinstance(member, PropertyProxy):
return member.set(value)
raise AttributeError(
"Can't set DBus attribute '{}'.".format(name)
)
[docs]class ObjectProxy(AbstractObjectProxy):
"""Proxy of a remote DBus object."""
__slots__ = ["_interface_names"]
# Set of instance attributes.
_locals = {*AbstractObjectProxy._locals, *__slots__}
def __init__(self, *args, **kwargs):
"""Create a new proxy.
:param handler: a DBus client object handler
"""
super().__init__(*args, **kwargs)
self._interface_names = None
def _get_interface(self, member_name):
"""Get the DBus interface of the member.
The members of standard interfaces have a priority.
"""
if self._interface_names is None:
members = reversed(
self._handler.specification.members
)
self._interface_names = {
m.name: m.interface_name
for m in members
}
try:
return self._interface_names[member_name]
except KeyError:
pass
raise AttributeError(
"DBus object has no attribute '{}'.".format(member_name)
)
[docs]class InterfaceProxy(AbstractObjectProxy):
"""Proxy of a remote DBus interface."""
__slots__ = ["_interface_name"]
# Set of instance attributes.
_locals = {*AbstractObjectProxy._locals, *__slots__}
def __init__(self, message_bus, service_name, object_path,
interface_name, *args, **kwargs):
"""Create a new proxy.
:param message_bus: a message bus
:param service_name: a DBus name of the service
:param object_path: a DBus path the object
:param handler: a DBus client object handler
"""
super().__init__(message_bus, service_name, object_path,
*args, **kwargs)
self._interface_name = interface_name
def _get_interface(self, member_name):
"""Get the DBus interface of the member."""
return self._interface_name