changeset 3206:ae09989e9feb

core, bridge: new `devicesInfosGet` method to get infos on known devices of an entity
author Goffi <goffi@goffi.org>
date Fri, 06 Mar 2020 18:19:03 +0100
parents 2c0628f3927e
children a10e12dfbda8
files doc/jp/info.rst sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/dbus_bridge.py sat/core/sat_main.py sat/memory/memory.py sat_frontends/bridge/dbus_bridge.py sat_frontends/bridge/pb.py
diffstat 7 files changed, 139 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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):
--- 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):
--- 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 <http://www.gnu.org/licenses/>.
 
-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
--- 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()
--- 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)