61
64
class ProgressTask(object):
62
65
"""Model component of a progress indicator.
64
Most code that needs to indicate progress should update one of these,
67
Most code that needs to indicate progress should update one of these,
65
68
and it will in turn update the display, if one is present.
67
70
Code updating the task may also set fields as hints about how to display
68
71
it: show_pct, show_spinner, show_eta, show_count, show_bar. UIs
69
72
will not necessarily respect all these fields.
74
:ivar update_latency: The interval (in seconds) at which the PB should be
75
updated. Setting this to zero suggests every update should be shown
78
:ivar show_transport_activity: If true (default), transport activity
79
will be shown when this task is drawn. Disable it if you're sure
80
that only irrelevant or uninteresting transport activity can occur
72
def __init__(self, parent_task=None, ui_factory=None):
84
def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
85
"""Construct a new progress task.
87
:param parent_task: Enclosing ProgressTask or None.
89
:param progress_view: ProgressView to display this ProgressTask.
91
:param ui_factory: The UI factory that will display updates;
92
deprecated in favor of passing progress_view directly.
94
Normally you should not call this directly but rather through
95
`ui_factory.nested_progress_bar`.
73
97
self._parent_task = parent_task
74
98
self._last_update = 0
75
99
self.total_cnt = None
76
100
self.current_cnt = None
102
# TODO: deprecate passing ui_factory
78
103
self.ui_factory = ui_factory
104
self.progress_view = progress_view
79
105
self.show_pct = False
80
106
self.show_spinner = True
81
107
self.show_eta = False,
82
108
self.show_count = True
83
109
self.show_bar = True
110
self.update_latency = 0.1
111
self.show_transport_activity = True
85
113
def __repr__(self):
86
114
return '%s(%r/%r, msg=%r)' % (
120
157
own_fraction = 0.0
121
158
return self._parent_task._overall_completion_fraction(own_fraction)
160
@deprecated_method(deprecated_in((2, 1, 0)))
123
161
def note(self, fmt_string, *args):
124
"""Record a note without disrupting the progress bar."""
125
# XXX: shouldn't be here; put it in mutter or the ui instead
162
"""Record a note without disrupting the progress bar.
164
Deprecated: use ui_factory.note() instead or bzrlib.trace. Note that
165
ui_factory.note takes just one string as the argument, not a format
166
string and arguments.
127
169
self.ui_factory.note(fmt_string % args)
129
171
self.ui_factory.note(fmt_string)
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,))
174
# TODO: deprecate this method; the model object shouldn't be concerned
175
# with whether it's shown or not. Most callers use this because they
176
# want to write some different non-progress output to the screen, but
177
# they should probably instead use a stream that's synchronized with
178
# the progress output. It may be there is a model-level use for
179
# saying "this task's not active at the moment" but I don't see it. --
181
if self.progress_view:
182
self.progress_view.clear()
184
self.ui_factory.clear_term()
187
# NOTE: This is also deprecated; you should provide a ProgressView instead.
228
188
class _BaseProgressBar(object):
230
190
def __init__(self,
300
260
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
578
263
def str_tdelta(delt):