Procházet zdrojové kódy

enhance JSON progress information

separate output types, extra information
Marian Beermann před 8 roky
rodič
revize
6288c9f751
2 změnil soubory, kde provedl 76 přidání a 15 odebrání
  1. 27 0
      docs/internals/frontends.rst
  2. 49 15
      src/borg/helpers.py

+ 27 - 0
docs/internals/frontends.rst

@@ -39,6 +39,33 @@ archive_progress
     path
         Current path
 
+progress_message
+    A message-based progress information with no concrete progress information, just a message
+    saying what is currently worked on.
+
+    operation
+        integer ID of the operation
+    finished
+        boolean indicating whether the operation has finished, only the last object for an *operation*
+        can have this property set to *true*.
+    message
+        current progress message (may be empty/absent)
+
+progress_percent
+    Absolute progress display with defined end/total and current value.
+
+    operation
+        integer ID of the operation
+    finished
+        boolean indicating whether the operation has finished, only the last object for an *operation*
+        can have this property set to *true*.
+    message
+        A formatted progress message, this will include the percentage and perhaps other information
+    current
+        Current value (always less-or-equal to *total*)
+    total
+        Total value
+
 file_status
     This is only output by :ref:`borg_create` and :ref:`borg_recreate` if ``--list`` is specified. The usual
     rules for the file listing applies, including the ``--filter`` option.

+ 49 - 15
src/borg/helpers.py

@@ -1377,10 +1377,20 @@ def ellipsis_truncate(msg, space):
 
 class ProgressIndicatorBase:
     LOGGER = 'borg.output.progress'
+    JSON_TYPE = None
+    json = False
+
+    operation_id_counter = 0
+
+    @classmethod
+    def operation_id(cls):
+        cls.operation_id_counter += 1
+        return cls.operation_id_counter
 
     def __init__(self):
         self.handler = None
         self.logger = logging.getLogger(self.LOGGER)
+        self.id = self.operation_id()
 
         # If there are no handlers, set one up explicitly because the
         # terminator and propagation needs to be set.  If there are,
@@ -1394,6 +1404,7 @@ class ProgressIndicatorBase:
             try:
                 formatter = logger.formatter
                 terminator = '\n' if logger.json else '\r'
+                self.json = logger.json
             except AttributeError:
                 terminator = '\r'
             else:
@@ -1404,12 +1415,30 @@ class ProgressIndicatorBase:
             if self.logger.level == logging.NOTSET:
                 self.logger.setLevel(logging.WARN)
             self.logger.propagate = False
+        self.emit = self.logger.getEffectiveLevel() == logging.INFO
 
     def __del__(self):
         if self.handler is not None:
             self.logger.removeHandler(self.handler)
             self.handler.close()
 
+    def output_json(self, *, finished=False, **kwargs):
+        assert self.json
+        if not self.emit:
+            return
+        print(json.dumps(dict(
+            operation=self.id,
+            type=self.JSON_TYPE,
+            finished=finished,
+            **kwargs,
+        )), file=sys.stderr)
+
+    def finish(self):
+        if self.json:
+            self.output_json(finished=True)
+        else:
+            self.output('')
+
 
 def justify_to_terminal_size(message):
     terminal_space = get_terminal_size(fallback=(-1, -1))[0]
@@ -1420,14 +1449,18 @@ def justify_to_terminal_size(message):
 
 
 class ProgressIndicatorMessage(ProgressIndicatorBase):
-    def output(self, msg):
-        self.logger.info(justify_to_terminal_size(msg))
+    JSON_TYPE = 'progress_message'
 
-    def finish(self):
-        self.output('')
+    def output(self, msg):
+        if self.json:
+            self.output_json(message=msg)
+        else:
+            self.logger.info(justify_to_terminal_size(msg))
 
 
 class ProgressIndicatorPercent(ProgressIndicatorBase):
+    JSON_TYPE = 'progress_percent'
+
     def __init__(self, total=0, step=5, start=0, msg="%3.0f%%"):
         """
         Percentage-based progress indicator
@@ -1466,22 +1499,23 @@ class ProgressIndicatorPercent(ProgressIndicatorBase):
         if pct is not None:
             # truncate the last argument, if no space is available
             if info is not None:
-                # no need to truncate if we're not outputing to a terminal
-                terminal_space = get_terminal_size(fallback=(-1, -1))[0]
-                if terminal_space != -1:
-                    space = terminal_space - len(self.msg % tuple([pct] + info[:-1] + ['']))
-                    info[-1] = ellipsis_truncate(info[-1], space)
+                if not self.json:
+                    # no need to truncate if we're not outputing to a terminal
+                    terminal_space = get_terminal_size(fallback=(-1, -1))[0]
+                    if terminal_space != -1:
+                        space = terminal_space - len(self.msg % tuple([pct] + info[:-1] + ['']))
+                        info[-1] = ellipsis_truncate(info[-1], space)
                 return self.output(self.msg % tuple([pct] + info), justify=False)
 
             return self.output(self.msg % pct)
 
     def output(self, message, justify=True):
-        if justify:
-            message = justify_to_terminal_size(message)
-        self.logger.info(message)
-
-    def finish(self):
-        self.output('')
+        if self.json:
+            self.output_json(message=message, current=self.counter, total=self.total)
+        else:
+            if justify:
+                message = justify_to_terminal_size(message)
+            self.logger.info(message)
 
 
 class ProgressIndicatorEndless: