1
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2005 Canonical <canonical.com>
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
45
"""Return estimated terminal width.
47
TODO: Do something smart on Windows?
49
TODO: Is there anything that gets a better update when the window
50
is resized while the program is running?
53
return int(os.environ['COLUMNS'])
54
except (IndexError, KeyError, ValueError):
42
from collections import deque
45
import bzrlib.errors as errors
46
from bzrlib.trace import mutter
58
49
def _supports_progress(f):
75
66
return DotsProgressBar(to_file=to_file, **kwargs)
69
class ProgressBarStack(object):
70
"""A stack of progress bars."""
79
to_messages_file=sys.stdout,
81
"""Setup the stack with the parameters the progress bars should have."""
82
self._to_file = to_file
83
self._show_pct = show_pct
84
self._show_spinner = show_spinner
85
self._show_eta = show_eta
86
self._show_bar = show_bar
87
self._show_count = show_count
88
self._to_messages_file = to_messages_file
90
self._klass = klass or TTYProgressBar
93
if len(self._stack) != 0:
94
return self._stack[-1]
99
"""Return a nested progress bar."""
100
if len(self._stack) == 0:
103
func = self.top().child_progress
104
new_bar = func(to_file=self._to_file,
105
show_pct=self._show_pct,
106
show_spinner=self._show_spinner,
107
show_eta=self._show_eta,
108
show_bar=self._show_bar,
109
show_count=self._show_count,
110
to_messages_file=self._to_messages_file,
112
self._stack.append(new_bar)
115
def return_pb(self, bar):
116
"""Return bar after its been used."""
117
if bar is not self._stack[-1]:
118
raise errors.MissingProgressBarFinish()
78
122
class _BaseProgressBar(object):
79
124
def __init__(self,
80
125
to_file=sys.stderr,
82
127
show_spinner=False,
131
to_messages_file=sys.stdout,
86
133
object.__init__(self)
87
134
self.to_file = to_file
135
self.to_messages_file = to_messages_file
89
136
self.last_msg = None
90
137
self.last_cnt = None
91
138
self.last_total = None
94
141
self.show_eta = show_eta
95
142
self.show_bar = show_bar
96
143
self.show_count = show_count
147
"""Return this bar to its progress stack."""
149
assert self._stack is not None
150
self._stack.return_pb(self)
152
def note(self, fmt_string, *args, **kwargs):
153
"""Record a note without disrupting the progress bar."""
155
self.to_messages_file.write(fmt_string % args)
156
self.to_messages_file.write('\n')
158
def child_progress(self, **kwargs):
159
return ChildProgress(**kwargs)
100
162
class DummyProgress(_BaseProgressBar):
108
170
def update(self, msg=None, current=None, total=None):
173
def child_update(self, message, current, total):
179
def note(self, fmt_string, *args, **kwargs):
180
"""See _BaseProgressBar.note()."""
182
def child_progress(self, **kwargs):
183
return DummyProgress(**kwargs)
115
185
class DotsProgressBar(_BaseProgressBar):
116
187
def __init__(self, **kwargs):
117
188
_BaseProgressBar.__init__(self, **kwargs)
118
189
self.last_msg = None
136
207
self.to_file.write('\n')
209
def child_update(self, message, current, total):
139
212
class TTYProgressBar(_BaseProgressBar):
140
213
"""Progress bar display object.
163
236
def __init__(self, **kwargs):
237
from bzrlib.osutils import terminal_width
164
238
_BaseProgressBar.__init__(self, **kwargs)
165
239
self.spin_pos = 0
166
self.width = _width()
240
self.width = terminal_width()
167
241
self.start_time = None
168
242
self.last_update = None
243
self.last_updates = deque()
244
self.child_fraction = 0
171
247
def throttle(self):
179
255
if interval > 0 and interval < self.MIN_PAUSE:
258
self.last_updates.append(now - self.last_update)
182
259
self.last_update = now
187
self.update(self.last_msg, self.last_cnt, self.last_total)
191
def update(self, msg, current_cnt=None, total_cnt=None):
264
self.update(self.last_msg, self.last_cnt, self.last_total,
267
def child_update(self, message, current, total):
268
if current is not None:
269
child_fraction = float(current) / total
270
if self.last_cnt is None:
272
elif self.last_cnt + child_fraction <= self.last_total:
273
self.child_fraction = child_fraction
275
mutter('not updating child fraction')
276
if self.last_msg is None:
281
def update(self, msg, current_cnt=None, total_cnt=None,
192
283
"""Update and redraw progress bar."""
284
self.child_fraction = child_fraction
289
if current_cnt > total_cnt:
290
total_cnt = current_cnt
292
old_msg = self.last_msg
194
293
# save these for the tick() function
195
294
self.last_msg = msg
196
295
self.last_cnt = current_cnt
197
296
self.last_total = total_cnt
298
if old_msg == self.last_msg and self.throttle():
203
assert current_cnt <= total_cnt
205
assert current_cnt >= 0
207
301
if self.show_eta and self.start_time and total_cnt:
208
eta = get_eta(self.start_time, current_cnt, total_cnt)
302
eta = get_eta(self.start_time, current_cnt+child_fraction,
303
total_cnt, last_updates = self.last_updates)
209
304
eta_str = " " + str_tdelta(eta)
245
340
# number of markers highlighted in bar
246
markers = int(round(float(cols) * current_cnt / total_cnt))
341
markers = int(round(float(cols) *
342
(current_cnt + child_fraction) / total_cnt))
247
343
bar_str = '[' + ('=' * markers).ljust(cols) + '] '
249
345
# don't know total, so can't show completion.
263
359
self.to_file.write('\r' + m.ljust(self.width - 1))
264
360
#self.to_file.flush()
268
363
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
269
364
#self.to_file.flush()
367
class ChildProgress(_BaseProgressBar):
368
"""A progress indicator that pushes its data to the parent"""
369
def __init__(self, _stack, **kwargs):
370
_BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
371
self.parent = _stack.top()
374
self.child_fraction = 0
377
def update(self, msg, current_cnt=None, total_cnt=None):
378
self.current = current_cnt
379
self.total = total_cnt
381
self.child_fraction = 0
384
def child_update(self, message, current, total):
386
self.child_fraction = 0
388
self.child_fraction = float(current) / total
392
if self.current is None:
395
count = self.current+self.child_fraction
396
if count > self.total:
397
mutter('clamping count of %d to %d' % (count, self.total))
399
self.parent.child_update(self.message, count, self.total)
273
405
def str_tdelta(delt):
282
def get_eta(start_time, current, total, enough_samples=3):
414
def get_eta(start_time, current, total, enough_samples=3, last_updates=None, n_recent=10):
283
415
if start_time is None:
302
434
assert total_duration >= elapsed
436
if last_updates and len(last_updates) >= n_recent:
437
while len(last_updates) > n_recent:
438
last_updates.popleft()
439
avg = sum(last_updates) / float(len(last_updates))
440
time_left = avg * (total - current)
442
old_time_left = total_duration - elapsed
444
# We could return the average, or some other value here
445
return (time_left + old_time_left) / 2
304
447
return total_duration - elapsed