瀏覽代碼

Improve extract progress display and ProgressIndicatorPercent

Abogical 8 年之前
父節點
當前提交
b737866905
共有 4 個文件被更改,包括 35 次插入14 次删除
  1. 4 8
      src/borg/archive.py
  2. 3 1
      src/borg/archiver.py
  3. 27 4
      src/borg/helpers.py
  4. 1 1
      src/borg/testsuite/archiver.py

+ 4 - 8
src/borg/archive.py

@@ -31,7 +31,7 @@ from .helpers import format_time, format_timedelta, format_file_size, file_statu
 from .helpers import safe_encode, safe_decode, make_path_safe, remove_surrogates, swidth_slice
 from .helpers import decode_dict, StableDict
 from .helpers import int_to_bigint, bigint_to_int, bin_to_hex
-from .helpers import ProgressIndicatorPercent, log_multi
+from .helpers import ellipsis_truncate, ProgressIndicatorPercent, log_multi
 from .helpers import PathPrefixPattern, FnmatchPattern
 from .helpers import consume, chunkit
 from .helpers import CompressionDecider1, CompressionDecider2, CompressionSpec
@@ -93,11 +93,7 @@ class Statistics:
                     msg = ''
                     space = columns - swidth(msg)
                 if space >= 8:
-                    if space < swidth('...') + swidth(path):
-                        path = '%s...%s' % (swidth_slice(path, space // 2 - swidth('...')),
-                                            swidth_slice(path, -space // 2))
-                    space -= swidth(path)
-                    msg += path + ' ' * space
+                    msg += ellipsis_truncate(path, space)
             else:
                 msg = ' ' * columns
             print(msg, file=stream or sys.stderr, end="\r", flush=True)
@@ -448,7 +444,7 @@ Number of files: {0.stats.nfiles}'''.format(
             if 'chunks' in item:
                 for _, data in self.pipeline.fetch_many([c.id for c in item.chunks], is_preloaded=True):
                     if pi:
-                        pi.show(increase=len(data))
+                        pi.show(increase=len(data), info=[remove_surrogates(item.path)])
                     if stdout:
                         sys.stdout.buffer.write(data)
                 if stdout:
@@ -501,7 +497,7 @@ Number of files: {0.stats.nfiles}'''.format(
                 ids = [c.id for c in item.chunks]
                 for _, data in self.pipeline.fetch_many(ids, is_preloaded=True):
                     if pi:
-                        pi.show(increase=len(data))
+                        pi.show(increase=len(data), info=[remove_surrogates(item.path)])
                     with backup_io():
                         if sparse and self.zeros.startswith(data):
                             # all-zero chunk: create a hole in a sparse file

+ 3 - 1
src/borg/archiver.py

@@ -501,7 +501,7 @@ class Archiver:
 
         filter = self.build_filter(matcher, peek_and_store_hardlink_masters, strip_components)
         if progress:
-            pi = ProgressIndicatorPercent(msg='Extracting files %5.1f%%', step=0.1)
+            pi = ProgressIndicatorPercent(msg='%5.1f%% Extracting: %s', step=0.1)
             pi.output('Calculating size')
             extracted_size = sum(item.file_size(hardlink_masters) for item in archive.iter_items(filter))
             pi.total = extracted_size
@@ -546,6 +546,8 @@ class Archiver:
         for pattern in include_patterns:
             if pattern.match_count == 0:
                 self.print_warning("Include pattern '%s' never matched.", pattern)
+        if pi:
+            pi.finish()
         return self.exit_code
 
     @with_repository()

+ 27 - 4
src/borg/helpers.py

@@ -26,6 +26,7 @@ from functools import wraps, partial, lru_cache
 from itertools import islice
 from operator import attrgetter
 from string import Formatter
+from shutil import get_terminal_size
 
 import msgpack
 import msgpack.fallback
@@ -1191,6 +1192,20 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
         env_var_override = None
 
 
+def ellipsis_truncate(msg, space):
+    from .platform import swidth
+    """
+    shorten a long string by adding ellipsis between it and return it, example:
+    this_is_a_very_long_string -------> this_is..._string
+    """
+    ellipsis_width = swidth('...')
+    msg_width = swidth(msg)
+    if space < ellipsis_width + msg_width:
+        return '%s...%s' % (swidth_slice(msg, space // 2 - ellipsis_width),
+                            swidth_slice(msg, -space // 2))
+    return msg + ' ' * (space - msg_width)
+
+
 class ProgressIndicatorPercent:
     LOGGER = 'borg.output.progress'
 
@@ -1208,7 +1223,6 @@ class ProgressIndicatorPercent:
         self.trigger_at = start  # output next percentage value when reaching (at least) this
         self.step = step
         self.msg = msg
-        self.output_len = len(self.msg % 100.0)
         self.handler = None
         self.logger = logging.getLogger(self.LOGGER)
 
@@ -1239,14 +1253,23 @@ class ProgressIndicatorPercent:
             self.trigger_at += self.step
             return pct
 
-    def show(self, current=None, increase=1):
+    def show(self, current=None, increase=1, info=[]):
         pct = self.progress(current, increase)
         if pct is not None:
+            # truncate the last argument, if space is available
+            if info != []:
+                msg = self.msg % (pct, *info[:-1], '')
+                space = get_terminal_size()[0] - len(msg)
+                if space < 8:
+                    info[-1] = ''
+                else:
+                    info[-1] = ellipsis_truncate(info[-1], space)
+                return self.output(self.msg % (pct, *info))
+
             return self.output(self.msg % pct)
 
     def output(self, message):
-        self.output_len = max(len(message), self.output_len)
-        message = message.ljust(self.output_len)
+        message = message.ljust(get_terminal_size(fallback=(4, 1))[0])
         self.logger.info(message)
 
     def finish(self):

+ 1 - 1
src/borg/testsuite/archiver.py

@@ -774,7 +774,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
 
         with changedir('output'):
             output = self.cmd('extract', self.repository_location + '::test', '--progress')
-            assert 'Extracting files' in output
+            assert 'Extracting:' in output
 
     def _create_test_caches(self):
         self.cmd('init', self.repository_location)