~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Martin Pool
  • Date: 2006-03-10 06:29:53 UTC
  • mfrom: (1608 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1611.
  • Revision ID: mbp@sourcefrog.net-20060310062953-bc1c7ade75c89a7a
[merge] bzr.dev; pycurl not updated for readv yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
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>
3
3
#
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
42
42
from collections import deque
43
43
 
44
44
 
 
45
import bzrlib.errors as errors
 
46
from bzrlib.trace import mutter 
 
47
 
 
48
 
45
49
def _supports_progress(f):
46
50
    if not hasattr(f, 'isatty'):
47
51
        return False
61
65
    else:
62
66
        return DotsProgressBar(to_file=to_file, **kwargs)
63
67
    
64
 
    
 
68
 
 
69
class ProgressBarStack(object):
 
70
    """A stack of progress bars."""
 
71
 
 
72
    def __init__(self,
 
73
                 to_file=sys.stderr,
 
74
                 show_pct=False,
 
75
                 show_spinner=True,
 
76
                 show_eta=False,
 
77
                 show_bar=True,
 
78
                 show_count=True,
 
79
                 to_messages_file=sys.stdout,
 
80
                 klass=None):
 
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
 
89
        self._stack = []
 
90
        self._klass = klass or TTYProgressBar
 
91
 
 
92
    def top(self):
 
93
        if len(self._stack) != 0:
 
94
            return self._stack[-1]
 
95
        else:
 
96
            return None
 
97
 
 
98
    def get_nested(self):
 
99
        """Return a nested progress bar."""
 
100
        if len(self._stack) == 0:
 
101
            func = self._klass
 
102
        else:
 
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,
 
111
                       _stack=self)
 
112
        self._stack.append(new_bar)
 
113
        return new_bar
 
114
 
 
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()
 
119
        self._stack.pop()
 
120
 
 
121
 
65
122
class _BaseProgressBar(object):
 
123
 
66
124
    def __init__(self,
67
125
                 to_file=sys.stderr,
68
126
                 show_pct=False,
70
128
                 show_eta=True,
71
129
                 show_bar=True,
72
130
                 show_count=True,
73
 
                 to_messages_file=sys.stdout):
 
131
                 to_messages_file=sys.stdout,
 
132
                 _stack=None):
74
133
        object.__init__(self)
75
134
        self.to_file = to_file
76
135
        self.to_messages_file = to_messages_file
82
141
        self.show_eta = show_eta
83
142
        self.show_bar = show_bar
84
143
        self.show_count = show_count
 
144
        self._stack = _stack
 
145
 
 
146
    def finished(self):
 
147
        """Return this bar to its progress stack."""
 
148
        self.clear()
 
149
        assert self._stack is not None
 
150
        self._stack.return_pb(self)
85
151
 
86
152
    def note(self, fmt_string, *args, **kwargs):
87
153
        """Record a note without disrupting the progress bar."""
89
155
        self.to_messages_file.write(fmt_string % args)
90
156
        self.to_messages_file.write('\n')
91
157
 
 
158
    def child_progress(self, **kwargs):
 
159
        return ChildProgress(**kwargs)
 
160
 
92
161
 
93
162
class DummyProgress(_BaseProgressBar):
94
163
    """Progress-bar standin that does nothing.
101
170
    def update(self, msg=None, current=None, total=None):
102
171
        pass
103
172
 
 
173
    def child_update(self, message, current, total):
 
174
        pass
 
175
 
104
176
    def clear(self):
105
177
        pass
106
178
        
107
179
    def note(self, fmt_string, *args, **kwargs):
108
180
        """See _BaseProgressBar.note()."""
109
181
 
 
182
    def child_progress(self, **kwargs):
 
183
        return DummyProgress(**kwargs)
110
184
 
111
185
class DotsProgressBar(_BaseProgressBar):
 
186
 
112
187
    def __init__(self, **kwargs):
113
188
        _BaseProgressBar.__init__(self, **kwargs)
114
189
        self.last_msg = None
131
206
        if self.need_nl:
132
207
            self.to_file.write('\n')
133
208
        
 
209
    def child_update(self, message, current, total):
 
210
        self.tick()
134
211
    
135
212
class TTYProgressBar(_BaseProgressBar):
136
213
    """Progress bar display object.
164
241
        self.start_time = None
165
242
        self.last_update = None
166
243
        self.last_updates = deque()
 
244
        self.child_fraction = 0
167
245
    
168
246
 
169
247
    def throttle(self):
183
261
        
184
262
 
185
263
    def tick(self):
