diff sat_frontends/jp/xmlui_manager.py @ 3040:fee60f17ebac

jp: jp asyncio port: /!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\ This patch implements the port of jp to asyncio, so it is now correctly using the bridge asynchronously, and it can be used with bridges like `pb`. This also simplify the code, notably for things which were previously implemented with many callbacks (like pagination with RSM). During the process, some behaviours have been modified/fixed, in jp and backends, check diff for details.
author Goffi <goffi@goffi.org>
date Wed, 25 Sep 2019 08:56:41 +0200
parents ab2696e34d29
children d909473a76cc
line wrap: on
line diff
--- a/sat_frontends/jp/xmlui_manager.py	Wed Sep 25 08:53:38 2019 +0200
+++ b/sat_frontends/jp/xmlui_manager.py	Wed Sep 25 08:56:41 2019 +0200
@@ -17,14 +17,14 @@
 # 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 functools import partial
 from sat.core.log import getLogger
-
-log = getLogger(__name__)
 from sat_frontends.tools import xmlui as xmlui_base
 from sat_frontends.jp.constants import Const as C
 from sat.tools.common.ansi import ANSI as A
 from sat.core.i18n import _
-from functools import partial
+
+log = getLogger(__name__)
 
 # workflow constants
 
@@ -67,7 +67,7 @@
     def name(self):
         return self._xmlui_name
 
-    def show(self):
+    async def show(self):
         """display current widget
 
         must be overriden by subclasses
@@ -178,14 +178,14 @@
     def __init__(self, xmlui_parent):
         Widget.__init__(self, xmlui_parent)
 
-    def show(self):
+    async def show(self):
         self.host.disp('')
 
 
 class TextWidget(xmlui_base.TextWidget, ValueWidget):
     type = "text"
 
-    def show(self):
+    async def show(self):
         self.host.disp(self.value)
 
 
@@ -199,7 +199,7 @@
         except AttributeError:
             return None
 
-    def show(self, no_lf=False, ansi=""):
+    async def show(self, no_lf=False, ansi=""):
         """show label
 
         @param no_lf(bool): same as for [JP.disp]
@@ -214,16 +214,16 @@
 class StringWidget(xmlui_base.StringWidget, InputWidget):
     type = "string"
 
-    def show(self):
+    async def show(self):
         if self.read_only or self.root.read_only:
             self.disp(self.value)
         else:
             elems = []
             self.verboseName(elems)
             if self.value:
-                elems.append(_("(enter: {default})").format(default=self.value))
+                elems.append(_(f"(enter: {self.value})"))
             elems.extend([C.A_HEADER, "> "])
-            value = input(A.color(*elems).encode('utf-8'))
+            value = await self.host.ainput(A.color(*elems))
             if value:
                 #  TODO: empty value should be possible
                 #       an escape key should be used for default instead of enter with empty value
@@ -238,7 +238,7 @@
     type = "textbox"
     # TODO: use a more advanced input method
 
-    def show(self):
+    async def show(self):
         self.verboseName()
         if self.read_only or self.root.read_only:
             self.disp(self.value)
@@ -251,9 +251,9 @@
             while True:
                 try:
                     if not values:
-                        line = input(A.color(C.A_HEADER, "[Ctrl-D to finish]> "))
+                        line = await self.host.ainput(A.color(C.A_HEADER, "[Ctrl-D to finish]> "))
                     else:
-                        line = input()
+                        line = await self.host.ainput()
                     values.append(line)
                 except EOFError:
                    break
@@ -264,20 +264,20 @@
 class XHTMLBoxWidget(xmlui_base.XHTMLBoxWidget, StringWidget):
     type = "xhtmlbox"
 
-    def show(self):
+    async def show(self):
         # FIXME: we use bridge in a blocking way as permitted by python-dbus
         #        this only for now to make it simpler, it must be refactored
         #        to use async when jp will be fully async (expected for 0.8)
