1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2005, 2006 Canonical Ltd
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19
"""Progress indicators.
20
21
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
21
will manage a conceptual stack of nested activities.
22
will maintain a ProgressBarStack for you.
24
For direct use, the factory ProgressBar will return an auto-detected progress
25
bar that should match your terminal type. You can manually create a
26
ProgressBarStack too if you need multiple levels of cooperating progress bars.
27
Note that bzrlib's internal functions use the ui module, so if you are using
28
bzrlib it really is best to use bzrlib.ui.ui_factory.
31
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
32
# when the rate is unpredictable
38
from bzrlib.lazy_import import lazy_import
39
lazy_import(globals(), """
31
40
from bzrlib import (
37
45
from bzrlib.trace import mutter
38
from bzrlib.symbol_versioning import (
44
48
def _supports_progress(f):
45
49
"""Detect if we can use pretty progress bars on the output stream f.
47
If this returns true we expect that a human may be looking at that
51
If this returns true we expect that a human may be looking at that
48
52
output, and that we can repaint a line to update it.
50
54
isatty = getattr(f, 'isatty', None)
61
class ProgressTask(object):
62
"""Model component of a progress indicator.
64
Most code that needs to indicate progress should update one of these,
65
and it will in turn update the display, if one is present.
67
Code updating the task may also set fields as hints about how to display
68
it: show_pct, show_spinner, show_eta, show_count, show_bar. UIs
69
will not necessarily respect all these fields.
72
def __init__(self, parent_task=None, ui_factory=None):
73
"""Construct a new progress task.
75
Normally you should not call this directly but rather through
76
`ui_factory.nested_progress_bar`.
78
self._parent_task = parent_task
81
self.current_cnt = None
83
self.ui_factory = ui_factory
85
self.show_spinner = True
86
self.show_eta = False,
87
self.show_count = True
91
return '%s(%r/%r, msg=%r)' % (
92
self.__class__.__name__,
97
def update(self, msg, current_cnt=None, total_cnt=None):
99
self.current_cnt = current_cnt
101
self.total_cnt = total_cnt
102
self.ui_factory._progress_updated(self)
105
self.update(self.msg)
108
self.ui_factory._progress_finished(self)
110
def make_sub_task(self):
111
return ProgressTask(self, self.ui_factory)
113
def _overall_completion_fraction(self, child_fraction=0.0):
114
"""Return fractional completion of this task and its parents
116
Returns None if no completion can be computed."""
117
if self.current_cnt is not None and self.total_cnt:
118
own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
120
# if this task has no estimation, it just passes on directly
121
# whatever the child has measured...
122
own_fraction = child_fraction
123
if self._parent_task is None:
126
if own_fraction is None:
128
return self._parent_task._overall_completion_fraction(own_fraction)
130
def note(self, fmt_string, *args):
131
"""Record a note without disrupting the progress bar."""
132
# XXX: shouldn't be here; put it in mutter or the ui instead
134
self.ui_factory.note(fmt_string % args)
136
self.ui_factory.note(fmt_string)
139
# XXX: shouldn't be here; put it in mutter or the ui instead
140
self.ui_factory.clear_term()
65
_progress_bar_types = {}
143
68
def ProgressBar(to_file=None, **kwargs):
162
87
_progress_bar_types.keys())
163
88
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
166
91
class ProgressBarStack(object):
167
"""A stack of progress bars.
169
This class is deprecated: instead, ask the ui factory for a new progress
170
task and finish it when it's done.
173
@deprecated_method(deprecated_in((1, 12, 0)))
92
"""A stack of progress bars."""
174
94
def __init__(self,
227
147
def return_pb(self, bar):
228
148
"""Return bar after its been used."""
229
149
if bar is not self._stack[-1]:
230
warnings.warn("%r is not currently active" % (bar,))
150
raise errors.MissingProgressBarFinish()
235
154
class _BaseProgressBar(object):
237
156
def __init__(self,
307
226
return DummyProgress(**kwargs)
229
_progress_bar_types['dummy'] = DummyProgress
230
_progress_bar_types['none'] = DummyProgress
310
233
class DotsProgressBar(_BaseProgressBar):
312
235
def __init__(self, **kwargs):
313
236
_BaseProgressBar.__init__(self, **kwargs)
314
237
self.last_msg = None
315
238
self.need_nl = False
320
243
def update(self, msg=None, current_cnt=None, total_cnt=None):
321
244
if msg and msg != self.last_msg:
402
326
elif self.last_cnt + child_fraction <= self.last_total:
403
327
self.child_fraction = child_fraction
329
mutter('not updating child fraction')
404
330
if self.last_msg is None:
405
331
self.last_msg = ''
408
334
def update(self, msg, current_cnt=None, total_cnt=None,
410
"""Update and redraw progress bar.
336
"""Update and redraw progress bar."""
413
338
msg = self.last_msg
418
343
if current_cnt < 0:
421
346
if current_cnt > total_cnt:
422
347
total_cnt = current_cnt
424
## # optional corner case optimisation
349
## # optional corner case optimisation
425
350
## # currently does not seem to fire so costs more than saved.
426
351
## # trivial optimal case:
427
352
## # NB if callers are doing a clear and restore with
444
366
self.last_total = total_cnt
445
367
self.child_fraction = child_fraction
447
# each function call takes 20ms/4000 = 0.005 ms,
369
# each function call takes 20ms/4000 = 0.005 ms,
448
370
# but multiple that by 4000 calls -> starts to cost.
449
371
# so anything to make this function call faster
450
372
# will improve base 'diff' time by up to 0.1 seconds.
454
376
if self.show_eta and self.start_time and self.last_total:
455
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
377
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
456
378
self.last_total, last_updates = self.last_updates)
457
379
eta_str = " " + str_tdelta(eta)
461
383
if self.show_spinner:
462
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
384
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
482
404
# make both fields the same size
483
405
t = '%i' % (self.last_total)
484
406
c = '%*i' % (len(t), self.last_cnt)
485
count_str = ' ' + c + '/' + t
407
count_str = ' ' + c + '/' + t
487
409
if self.show_bar:
488
410
# progress bar, if present, soaks up all remaining space
499
421
# so just show an expanded spinning thingy
500
422
m = self.spin_pos % cols
501
423
ms = (' ' * m + '*').ljust(cols)
503
425
bar_str = '[' + ms + '] '
509
m = spin_str + bar_str + self.last_msg + count_str \
431
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
511
432
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
512
433
self._have_output = True
513
434
#self.to_file.flush()
516
437
if self._have_output:
517
438
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
518
439
self._have_output = False
519
#self.to_file.flush()
440
#self.to_file.flush()
443
_progress_bar_types['tty'] = TTYProgressBar
524
446
class ChildProgress(_BaseProgressBar):
609
531
if elapsed < 2.0: # not enough time to estimate
612
534
total_duration = float(elapsed) * float(total) / float(current)
536
assert total_duration >= elapsed
614
538
if last_updates and len(last_updates) >= n_recent:
615
539
avg = sum(last_updates) / float(len(last_updates))
616
540
time_left = avg * (total - current)
637
561
self.cur_phase = 0
639
563
self.cur_phase += 1
564
assert self.cur_phase < self.total
640
565
self.pb.update(self.message, self.cur_phase, self.total)
643
_progress_bar_types = {}
644
_progress_bar_types['dummy'] = DummyProgress
645
_progress_bar_types['none'] = DummyProgress
646
_progress_bar_types['tty'] = TTYProgressBar
647
_progress_bar_types['dots'] = DotsProgressBar