changeset 4041:2594e1951cf7

core (bridge): `action_new` now use serialised dict for extra data.
author Goffi <goffi@goffi.org>
date Mon, 15 May 2023 16:20:45 +0200
parents 1f967f85fc23
children 877145b4ba01
files sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/dbus_bridge.py sat/core/sat_main.py sat/tools/xml_tools.py sat_frontends/bridge/dbus_bridge.py sat_frontends/jp/base.py sat_frontends/jp/cmd_file.py sat_frontends/jp/cmd_pipe.py sat_frontends/jp/xmlui_manager.py sat_frontends/quick_frontend/quick_app.py
diffstat 10 files changed, 111 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/sat/bridge/bridge_constructor/bridge_template.ini	Mon May 15 16:20:38 2023 +0200
+++ b/sat/bridge/bridge_constructor/bridge_template.ini	Mon May 15 16:20:45 2023 +0200
@@ -120,10 +120,10 @@
 [action_new]
 type=signal
 category=core
-sig_in=a{ss}sis
+sig_in=ssis
 doc=A frontend action is requested
-doc_param_0=action_data: a dict where key can be:
-    - xmlui: a XMLUI need to be displayed
+doc_param_0=action_data: a serialised dict where key can be:
+    - xmlui: a XMLUI describing the action
     - progress: a progress id
     - meta_*: meta information on the action, used to make automation more easy,
         some are defined below
@@ -761,8 +761,8 @@
 async=
 type=method
 category=core
-sig_in=sa{ss}s
-sig_out=a{ss}
+sig_in=sss
+sig_out=s
 param_2_default="@DEFAULT@"
 doc=Launch a registred action
 doc_param_0=callback_id: id of the registred callback
@@ -775,7 +775,7 @@
 type=method
 category=core
 sig_in=s
-sig_out=a(a{ss}si)
+sig_out=a(ssi)
 param_0_default="@DEFAULT@"
 doc=Get all not yet answered actions
 doc_param_0=%(doc_profile_key)s
