13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
"""Progress indicators.
31
30
from bzrlib import (
37
33
from bzrlib.trace import mutter
38
34
from bzrlib.symbol_versioning import (
44
41
def _supports_progress(f):
45
"""Detect if we can use pretty progress bars on the output stream f.
42
"""Detect if we can use pretty progress bars on file F.
47
If this returns true we expect that a human may be looking at that
44
If this returns true we expect that a human may be looking at that
48
45
output, and that we can repaint a line to update it.
47
This doesn't check the policy for whether we *should* use them.
50
49
isatty = getattr(f, 'isatty', None)
54
# The following case also handles Win32 - on that platform $TERM is
55
# typically never set, so the case None is treated as a smart terminal,
56
# not dumb. <https://bugs.launchpad.net/bugs/334808> win32 files do have
57
# isatty methods that return true.
55
58
if os.environ.get('TERM') == 'dumb':
56
59
# e.g. emacs compile window
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)' % (
94
122
self.current_cnt = current_cnt
96
124
self.total_cnt = total_cnt
97
self.ui_factory._progress_updated(self)
125
if self.progress_view:
126
self.progress_view.show_progress(self)
128
self.ui_factory._progress_updated(self)
100
131
self.update(self.msg)
102
133
def finished(self):
103
self.ui_factory._progress_finished(self)
134
if self.progress_view:
135
self.progress_view.task_finished(self)
137
self.ui_factory._progress_finished(self)
105
139
def make_sub_task(self):
106
return ProgressTask(self, self.ui_factory)
140
return ProgressTask(self, ui_factory=self.ui_factory,
141
progress_view=self.progress_view)
108
143
def _overall_completion_fraction(self, child_fraction=0.0):
109
144
"""Return fractional completion of this task and its parents
111
146
Returns None if no completion can be computed."""
147
if self.current_cnt is not None and self.total_cnt:
113
148
own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
150
# if this task has no estimation, it just passes on directly
151
# whatever the child has measured...
152
own_fraction = child_fraction
116
153
if self._parent_task is None:
117
154
return own_fraction
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()
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
@deprecated_function(deprecated_in((1, 16, 0)))
136
188
def ProgressBar(to_file=None, **kwargs):
137
"""Abstract factory"""
189
"""Construct a progress bar.
191
Deprecated; ask the ui_factory for a progress task instead.
138
193
if to_file is None:
139
194
to_file = sys.stderr
140
195
requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
156
211
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,))
214
# NOTE: This is also deprecated; you should provide a ProgressView instead.
228
215
class _BaseProgressBar(object):
230
217
def __init__(self,
271
258
self.to_messages_file.write(fmt_string % args)
272
259
self.to_messages_file.write('\n')
261
@deprecated_function(deprecated_in((1, 16, 0)))
274
262
def child_progress(self, **kwargs):
275
263
return ChildProgress(**kwargs)
303
291
class DotsProgressBar(_BaseProgressBar):
293
@deprecated_function(deprecated_in((1, 16, 0)))
305
294
def __init__(self, **kwargs):
306
295
_BaseProgressBar.__init__(self, **kwargs)
307
296
self.last_msg = None
308
297
self.need_nl = False
313
302
def update(self, msg=None, current_cnt=None, total_cnt=None):
314
303
if msg and msg != self.last_msg:
318
307
self.last_msg = msg
319
308
self.need_nl = True
320
309
self.to_file.write('.')
324
313
self.to_file.write('\n')
325
314
self.need_nl = False
327
316
def child_update(self, message, current, total):
333
320
class TTYProgressBar(_BaseProgressBar):
334
321
"""Progress bar display object.
353
340
SPIN_CHARS = r'/-\|'
342
@deprecated_function(deprecated_in((1, 16, 0)))
356
343
def __init__(self, **kwargs):
357
344
from bzrlib.osutils import terminal_width
358
345
_BaseProgressBar.__init__(self, **kwargs)
362
349
self._max_last_updates = 10
363
350
self.child_fraction = 0
364
351
self._have_output = False
366
353
def throttle(self, old_msg):
367
354
"""Return True if the bar was updated too recently"""
368
355
# time.time consistently takes 40/4000 ms = 0.01 ms.
411
398
if current_cnt < 0:
414
401
if current_cnt > total_cnt:
415
402
total_cnt = current_cnt
417
## # optional corner case optimisation
404
## # optional corner case optimisation
418
405
## # currently does not seem to fire so costs more than saved.
419
406
## # trivial optimal case:
420
407
## # NB if callers are doing a clear and restore with
437
424
self.last_total = total_cnt
438
425
self.child_fraction = child_fraction
440
# each function call takes 20ms/4000 = 0.005 ms,
427
# each function call takes 20ms/4000 = 0.005 ms,
441
428
# but multiple that by 4000 calls -> starts to cost.
442
429
# so anything to make this function call faster
443
430
# will improve base 'diff' time by up to 0.1 seconds.
447
434
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,
435
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
449
436
self.last_total, last_updates = self.last_updates)
450
437
eta_str = " " + str_tdelta(eta)
454
441
if self.show_spinner:
455
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
442
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
485
472
if self.last_total:
486
473
# number of markers highlighted in bar
487
markers = int(round(float(cols) *
474
markers = int(round(float(cols) *
488
475
(self.last_cnt + self.child_fraction) / self.last_total))
489
476
bar_str = '[' + ('=' * markers).ljust(cols) + '] '
504
491
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
505
492
self._have_output = True
506
493
#self.to_file.flush()
509
496
if self._have_output:
510
497
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
511
498
self._have_output = False
512
#self.to_file.flush()
499
#self.to_file.flush()
517
504
class ChildProgress(_BaseProgressBar):
518
505
"""A progress indicator that pushes its data to the parent"""
507
@deprecated_function(deprecated_in((1, 16, 0)))
520
508
def __init__(self, _stack, **kwargs):
521
509
_BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
522
510
self.parent = _stack.top()
558
546
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
549
def str_tdelta(delt):