core, bridge: new devicesInfosGet method to get infos on known devices of an entity

This commit is contained in:
Goffi 2020-03-06 18:19:03 +01:00
commit 5a467b15d5
7 changed files with 139 additions and 11 deletions

View file

@ -56,3 +56,22 @@ example
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

View file

@ -944,6 +944,19 @@ doc_return=session informations, with at least the following keys:
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

View file

@ -258,6 +258,12 @@ class DbusObject(dbus.service.Object):
def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
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'))

View file

@ -21,6 +21,7 @@ import sys
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
}
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):

View file

@ -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
from collections import namedtuple
import shortuuid
import mimetypes
import time
from uuid import uuid4
from collections import namedtuple
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 BlockCipher
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 @@ class Memory(object):
"""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

View file

@ -217,6 +217,15 @@ class Bridge:
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 @@ class AIOBridge(Bridge):
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()

View file

@ -199,6 +199,14 @@ class Bridge(object):
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 @@ class AIOBridge(Bridge):
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)