# HG changeset patch # User Goffi # Date 1583515143 -3600 # Node ID ae09989e9febf644a7b7cdf8c86a344aba946ee8 # Parent 2c0628f3927eb84f256b503505dd61c77c370237 core, bridge: new `devicesInfosGet` method to get infos on known devices of an entity diff -r 2c0628f3927e -r ae09989e9feb doc/jp/info.rst --- a/doc/jp/info.rst Fri Mar 06 18:19:03 2020 +0100 +++ b/doc/jp/info.rst Fri Mar 06 18:19:03 2020 +0100 @@ -56,3 +56,22 @@ Get session informations:: $ jp info session + +devices +------- + +List known devices for an entity. You'll get resource name, and data such as presence +data, and identities (i.e. name and type of the client used). + +If entity's bare jid is not specified, a list of your own devices is returned. + +example +------- + +List known devices of Louise:: + + $ jp info devices louise@example.org + +Check if we have other devices connected:: + + $ jp info devices diff -r 2c0628f3927e -r ae09989e9feb sat/bridge/bridge_constructor/bridge_template.ini --- a/sat/bridge/bridge_constructor/bridge_template.ini Fri Mar 06 18:19:03 2020 +0100 +++ b/sat/bridge/bridge_constructor/bridge_template.ini Fri Mar 06 18:19:03 2020 +0100 @@ -944,6 +944,19 @@ jid: current full jid started: date of creation of the session (Epoch time) +[devicesInfosGet] +async= +type=method +category=core +sig_in=ss +sig_out=s +doc=Get various informations on an entity devices +doc_param_0=bare_jid: get data on known devices from this entity + empty string to get devices of the profile +doc_param_1=%(doc_profile_key)s +doc_return=list of known devices, where each item is a dict with a least following keys: + resource: device resource + [namespacesGet] type=method category=core diff -r 2c0628f3927e -r ae09989e9feb sat/bridge/dbus_bridge.py --- a/sat/bridge/dbus_bridge.py Fri Mar 06 18:19:03 2020 +0100 +++ b/sat/bridge/dbus_bridge.py Fri Mar 06 18:19:03 2020 +0100 @@ -259,6 +259,12 @@ return self._callback("delContact", str(entity_jid), str(profile_key), callback=callback, errback=errback) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='ss', out_signature='s', + async_callbacks=('callback', 'errback')) + def devicesInfosGet(self, bare_jid, profile_key, callback=None, errback=None): + return self._callback("devicesInfosGet", str(bare_jid), str(profile_key), callback=callback, errback=errback) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='asa(ss)bbbbbs', out_signature='(a{sa(sss)}a{sa(sss)}a{sa(sss)})', async_callbacks=('callback', 'errback')) def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", callback=None, errback=None): diff -r 2c0628f3927e -r ae09989e9feb sat/core/sat_main.py --- a/sat/core/sat_main.py Fri Mar 06 18:19:03 2020 +0100 +++ b/sat/core/sat_main.py Fri Mar 06 18:19:03 2020 +0100 @@ -21,6 +21,7 @@ import os.path import uuid import hashlib +import copy from pathlib import Path import sat from sat.core.i18n import _, D_, languageSwitch @@ -172,6 +173,7 @@ self.bridge.register_method("saveParamsTemplate", self.memory.save_xml) self.bridge.register_method("loadParamsTemplate", self.memory.load_xml) self.bridge.register_method("sessionInfosGet", self.getSessionInfos) + self.bridge.register_method("devicesInfosGet", self._getDevicesInfos) self.bridge.register_method("namespacesGet", self.getNamespaces) self.bridge.register_method("imageCheck", self._imageCheck) self.bridge.register_method("imageResize", self._imageResize) @@ -676,6 +678,65 @@ } return defer.succeed(data) + def _getDevicesInfos(self, bare_jid, profile_key): + client = self.getClient(profile_key) + if not bare_jid: + bare_jid = None + d = defer.ensureDeferred(self.getDevicesInfos(client, bare_jid)) + d.addCallback(lambda data: data_format.serialise(data)) + return d + + async def getDevicesInfos(self, client, bare_jid=None): + """compile data on an entity devices + + @param bare_jid(jid.JID, None): bare jid of entity to check + None to use client own jid + @return (list[dict]): list of data, one item per resource. + Following keys can be set: + - resource(str): resource name + """ + own_jid = client.jid.userhostJID() + if bare_jid is None: + bare_jid = own_jid + else: + bare_jid = jid.JID(bare_jid) + resources = self.memory.getAllResources(client, bare_jid) + if bare_jid == own_jid: + # our own jid is not stored in memory's cache + resources.add(client.jid.resource) + ret_data = [] + for resource in resources: + res_jid = copy.copy(bare_jid) + res_jid.resource = resource + cache_data = self.memory.getEntityData(res_jid, profile_key=client.profile) + res_data = { + "resource": resource, + } + try: + presence = cache_data['presence'] + except KeyError: + pass + else: + res_data['presence'] = { + "show": presence.show, + "priority": presence.priority, + "statuses": presence.statuses, + } + + disco = await self.getDiscoInfos(client, res_jid) + + for (category, type_), name in disco.identities.items(): + identities = res_data.setdefault('identities', []) + identities.append({ + "name": name, + "category": category, + "type": type_, + }) + + ret_data.append(res_data) + + return ret_data + # images def _imageCheck(self, path): diff -r 2c0628f3927e -r ae09989e9feb sat/memory/memory.py --- a/sat/memory/memory.py Fri Mar 06 18:19:03 2020 +0100 +++ b/sat/memory/memory.py Fri Mar 06 18:19:03 2020 +0100 @@ -17,19 +17,18 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from sat.core.i18n import _ - -from sat.core.log import getLogger - -log = getLogger(__name__) - import os.path import copy +import shortuuid +import mimetypes +import time +from uuid import uuid4 from collections import namedtuple -from uuid import uuid4 from twisted.python import failure from twisted.internet import defer, reactor, error from twisted.words.protocols.jabber import jid +from sat.core.i18n import _ +from sat.core.log import getLogger from sat.core import exceptions from sat.core.constants import Const as C from sat.memory.sqlite import SqliteStorage @@ -40,9 +39,9 @@ from sat.memory.crypto import PasswordHasher from sat.tools import config as tools_config from sat.tools.common import data_format -import shortuuid -import mimetypes -import time + + +log = getLogger(__name__) PresenceTuple = namedtuple("PresenceTuple", ("show", "priority", "statuses")) @@ -690,7 +689,7 @@ """Return all resource from jid for which we have had data in this session @param entity_jid: bare jid of the entity - return (list[unicode]): list of resources + return (set[unicode]): set of resources @raise exceptions.UnknownEntityError: if entity is not in cache @raise ValueError: entity_jid has a resource diff -r 2c0628f3927e -r ae09989e9feb sat_frontends/bridge/dbus_bridge.py --- a/sat_frontends/bridge/dbus_bridge.py Fri Mar 06 18:19:03 2020 +0100 +++ b/sat_frontends/bridge/dbus_bridge.py Fri Mar 06 18:19:03 2020 +0100 @@ -217,6 +217,15 @@ error_handler = lambda err:errback(dbus_to_bridge_exception(err)) return self.db_core_iface.delContact(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler) + def devicesInfosGet(self, bare_jid, profile_key, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + return str(self.db_core_iface.devicesInfosGet(bare_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)) + def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", callback=None, errback=None): if callback is None: error_handler = None @@ -974,6 +983,14 @@ self.db_core_iface.delContact(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler) return fut + def devicesInfosGet(self, bare_jid, profile_key): + loop = asyncio.get_running_loop() + fut = loop.create_future() + reply_handler = lambda ret=None: loop.call_soon_threadsafe(fut.set_result, ret) + error_handler = lambda err: loop.call_soon_threadsafe(fut.set_exception, dbus_to_bridge_exception(err)) + self.db_core_iface.devicesInfosGet(bare_jid, profile_key, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler) + return fut + def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@"): loop = asyncio.get_running_loop() fut = loop.create_future() diff -r 2c0628f3927e -r ae09989e9feb sat_frontends/bridge/pb.py --- a/sat_frontends/bridge/pb.py Fri Mar 06 18:19:03 2020 +0100 +++ b/sat_frontends/bridge/pb.py Fri Mar 06 18:19:03 2020 +0100 @@ -199,6 +199,14 @@ errback = self._generic_errback d.addErrback(errback) + def devicesInfosGet(self, bare_jid, profile_key, callback=None, errback=None): + d = self.root.callRemote("devicesInfosGet", bare_jid, profile_key) + if callback is not None: + d.addCallback(callback) + if errback is None: + errback = self._generic_errback + d.addErrback(errback) + def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", callback=None, errback=None): d = self.root.callRemote("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) if callback is not None: @@ -711,6 +719,11 @@ d.addErrback(self._errback) return d.asFuture(asyncio.get_event_loop()) + def devicesInfosGet(self, bare_jid, profile_key): + d = self.root.callRemote("devicesInfosGet", bare_jid, profile_key) + d.addErrback(self._errback) + return d.asFuture(asyncio.get_event_loop()) + def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@"): d = self.root.callRemote("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) d.addErrback(self._errback)