Browse Source

Improve extract progress display and ProgressIndicatorPercent

Abogical 8 years ago
parent
commit
b737866905
4 changed files with 35 additions and 14 deletions
  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 safe_encode, safe_decode, make_path_safe, remove_surrogates, swidth_slice
 from .helpers import decode_dict, StableDict
 from .helpers import decode_dict, StableDict
 from .helpers import int_to_bigint, bigint_to_int, bin_to_hex
 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 PathPrefixPattern, FnmatchPattern
 from .helpers import consume, chunkit
 from .helpers import consume, chunkit
 from .helpers import CompressionDecider1, CompressionDecider2, CompressionSpec
 from .helpers import CompressionDecider1, CompressionDecider2, CompressionSpec
@@ -93,11 +93,7 @@ class Statistics:
                     msg = ''
                     msg = ''
                     space = columns - swidth(msg)
                     space = columns - swidth(msg)
                 if space >= 8:
                 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:
             else:
                 msg = ' ' * columns
                 msg = ' ' * columns
             print(msg, file=stream or sys.stderr, end="\r", flush=True)
             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:
             if 'chunks' in item:
                 for _, data in self.pipeline.fetch_many([c.id for c in item.chunks], is_preloaded=True):
                 for _, data in self.pipeline.fetch_many([c.id for c in item.chunks], is_preloaded=True):
                     if pi:
                     if pi:
-                        pi.show(increase=len(data))
+                        pi.show(increase=len(data), info=[remove_surrogates(item.path)])
                     if stdout:
                     if stdout:
                         sys.stdout.buffer.write(data)
                         sys.stdout.buffer.write(data)
                 if stdout:
                 if stdout:
@@ -501,7 +497,7 @@ Number of files: {0.stats.nfiles}'''.format(
                 ids = [c.id for c in item.chunks]
                 ids = [c.id for c in item.chunks]
                 for _, data in self.pipeline.fetch_many(ids, is_preloaded=True):
                 for _, data in self.pipeline.fetch_many(ids, is_preloaded=True):
                     if pi:
                     if pi:
-                        pi.show(increase=len(data))
+                        pi.show(increase=len(data), info=[remove_surrogates(item.path)])
                     with backup_io():
                     with backup_io():
                         if sparse and self.zeros.startswith(data):
                         if sparse and self.zeros.startswith(data):
                             # all-zero chunk: create a hole in a sparse file
                             # 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)
         filter = self.build_filter(matcher, peek_and_store_hardlink_masters, strip_components)
         if progress:
         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')
             pi.output('Calculating size')
             extracted_size = sum(item.file_size(hardlink_masters) for item in archive.iter_items(filter))
             extracted_size = sum(item.file_size(hardlink_masters) for item in archive.iter_items(filter))
             pi.total = extracted_size
             pi.total = extracted_size
@@ -546,6 +546,8 @@ class Archiver:
         for pattern in include_patterns:
         for pattern in include_patterns:
             if pattern.match_count == 0:
             if pattern.match_count == 0:
                 self.print_warning("Include pattern '%s' never matched.", pattern)
                 self.print_warning("Include pattern '%s' never matched.", pattern)
+        if pi:
+            pi.finish()
         return self.exit_code
         return self.exit_code
 
 
     @with_repository()
     @with_repository()

+ 27 - 4
src/borg/helpers.py

@@ -26,6 +26,7 @@ from functools import wraps, partial, lru_cache
 from itertools import islice
 from itertools import islice
 from operator import attrgetter
 from operator import attrgetter
 from string import Formatter
 from string import Formatter
+from shutil import get_terminal_size
 
 
 import msgpack
 import msgpack
 import msgpack.fallback
 import msgpack.fallback
@@ -1191,6 +1192,20 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
         env_var_override = 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:
 class ProgressIndicatorPercent:
     LOGGER = 'borg.output.progress'
     LOGGER = 'borg.output.progress'
 
 
@@ -1208,7 +1223,6 @@ class ProgressIndicatorPercent:
         self.trigger_at = start  # output next percentage value when reaching (at least) this
         self.trigger_at = start  # output next percentage value when reaching (at least) this
         self.step = step
         self.step = step
         self.msg = msg
         self.msg = msg
-        self.output_len = len(self.msg % 100.0)
         self.handler = None
         self.handler = None
         self.logger = logging.getLogger(self.LOGGER)
         self.logger = logging.getLogger(self.LOGGER)
 
 
@@ -1239,14 +1253,23 @@ class ProgressIndicatorPercent:
             self.trigger_at += self.step
             self.trigger_at += self.step
             return pct
             return pct
 
 
-    def show(self, current=None, increase=1):
+    def show(self, current=None, increase=1, info=[]):
         pct = self.progress(current, increase)
         pct = self.progress(current, increase)
         if pct is not None:
         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)
             return self.output(self.msg % pct)
 
 
     def output(self, message):
     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)
         self.logger.info(message)
 
 
     def finish(self):
     def finish(self):

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

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