--- a/sat/bridge/dbus_bridge.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat/bridge/dbus_bridge.py	Mon May 15 16:20:45 2023 +0200
@@ -88,8 +88,8 @@
 
     core_iface = DBusInterface(
         const_INT_PREFIX + const_CORE_SUFFIX,
-        Method('action_launch', arguments='sa{ss}s', returns='a{ss}'),
-        Method('actions_get', arguments='s', returns='a(a{ss}si)'),
+        Method('action_launch', arguments='sss', returns='s'),
+        Method('actions_get', arguments='s', returns='a(ssi)'),
         Method('config_get', arguments='ss', returns='s'),
         Method('connect', arguments='ssa{ss}', returns='b'),
         Method('contact_add', arguments='ss', returns=''),
@@ -155,7 +155,7 @@
         Method('subscription', arguments='sss', returns=''),
         Method('version_get', arguments='', returns='s'),
         Signal('_debug', 'sa{ss}s'),
-        Signal('action_new', 'a{ss}sis'),
+        Signal('action_new', 'ssis'),
         Signal('connected', 'ss'),
         Signal('contact_deleted', 'ss'),
         Signal('contact_new', 'sa{ss}ass'),
--- a/sat/core/sat_main.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat/core/sat_main.py	Mon May 15 16:20:45 2023 +0200
@@ -36,6 +36,7 @@
 from wokkel.xmppim import RosterItem
 from sat.core import xmpp
 from sat.core import exceptions
+from sat.core.core_types import SatXMPPEntity
 from sat.core.log import getLogger
 
 from sat.core.constants import Const as C
@@ -201,7 +202,7 @@
         self.bridge.register_method("contact_del", self._del_contact)
         self.bridge.register_method("roster_resync", self._roster_resync)
         self.bridge.register_method("is_connected", self.is_connected)
-        self.bridge.register_method("action_launch", self.launch_callback)
+        self.bridge.register_method("action_launch", self._action_launch)
         self.bridge.register_method("actions_get", self.actions_get)
         self.bridge.register_method("progress_get", self._progress_get)
         self.bridge.register_method("progress_get_all", self._progress_get_all)
@@ -885,7 +886,7 @@
 
     def get_local_path(
         self,
-        client: Optional[xmpp.SatXMPPEntity],
+        client: Optional[SatXMPPEntity],
         dir_name: str,
         *extra_path: str,
         component: bool = False,
@@ -1119,7 +1120,7 @@
 
     async def find_by_features(
         self,
-        client: xmpp.SatXMPPEntity,
+        client: SatXMPPEntity,
         namespaces: List[str],
         identities: Optional[List[Tuple[str, str]]]=None,
         bare_jids: bool=False,
@@ -1277,7 +1278,9 @@
             action_timer = reactor.callLater(60 * 30, self._kill_action, keep_id, client)
             client.actions[keep_id] = (action_data, id_, security_limit, action_timer)
 
-        self.bridge.action_new(action_data, id_, security_limit, profile)
+        self.bridge.action_new(
+            data_format.serialise(action_data), id_, security_limit, profile
+        )
 
     def actions_get(self, profile):
         """Return current non answered actions
@@ -1285,7 +1288,10 @@
         @param profile: %(doc_profile)s
         """
         client = self.get_client(profile)
-        return [action_tuple[:-1] for action_tuple in client.actions.values()]
+        return [
+            (data_format.serialise(action_tuple[0]), *action_tuple[1:-1])
+            for action_tuple in client.actions.values()
+        ]
 
     def register_progress_cb(
         self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE
@@ -1401,7 +1407,7 @@
 
             def purge_callback():
                 try:
-                    self.removeCallback(callback_id)
+                    self.remove_callback(callback_id)
                 except KeyError:
                     pass
 
@@ -1409,13 +1415,32 @@
 
         return callback_id
 
-    def removeCallback(self, callback_id):
+    def remove_callback(self, callback_id):
         """ Remove a previously registered callback
         @param callback_id: id returned by [register_callback] """
         log.debug("Removing callback [%s]" % callback_id)
         del self._cb_map[callback_id]
 
-    def launch_callback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE):
+    def _action_launch(
+        self,
+        callback_id: str,
+        data_s: str,
+        profile_key: str
+    ) -> defer.Deferred:
+        d = self.launch_callback(
+            callback_id,
+            data_format.deserialise(data_s),
+            profile_key
+        )
+        d.addCallback(data_format.serialise)
+        return d
+
+    def launch_callback(
+        self,
+        callback_id: str,
+        data: Optional[dict] = None,
+        profile_key: str = C.PROF_KEY_NONE
+    ) -> defer.Deferred:
         """Launch a specific callback
 
         @param callback_id: id of the action (callback) to launch
@@ -1428,7 +1453,9 @@
                 - C.BOOL_TRUE
                 - C.BOOL_FALSE
         """
-        #  FIXME: security limit need to be checked here
+        # FIXME: is it possible to use this method without profile connected? If not,
+        #     client must be used instead of profile_key
+        # FIXME: security limit need to be checked here
         try:
             client = self.get_client(profile_key)
         except exceptions.NotFound:
@@ -1466,7 +1493,7 @@
             del kwargs["with_data"]
 
         if kwargs.pop("one_shot", False):
-            self.removeCallback(callback_id)
+            self.remove_callback(callback_id)
 
         return utils.as_deferred(callback, *args, **kwargs)
 
--- a/sat/tools/xml_tools.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat/tools/xml_tools.py	Mon May 15 16:20:45 2023 +0200
@@ -1755,23 +1755,31 @@
     return xmlui_d
 
 
-def defer_dialog(host, message, title="Please confirm", type_=C.XMLUI_DIALOG_CONFIRM,
-    options=None, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False,
-    profile=C.PROF_KEY_NONE):
+def defer_dialog(
+    host,
+    message: str,
+    title: str = "Please confirm",
+    type_: str = C.XMLUI_DIALOG_CONFIRM,
+    options: Optional[dict] = None,
+    action_extra: Optional[dict] = None,
+    security_limit: int = C.NO_SECURITY_LIMIT,
+    chained: bool = False,
+    profile: str = C.PROF_KEY_NONE
+) -> defer.Deferred:
     """Create a submitable dialog and manage it with a deferred
 
-    @param message(unicode): message to display
-    @param title(unicode): title of the dialog
-    @param type(unicode): dialog type (C.XMLUI_DIALOG_*)
-    @param options(None, dict): if not None, will be used to update (extend) dialog_opt
-                                arguments of XMLUI
-    @param action_extra(None, dict): extra action to merge with xmlui
+    @param message: message to display
+    @param title: title of the dialog
+    @param type: dialog type (C.XMLUI_DIALOG_* or plugin specific string)
+    @param options: if not None, will be used to update (extend) dialog_opt arguments of
+        XMLUI
+    @param action_extra: extra action to merge with xmlui
         mainly used to add meta informations (see action_new doc)
     @param security_limit: %(doc_security_limit)s
-    @param chained(bool): True if the Deferred result must be returned to the frontend
+    @param chained: True if the Deferred result must be returned to the frontend
         useful when backend is in a series of dialogs with an ui
     @param profile: %(doc_profile)s
-    @return (dict): Deferred dict
+    @return: answer dict
     """
     assert profile is not None
     dialog_opt = {"type": type_, "message": message}
--- a/sat_frontends/bridge/dbus_bridge.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/bridge/dbus_bridge.py	Mon May 15 16:20:45 2023 +0200
@@ -165,7 +165,7 @@
             if errback is None:
                 errback = log.error
             error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.action_launch(callback_id, data, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
+        return str(self.db_core_iface.action_launch(callback_id, data, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
 
     def actions_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
         if callback is None:
--- a/sat_frontends/jp/base.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/jp/base.py	Mon May 15 16:20:45 2023 +0200
@@ -1387,9 +1387,16 @@
     def __init__(self, *args, **kwargs):
         super(CommandAnswering, self).__init__(*args, **kwargs)
 
-    async def on_action_new(self, action_data, action_id, security_limit, profile):
+    async def on_action_new(
+        self,
+        action_data_s: str,
+        action_id: str,
+        security_limit: int,
+        profile: str
+    ) -> None:
         if profile != self.profile:
             return
+        action_data = data_format.deserialise(action_data_s)
         try:
             action_type = action_data['meta_type']
         except KeyError:
@@ -1424,5 +1431,5 @@
         """Auto reply to confirmation requests"""
         self.host.bridge.register_signal("action_new", self.on_action_new)
         actions = await self.host.bridge.actions_get(self.profile)
-        for action_data, action_id, security_limit in actions:
-            await self.on_action_new(action_data, action_id, security_limit, self.profile)
+        for action_data_s, action_id, security_limit in actions:
+            await self.on_action_new(action_data_s, action_id, security_limit, self.profile)
--- a/sat_frontends/jp/cmd_file.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/jp/cmd_file.py	Mon May 15 16:20:45 2023 +0200
@@ -387,12 +387,15 @@
             if self._overwrite_refused:
                 self.disp(_("File refused because overwrite is needed"), error=True)
                 await self.host.bridge.action_launch(
-                    xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile
+                    xmlui_id, data_format.serialise({"cancelled": C.BOOL_TRUE}),
+                    profile_key=profile
                 )
                 return self.host.quit_from_signal(2)
             await self.set_progress_id(progress_id)
             xmlui_data = {"path": self.path}
-            await self.host.bridge.action_launch(xmlui_id, xmlui_data, profile_key=profile)
+            await self.host.bridge.action_launch(
+                xmlui_id, data_format.serialise(xmlui_data), profile_key=profile
+            )
 
     async def on_overwrite_action(self, action_data, action_id, security_limit, profile):
         xmlui_id = self.get_xmlui_id(action_data)
@@ -413,7 +416,9 @@
                 self._overwrite_refused = True
 
             xmlui_data = {"answer": C.bool_const(self.args.force)}
-            await self.host.bridge.action_launch(xmlui_id, xmlui_data, profile_key=profile)
+            await self.host.bridge.action_launch(
+                xmlui_id, data_format.serialise(xmlui_data), profile_key=profile
+            )
 
     async def on_not_in_roster_action(self, action_data, action_id, security_limit, profile):
         xmlui_id = self.get_xmlui_id(action_data)
@@ -443,7 +448,9 @@
             confirmed = await self.host.confirm(xmlui.dlg.message)
 
         xmlui_data = {"answer": C.bool_const(confirmed)}
-        await self.host.bridge.action_launch(xmlui_id, xmlui_data, profile_key=profile)
+        await self.host.bridge.action_launch(
+            xmlui_id, data_format.serialise(xmlui_data), profile_key=profile
+        )
         if not confirmed and not self.args.multiple:
             self.disp(_("Session refused for {from_jid}").format(from_jid=from_jid))
             self.host.quit_from_signal(0)
--- a/sat_frontends/jp/cmd_pipe.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/jp/cmd_pipe.py	Mon May 15 16:20:45 2023 +0200
@@ -17,15 +17,17 @@
 # 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/>.
 
-import socket
 import asyncio
 import errno
 from functools import partial
+import socket
+import sys
+
+from sat.core.i18n import _
+from sat.tools.common import data_format
 from sat_frontends.jp import base
-from sat_frontends.jp.constants import Const as C
 from sat_frontends.jp import xmlui_manager
-import sys
-from sat.core.i18n import _
+from sat_frontends.jp.constants import Const as C
 from sat_frontends.tools import jid
 
 __commands__ = ["Pipe"]
@@ -141,7 +143,8 @@
                     break
             xmlui_data = {"answer": C.BOOL_TRUE, "port": str(port)}
             await self.host.bridge.action_launch(
-                xmlui_id, xmlui_data, profile_key=profile)
+                xmlui_id, data_format.serialise(xmlui_data), profile_key=profile
+            )
             async with server:
                 await server.serve_forever()
             self.host.quit_from_signal()
--- a/sat_frontends/jp/xmlui_manager.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/jp/xmlui_manager.py	Mon May 15 16:20:45 2023 +0200
@@ -23,6 +23,7 @@
 from sat_frontends.jp.constants import Const as C
 from sat.tools.common.ansi import ANSI as A
 from sat.core.i18n import _
+from sat.tools.common import data_format
 
 log = getLogger(__name__)
 
@@ -619,10 +620,12 @@
     async def _xmlui_launch_action(self, action_id, data):
         XMLUIPanel._actions += 1
         try:
-            data = await self.host.bridge.action_launch(
-                action_id,
-                data,
-                self.profile,
+            data = data_format.deserialise(
+                await self.host.bridge.action_launch(
+                    action_id,
+                    data_format.serialise(data),
+                    self.profile,
+                )
             )
         except Exception as e:
             self.disp(f"can't launch XMLUI action: {e}", error=True)
--- a/sat_frontends/quick_frontend/quick_app.py	Mon May 15 16:20:38 2023 +0200
+++ b/sat_frontends/quick_frontend/quick_app.py	Mon May 15 16:20:45 2023 +0200
@@ -742,8 +742,10 @@
         self.sync = False
         self.set_presence_status(C.PRESENCE_UNAVAILABLE, "", profile=profile)
 
-    def action_new_handler(self, action_data, id_, security_limit, profile):
-        self.action_manager(action_data, user_action=False, profile=profile)
+    def action_new_handler(self, action_data_s, id_, security_limit, profile):
+        self.action_manager(
+            data_format.deserialise(action_data_s), user_action=False, profile=profile
+        )
 
     def contact_new_handler(self, jid_s, attributes, groups, profile):
         entity = jid.JID(jid_s)
@@ -1298,18 +1300,6 @@
                 self.register_progress_cbs(progress_id, progress_cb, progress_eb)
             self.progress_id_handler(progress_id, profile)
 
-        # we ignore metadata
-        action_data = {
-            k: v for k, v in action_data.items() if not k.startswith("meta_")
-        }
-
-        if action_data:
-            raise exceptions.DataError(
-                "Not all keys in action_data are managed ({keys})".format(
-                    keys=", ".join(list(action_data.keys()))
-                )
-            )
-
     def _action_cb(self, data, callback, callback_id, profile):
         if callback is None:
             self.action_manager(data, profile=profile)
@@ -1334,9 +1324,12 @@
         """
         if data is None:
             data = dict()
-        action_cb = lambda data: self._action_cb(data, callback, callback_id, profile)
+        action_cb = lambda data: self._action_cb(
+            data_format.deserialise(data), callback, callback_id, profile
+        )
         self.bridge.action_launch(
-            callback_id, data, profile, callback=action_cb, errback=self.dialog_failure
+            callback_id, data_format.serialise(data), profile, callback=action_cb,
+            errback=self.dialog_failure
         )
 
     def launch_menu(