61
def ProgressBar(to_file=sys.stderr, **kwargs):
61
def ProgressBar(to_file=None, **kwargs):
62
62
"""Abstract factory"""
63
65
if _supports_progress(to_file):
64
66
return TTYProgressBar(to_file=to_file, **kwargs)
70
72
"""A stack of progress bars."""
79
to_messages_file=sys.stdout,
81
to_messages_file=None,
81
83
"""Setup the stack with the parameters the progress bars should have."""
86
if to_messages_file is None:
87
to_messages_file = sys.stdout
82
88
self._to_file = to_file
83
89
self._show_pct = show_pct
84
90
self._show_spinner = show_spinner
122
134
class _BaseProgressBar(object):
124
136
def __init__(self,
127
139
show_spinner=False,
131
to_messages_file=sys.stdout,
143
to_messages_file=None,
133
145
object.__init__(self)
148
if to_messages_file is None:
149
to_messages_file = sys.stdout
134
150
self.to_file = to_file
135
151
self.to_messages_file = to_messages_file
136
152
self.last_msg = None
142
158
self.show_bar = show_bar
143
159
self.show_count = show_count
144
160
self._stack = _stack
162
self.MIN_PAUSE = 0.1 # seconds
165
self.start_time = now
166
# next update should not throttle
167
self.last_update = now - self.MIN_PAUSE - 1
146
169
def finished(self):
147
170
"""Return this bar to its progress stack."""
182
205
def child_progress(self, **kwargs):
183
206
return DummyProgress(**kwargs)
185
209
class DotsProgressBar(_BaseProgressBar):
187
211
def __init__(self, **kwargs):
207
230
self.to_file.write('\n')
209
233
def child_update(self, message, current, total):
212
237
class TTYProgressBar(_BaseProgressBar):
213
238
"""Progress bar display object.
239
263
self.spin_pos = 0
240
264
self.width = terminal_width()
241
265
self.start_time = None
242
self.last_update = None
243
266
self.last_updates = deque()
244
267
self.child_fraction = 0
247
270
def throttle(self):
248
271
"""Return True if the bar was updated too recently"""
250
if self.start_time is None:
251
self.start_time = self.last_update = now
254
interval = now - self.last_update
255
if interval > 0 and interval < self.MIN_PAUSE:
272
# time.time consistently takes 40/4000 ms = 0.01 ms.
273
# but every single update to the pb invokes it.
274
# so we use time.clock which takes 20/4000 ms = 0.005ms
275
# on the downside, time.clock() appears to have approximately
276
# 10ms granularity, so we treat a zero-time change as 'throttled.'
279
interval = now - self.last_update
281
if interval < self.MIN_PAUSE:
258
284
self.last_updates.append(now - self.last_update)
259
285
self.last_update = now
281
307
def update(self, msg, current_cnt=None, total_cnt=None,
282
308
child_fraction=0):
283
309
"""Update and redraw progress bar."""
284
self.child_fraction = child_fraction
286
311
if current_cnt < 0:
289
314
if current_cnt > total_cnt:
290
315
total_cnt = current_cnt
317
## # optional corner case optimisation
318
## # currently does not seem to fire so costs more than saved.
319
## # trivial optimal case:
320
## # NB if callers are doing a clear and restore with
321
## # the saved values, this will prevent that:
322
## # in that case add a restore method that calls
323
## # _do_update or some such
324
## if (self.last_msg == msg and
325
## self.last_cnt == current_cnt and
326
## self.last_total == total_cnt and
327
## self.child_fraction == child_fraction):
292
330
old_msg = self.last_msg
293
331
# save these for the tick() function
294
332
self.last_msg = msg
295
333
self.last_cnt = current_cnt
296
334
self.last_total = total_cnt
335
self.child_fraction = child_fraction
337
# each function call takes 20ms/4000 = 0.005 ms,
338
# but multiple that by 4000 calls -> starts to cost.
339
# so anything to make this function call faster
340
# will improve base 'diff' time by up to 0.1 seconds.
298
341
if old_msg == self.last_msg and self.throttle():
301
if self.show_eta and self.start_time and total_cnt:
302
eta = get_eta(self.start_time, current_cnt+child_fraction,
303
total_cnt, last_updates = self.last_updates)
344
if self.show_eta and self.start_time and self.last_total:
345
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
346
self.last_total, last_updates = self.last_updates)
304
347
eta_str = " " + str_tdelta(eta)
313
356
# always update this; it's also used for the bar
314
357
self.spin_pos += 1
316
if self.show_pct and total_cnt and current_cnt:
317
pct = 100.0 * ((current_cnt + child_fraction) / total_cnt)
359
if self.show_pct and self.last_total and self.last_cnt:
360
pct = 100.0 * ((self.last_cnt + self.child_fraction) / self.last_total)
318
361
pct_str = ' (%5.1f%%)' % pct
322
365
if not self.show_count:
324
elif current_cnt is None:
367
elif self.last_cnt is None:
326
elif total_cnt is None:
327
count_str = ' %i' % (current_cnt)
369
elif self.last_total is None:
370
count_str = ' %i' % (self.last_cnt)
329
372
# make both fields the same size
330
t = '%i' % (total_cnt)
331
c = '%*i' % (len(t), current_cnt)
373
t = '%i' % (self.last_total)
374
c = '%*i' % (len(t), self.last_cnt)
332
375
count_str = ' ' + c + '/' + t
334
377
if self.show_bar:
335
378
# progress bar, if present, soaks up all remaining space
336
cols = self.width - 1 - len(msg) - len(spin_str) - len(pct_str) \
379
cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
337
380
- len(eta_str) - len(count_str) - 3
340
383
# number of markers highlighted in bar
341
384
markers = int(round(float(cols) *
342
(current_cnt + child_fraction) / total_cnt))
385
(self.last_cnt + self.child_fraction) / self.last_total))
343
386
bar_str = '[' + ('=' * markers).ljust(cols) + '] '
345
388
# don't know total, so can't show completion.
356
m = spin_str + bar_str + msg + count_str + pct_str + eta_str
399
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
358
401
assert len(m) < self.width
359
402
self.to_file.write('\r' + m.ljust(self.width - 1))
367
410
class ChildProgress(_BaseProgressBar):
368
411
"""A progress indicator that pushes its data to the parent"""
369
413
def __init__(self, _stack, **kwargs):
370
414
_BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
371
415
self.parent = _stack.top()
395
439
count = self.current+self.child_fraction
396
440
if count > self.total:
397
mutter('clamping count of %d to %d' % (count, self.total))
442
mutter('clamping count of %d to %d' % (count, self.total))
398
443
count = self.total
399
444
self.parent.child_update(self.message, count, self.total)
449
def note(self, *args, **kwargs):
450
self.parent.note(*args, **kwargs)
405
453
def str_tdelta(delt):