-        self.value = self.host.bridge.syntaxConvert(
+        self.value = await self.host.bridge.syntaxConvert(
             self.value, C.SYNTAX_XHTML, "markdown", False, self.host.profile)
-        super(XHTMLBoxWidget, self).show()
+        await super(XHTMLBoxWidget, self).show()
 
 
 class ListWidget(xmlui_base.ListWidget, OptionsWidget):
     type = "list"
     # TODO: handle flags, notably multi
 
-    def show(self):
+    async def show(self):
         if self.root.values_only:
             for value in self.values:
                 self.disp(self.value)
@@ -308,8 +308,8 @@
         choice = None
         limit_max = len(self.options) - 1
         while choice is None or choice < 0 or choice > limit_max:
-            choice = input(
-                A.color(C.A_HEADER, _("your choice (0-{max}): ").format(max=limit_max))
+            choice = await self.host.ainput(
+                A.color(C.A_HEADER, _(f"your choice (0-{limit_max}): "))
             )
             try:
                 choice = int(choice)
@@ -322,7 +322,7 @@
 class BoolWidget(xmlui_base.BoolWidget, InputWidget):
     type = "bool"
 
-    def show(self):
+    async def show(self):
         disp_true = A.color(A.FG_GREEN, "TRUE")
         disp_false = A.color(A.FG_RED, "FALSE")
         if self.read_only or self.root.read_only:
@@ -338,7 +338,7 @@
             while choice not in ("0", "1"):
                 elems = [C.A_HEADER, _("your choice (0,1): ")]
                 self.verboseName(elems)
-                choice = input(A.color(*elems))
+                choice = await self.host.ainput(A.color(*elems))
             self.value = bool(int(choice))
             self.disp("")
 
@@ -365,9 +365,9 @@
     def _xmluiRemove(self, widget):
         self.children.remove(widget)
 
-    def show(self):
+    async def show(self):
         for child in self.children:
-            child.show()
+            await child.show()
 
 
 class VerticalContainer(xmlui_base.VerticalContainer, Container):
@@ -381,7 +381,7 @@
 class LabelContainer(xmlui_base.PairsContainer, Container):
     type = "label"
 
-    def show(self):
+    async def show(self):
         for child in self.children:
             no_lf = False
             # we check linked widget type
@@ -399,9 +399,9 @@
                         no_lf = True
                     elif wid_type == "bool" and for_widget.read_only:
                         no_lf = True
-                child.show(no_lf=no_lf, ansi=A.FG_CYAN)
+                await child.show(no_lf=no_lf, ansi=A.FG_CYAN)
             else:
-                child.show()
+                await child.show()
 
 
 ## Dialogs ##
@@ -415,24 +415,61 @@
     def disp(self, *args, **kwargs):
         self.host.disp(*args, **kwargs)
 
-    def show(self):
+    async def show(self):
         """display current dialog
 
         must be overriden by subclasses
         """
         raise NotImplementedError(self.__class__)
 
+class MessageDialog(xmlui_base.MessageDialog, Dialog):
+
+    def __init__(self, xmlui_parent, title, message, level):
+        Dialog.__init__(self, xmlui_parent)
+        xmlui_base.MessageDialog.__init__(self, xmlui_parent)
+        self.title, self.message, self.level = title, message, level
+
+    async def show(self):
+        # TODO: handle level
+        if self.title:
+            self.disp(A.color(C.A_HEADER, self.title))
+        self.disp(self.message)
+
 
 class NoteDialog(xmlui_base.NoteDialog, Dialog):
-    def show(self):
-        # TODO: handle title and level
-        self.disp(self.message)
 
     def __init__(self, xmlui_parent, title, message, level):
         Dialog.__init__(self, xmlui_parent)
         xmlui_base.NoteDialog.__init__(self, xmlui_parent)
         self.title, self.message, self.level = title, message, level
 
+    async def show(self):
+        # TODO: handle title and level
+        self.disp(self.message)
+
+
+class ConfirmDialog(xmlui_base.ConfirmDialog, Dialog):
+
+    def __init__(self, xmlui_parent, title, message, level, buttons_set):
+        Dialog.__init__(self, xmlui_parent)
+        xmlui_base.ConfirmDialog.__init__(self, xmlui_parent)
+        self.title, self.message, self.level, self.buttons_set = (
+            title, message, level, buttons_set)
+
+    async def show(self):
+        # TODO: handle buttons_set and level
+        self.disp(self.message)
+        if self.title:
+            self.disp(A.color(C.A_HEADER, self.title))
+        input_ = None
+        while input_ not in ('y', 'n'):
+            input_ = await self.host.ainput(f"{self.message} (y/n)? ")
+            input_ = input_.lower()
+        if input_ == 'y':
+            self._xmluiValidated()
+        else:
+            self._xmluiCancelled()
+
 
 ## Factory ##
 
@@ -444,7 +481,7 @@
             return cls
 
 
-class XMLUIPanel(xmlui_base.XMLUIPanel):
+class XMLUIPanel(xmlui_base.AIOXMLUIPanel):
     widget_factory = WidgetFactory()
     _actions = 0  # use to keep track of bridge's launchAction calls
     read_only = False
@@ -470,7 +507,7 @@
     def command(self):
         return self.host.command
 
-    def show(self, workflow=None, read_only=False, values_only=False):
+    async def show(self, workflow=None, read_only=False, values_only=False):
         """display the panel
 
         @param workflow(list, None): command to execute if not None
@@ -489,11 +526,11 @@
         if workflow:
             XMLUIPanel.workflow = workflow
         if XMLUIPanel.workflow:
-            self.runWorkflow()
+            await self.runWorkflow()
         else:
-            self.main_cont.show()
+            await self.main_cont.show()
 
-    def runWorkflow(self):
+    async def runWorkflow(self):
         """loop into workflow commands and execute commands
 
         SUBMIT will interrupt workflow (which will be continue on callback)
@@ -506,7 +543,7 @@
             except IndexError:
                 break
             if cmd == SUBMIT:
-                self.onFormSubmitted()
+                await self.onFormSubmitted()
                 self.submit_id = None  # avoid double submit
                 return
             elif isinstance(cmd, list):
@@ -515,32 +552,32 @@
                 if widget.type == "bool":
                     value = C.bool(value)
                 widget.value = value
-        self.show()
+        await self.show()
 
-    def submitForm(self, callback=None):
+    async def submitForm(self, callback=None):
         XMLUIPanel._submit_cb = callback
-        self.onFormSubmitted()
+        await self.onFormSubmitted()
 
-    def onFormSubmitted(self, ignore=None):
-        #  self.submitted is a Q&D workaround to avoid
+    async def onFormSubmitted(self, ignore=None):
+        # self.submitted is a Q&D workaround to avoid
         # double submit when a workflow is set
         if self.submitted:
             return
         self.submitted = True
-        super(XMLUIPanel, self).onFormSubmitted(ignore)
+        await super(XMLUIPanel, self).onFormSubmitted(ignore)
 
     def _xmluiClose(self):
         pass
 
-    def _launchActionCb(self, data):
+    async def _launchActionCb(self, data):
         XMLUIPanel._actions -= 1
         assert XMLUIPanel._actions >= 0
         if "xmlui" in data:
             xmlui_raw = data["xmlui"]
             xmlui = create(self.host, xmlui_raw)
-            xmlui.show()
+            await xmlui.show()
             if xmlui.submit_id:
-                xmlui.onFormSubmitted()
+                await xmlui.onFormSubmitted()
         # TODO: handle data other than XMLUI
         if not XMLUIPanel._actions:
             if self._submit_cb is None:
@@ -548,19 +585,19 @@
             else:
                 self._submit_cb()
 
-    def _xmluiLaunchAction(self, action_id, data):
+    async def _xmluiLaunchAction(self, action_id, data):
         XMLUIPanel._actions += 1
-        self.host.bridge.launchAction(
-            action_id,
-            data,
-            self.profile,
-            callback=self._launchActionCb,
-            errback=partial(
-                self.command.errback,
-                msg=_("can't launch XMLUI action: {}"),
-                exit_code=C.EXIT_BRIDGE_ERRBACK,
-            ),
-        )
+        try:
+            data = await self.host.bridge.launchAction(
+                action_id,
+                data,
+                self.profile,
+            )
+        except Exception as e:
+            self.disp(f"can't launch XMLUI action: {e}", error=True)
+            self.host.quit(C.EXIT_BRIDGE_ERRBACK)
+        else:
+            await self._launchActionCb(data)
 
 
 class XMLUIDialog(xmlui_base.XMLUIDialog):
@@ -568,8 +605,8 @@
     dialog_factory = WidgetFactory()
     read_only = False
 
-    def show(self, __=None):
-        self.dlg.show()
+    async def show(self, __=None):
+        await self.dlg.show()
 
     def _xmluiClose(self):
         pass