65
67
Code updating the task may also set fields as hints about how to display
66
68
it: show_pct, show_spinner, show_eta, show_count, show_bar. UIs
67
69
will not necessarily respect all these fields.
69
:ivar update_latency: The interval (in seconds) at which the PB should be
70
updated. Setting this to zero suggests every update should be shown
73
:ivar show_transport_activity: If true (default), transport activity
74
will be shown when this task is drawn. Disable it if you're sure
75
that only irrelevant or uninteresting transport activity can occur
79
def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
80
"""Construct a new progress task.
82
:param parent_task: Enclosing ProgressTask or None.
84
:param progress_view: ProgressView to display this ProgressTask.
86
:param ui_factory: The UI factory that will display updates;
87
deprecated in favor of passing progress_view directly.
89
Normally you should not call this directly but rather through
90
`ui_factory.nested_progress_bar`.
72
def __init__(self, parent_task=None, ui_factory=None):
92
73
self._parent_task = parent_task
93
74
self._last_update = 0
94
75
self.total_cnt = None
95
76
self.current_cnt = None
97
# TODO: deprecate passing ui_factory
98
78
self.ui_factory = ui_factory
99
self.progress_view = progress_view
100
79
self.show_pct = False
101
80
self.show_spinner = True
102
81
self.show_eta = False,
103
82
self.show_count = True
104
83
self.show_bar = True
105
self.update_latency = 0.1
106
self.show_transport_activity = True
108
85
def __repr__(self):
109
86
return '%s(%r/%r, msg=%r)' % (
152
120
own_fraction = 0.0
153
121
return self._parent_task._overall_completion_fraction(own_fraction)
155
@deprecated_method(deprecated_in((2, 1, 0)))
156
123
def note(self, fmt_string, *args):
157
"""Record a note without disrupting the progress bar.
159
Deprecated: use ui_factory.note() instead or bzrlib.trace. Note that
160
ui_factory.note takes just one string as the argument, not a format
161
string and arguments.
124
"""Record a note without disrupting the progress bar."""
125
# XXX: shouldn't be here; put it in mutter or the ui instead
164
127
self.ui_factory.note(fmt_string % args)
166
129
self.ui_factory.note(fmt_string)
169
# TODO: deprecate this method; the model object shouldn't be concerned
170
# with whether it's shown or not. Most callers use this because they
171
# want to write some different non-progress output to the screen, but
172
# they should probably instead use a stream that's synchronized with
173
# the progress output. It may be there is a model-level use for
174
# saying "this task's not active at the moment" but I don't see it. --
176
if self.progress_view:
177
self.progress_view.clear()
179
self.ui_factory.clear_term()
182
# NOTE: This is also deprecated; you should provide a ProgressView instead.
132
# XXX: shouldn't be here; put it in mutter or the ui instead
133
self.ui_factory.clear_term()
136
def ProgressBar(to_file=None, **kwargs):
137
"""Abstract factory"""
140
requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
141
# An value of '' or not set reverts to standard processing
142
if requested_bar_type in (None, ''):
143
if _supports_progress(to_file):
144
return TTYProgressBar(to_file=to_file, **kwargs)
146
return DummyProgress(to_file=to_file, **kwargs)
148
# Minor sanitation to prevent spurious errors
149
requested_bar_type = requested_bar_type.lower().strip()
150
# TODO: jam 20060710 Arguably we shouldn't raise an exception
151
# but should instead just disable progress bars if we
152
# don't recognize the type
153
if requested_bar_type not in _progress_bar_types:
154
raise errors.InvalidProgressBarType(requested_bar_type,
155
_progress_bar_types.keys())
156
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
159
class ProgressBarStack(object):
160
"""A stack of progress bars.
162
This class is deprecated: instead, ask the ui factory for a new progress
163
task and finish it when it's done.
166
@deprecated_method(deprecated_in((1, 12, 0)))
174
to_messages_file=None,
176
"""Setup the stack with the parameters the progress bars should have."""
179
if to_messages_file is None:
180
to_messages_file = sys.stdout
181
self._to_file = to_file
182
self._show_pct = show_pct
183
self._show_spinner = show_spinner
184
self._show_eta = show_eta
185
self._show_bar = show_bar
186
self._show_count = show_count
187
self._to_messages_file = to_messages_file
189
self._klass = klass or ProgressBar
192
if len(self._stack) != 0:
193
return self._stack[-1]
198
if len(self._stack) != 0:
199
return self._stack[0]
203
def get_nested(self):
204
"""Return a nested progress bar."""
205
if len(self._stack) == 0:
208
func = self.top().child_progress
209
new_bar = func(to_file=self._to_file,
210
show_pct=self._show_pct,
211
show_spinner=self._show_spinner,
212
show_eta=self._show_eta,
213
show_bar=self._show_bar,
214
show_count=self._show_count,
215
to_messages_file=self._to_messages_file,
217
self._stack.append(new_bar)
220
def return_pb(self, bar):
221
"""Return bar after its been used."""
222
if bar is not self._stack[-1]:
223
warnings.warn("%r is not currently active" % (bar,))
183
228
class _BaseProgressBar(object):
185
230
def __init__(self,
255
300
return DummyProgress(**kwargs)
303
class DotsProgressBar(_BaseProgressBar):
305
def __init__(self, **kwargs):
306
_BaseProgressBar.__init__(self, **kwargs)
313
def update(self, msg=None, current_cnt=None, total_cnt=None):
314
if msg and msg != self.last_msg:
316
self.to_file.write('\n')
317
self.to_file.write(msg + ': ')
320
self.to_file.write('.')
324
self.to_file.write('\n')
327
def child_update(self, message, current, total):
333
class TTYProgressBar(_BaseProgressBar):
334
"""Progress bar display object.
336
Several options are available to control the display. These can
337
be passed as parameters to the constructor or assigned at any time:
340
Show percentage complete.
342
Show rotating baton. This ticks over on every update even
343
if the values don't change.
345
Show predicted time-to-completion.
349
Show numerical counts.
351
The output file should be in line-buffered or unbuffered mode.
356
def __init__(self, **kwargs):
357
from bzrlib.osutils import terminal_width
358
_BaseProgressBar.__init__(self, **kwargs)
360
self.width = terminal_width()
361
self.last_updates = []
362
self._max_last_updates = 10
363
self.child_fraction = 0
364
self._have_output = False
366
def throttle(self, old_msg):
367
"""Return True if the bar was updated too recently"""
368
# time.time consistently takes 40/4000 ms = 0.01 ms.
369
# time.clock() is faster, but gives us CPU time, not wall-clock time
371
if self.start_time is not None and (now - self.start_time) < 1:
373
if old_msg != self.last_msg:
375
interval = now - self.last_update
377
if interval < self.MIN_PAUSE:
380
self.last_updates.append(now - self.last_update)
381
# Don't let the queue grow without bound
382
self.last_updates = self.last_updates[-self._max_last_updates:]
383
self.last_update = now
387
self.update(self.last_msg, self.last_cnt, self.last_total,
390
def child_update(self, message, current, total):
391
if current is not None and total != 0:
392
child_fraction = float(current) / total
393
if self.last_cnt is None:
395
elif self.last_cnt + child_fraction <= self.last_total:
396
self.child_fraction = child_fraction
397
if self.last_msg is None:
401
def update(self, msg, current_cnt=None, total_cnt=None,
403
"""Update and redraw progress bar.
408
if total_cnt is None:
409
total_cnt = self.last_total
414
if current_cnt > total_cnt:
415
total_cnt = current_cnt
417
## # optional corner case optimisation
418
## # currently does not seem to fire so costs more than saved.
419
## # trivial optimal case:
420
## # NB if callers are doing a clear and restore with
421
## # the saved values, this will prevent that:
422
## # in that case add a restore method that calls
423
## # _do_update or some such
424
## if (self.last_msg == msg and
425
## self.last_cnt == current_cnt and
426
## self.last_total == total_cnt and
427
## self.child_fraction == child_fraction):
433
old_msg = self.last_msg
434
# save these for the tick() function
436
self.last_cnt = current_cnt
437
self.last_total = total_cnt
438
self.child_fraction = child_fraction
440
# each function call takes 20ms/4000 = 0.005 ms,
441
# but multiple that by 4000 calls -> starts to cost.
442
# so anything to make this function call faster
443
# will improve base 'diff' time by up to 0.1 seconds.
444
if self.throttle(old_msg):
447
if self.show_eta and self.start_time and self.last_total:
448
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
449
self.last_total, last_updates = self.last_updates)
450
eta_str = " " + str_tdelta(eta)
454
if self.show_spinner:
455
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
459
# always update this; it's also used for the bar
462
if self.show_pct and self.last_total and self.last_cnt:
463
pct = 100.0 * ((self.last_cnt + self.child_fraction) / self.last_total)
464
pct_str = ' (%5.1f%%)' % pct
468
if not self.show_count:
470
elif self.last_cnt is None:
472
elif self.last_total is None:
473
count_str = ' %i' % (self.last_cnt)
475
# make both fields the same size
476
t = '%i' % (self.last_total)
477
c = '%*i' % (len(t), self.last_cnt)
478
count_str = ' ' + c + '/' + t
481
# progress bar, if present, soaks up all remaining space
482
cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
483
- len(eta_str) - len(count_str) - 3
486
# number of markers highlighted in bar
487
markers = int(round(float(cols) *
488
(self.last_cnt + self.child_fraction) / self.last_total))
489
bar_str = '[' + ('=' * markers).ljust(cols) + '] '
491
# don't know total, so can't show completion.
492
# so just show an expanded spinning thingy
493
m = self.spin_pos % cols
494
ms = (' ' * m + '*').ljust(cols)
496
bar_str = '[' + ms + '] '
502
m = spin_str + bar_str + self.last_msg + count_str \
504
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
505
self._have_output = True
506
#self.to_file.flush()
509
if self._have_output:
510
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
511
self._have_output = False
512
#self.to_file.flush()
517
class ChildProgress(_BaseProgressBar):
518
"""A progress indicator that pushes its data to the parent"""
520
def __init__(self, _stack, **kwargs):
521
_BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
522
self.parent = _stack.top()
525
self.child_fraction = 0
528
def update(self, msg, current_cnt=None, total_cnt=None):
529
self.current = current_cnt
530
if total_cnt is not None:
531
self.total = total_cnt
533
self.child_fraction = 0
536
def child_update(self, message, current, total):
537
if current is None or total == 0:
538
self.child_fraction = 0
540
self.child_fraction = float(current) / total
544
if self.current is None:
547
count = self.current+self.child_fraction
548
if count > self.total:
550
mutter('clamping count of %d to %d' % (count, self.total))
552
self.parent.child_update(self.message, count, self.total)
557
def note(self, *args, **kwargs):
558
self.parent.note(*args, **kwargs)
561
class InstrumentedProgress(TTYProgressBar):
562
"""TTYProgress variant that tracks outcomes"""
564
def __init__(self, *args, **kwargs):
565
self.always_throttled = True
566
self.never_throttle = False
567
TTYProgressBar.__init__(self, *args, **kwargs)
569
def throttle(self, old_message):
570
if self.never_throttle:
573
result = TTYProgressBar.throttle(self, old_message)
575
self.always_throttled = False
258
578
def str_tdelta(delt):