186
 
        self.update(self.last_msg, self.last_cnt, self.last_total)
187
 
                 
188
 
 
189
 
 
190
 
    def update(self, msg, current_cnt=None, total_cnt=None):
 
264
        self.update(self.last_msg, self.last_cnt, self.last_total, 
 
265
                    self.child_fraction)
 
266
 
 
267
    def child_update(self, message, current, total):
 
268
        if current is not None and total != 0:
 
269
            child_fraction = float(current) / total
 
270
            if self.last_cnt is None:
 
271
                pass
 
272
            elif self.last_cnt + child_fraction <= self.last_total:
 
273
                self.child_fraction = child_fraction
 
274
            else:
 
275
                mutter('not updating child fraction')
 
276
        if self.last_msg is None:
 
277
            self.last_msg = ''
 
278
        self.tick()
 
279
 
 
280
 
 
281
    def update(self, msg, current_cnt=None, total_cnt=None, 
 
282
               child_fraction=0):
191
283
        """Update and redraw progress bar."""
 
284
        self.child_fraction = child_fraction
192
285
 
193
286
        if current_cnt < 0:
194
287
            current_cnt = 0
206
299
            return 
207
300
        
208
301
        if self.show_eta and self.start_time and total_cnt:
209
 
            eta = get_eta(self.start_time, current_cnt, total_cnt,
210
 
                    last_updates = self.last_updates)
 
302
            eta = get_eta(self.start_time, current_cnt+child_fraction, 
 
303
                    total_cnt, last_updates = self.last_updates)
211
304
            eta_str = " " + str_tdelta(eta)
212
305
        else:
213
306
            eta_str = ""
221
314
        self.spin_pos += 1
222
315
 
223
316
        if self.show_pct and total_cnt and current_cnt:
224
 
            pct = 100.0 * current_cnt / total_cnt
 
317
            pct = 100.0 * ((current_cnt + child_fraction) / total_cnt)
225
318
            pct_str = ' (%5.1f%%)' % pct
226
319
        else:
227
320
            pct_str = ''
245
338
 
246
339
            if total_cnt:
247
340
                # number of markers highlighted in bar
248
 
                markers = int(round(float(cols) * current_cnt / total_cnt))
 
341
                markers = int(round(float(cols) * 
 
342
                              (current_cnt + child_fraction) / total_cnt))
249
343
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
250
344
            elif False:
251
345
                # don't know total, so can't show completion.
269
363
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
270
364
        #self.to_file.flush()        
271
365
 
272
 
        
 
366
 
 
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()
 
372
        self.current = None
 
373
        self.total = None
 
374
        self.child_fraction = 0
 
375
        self.message = None
 
376
 
 
377
    def update(self, msg, current_cnt=None, total_cnt=None):
 
378
        self.current = current_cnt
 
379
        self.total = total_cnt
 
380
        self.message = msg
 
381
        self.child_fraction = 0
 
382
        self.tick()
 
383
 
 
384
    def child_update(self, message, current, total):
 
385
        if current is None or total == 0:
 
386
            self.child_fraction = 0
 
387
        else:
 
388
            self.child_fraction = float(current) / total
 
389
        self.tick()
 
390
 
 
391
    def tick(self):
 
392
        if self.current is None:
 
393
            count = None
 
394
        else:
 
395
            count = self.current+self.child_fraction
 
396
            if count > self.total:
 
397
                mutter('clamping count of %d to %d' % (count, self.total))
 
398
                count = self.total
 
399
        self.parent.child_update(self.message, count, self.total)
 
400
 
 
401
    def clear(self):
 
402
        pass
 
403
 
 
404
 
273
405
def str_tdelta(delt):
274
406
    if delt is None:
275
407
        return "-:--:--"
315
447
    return total_duration - elapsed
316
448
 
317
449
 
 
450
class ProgressPhase(object):
 
451
    """Update progress object with the current phase"""
 
452
    def __init__(self, message, total, pb):
 
453
        object.__init__(self)
 
454
        self.pb = pb
 
455
        self.message = message
 
456
        self.total = total
 
457
        self.cur_phase = None
 
458
 
 
459
    def next_phase(self):
 
460
        if self.cur_phase is None:
 
461
            self.cur_phase = 0
 
462
        else:
 
463
            self.cur_phase += 1
 
464
        assert self.cur_phase < self.total 
 
465
        self.pb.update(self.message, self.cur_phase, self.total)
 
466
 
 
467
 
318
468
def run_tests():
319
469
    import doctest
320
470
    result = doctest.testmod()