~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

Got merge and revert using nested pbs

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
39
39
import sys
40
40
import time
41
41
import os
42
 
 
43
 
 
44
 
def _width():
45
 
    """Return estimated terminal width.
46
 
 
47
 
    TODO: Do something smart on Windows?
48
 
 
49
 
    TODO: Is there anything that gets a better update when the window
50
 
          is resized while the program is running?
51
 
    """
52
 
    try:
53
 
        return int(os.environ['COLUMNS'])
54
 
    except (IndexError, KeyError, ValueError):
55
 
        return 80
 
42
from collections import deque
 
43
 
 
44
 
 
45
import bzrlib.errors as errors
 
46
from bzrlib.trace import mutter 
56
47
 
57
48
 
58
49
def _supports_progress(f):
74
65
    else:
75
66
        return DotsProgressBar(to_file=to_file, **kwargs)
76
67
    
77
 
    
 
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=False,
 
76
                 show_eta=True,
 
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
 
78
122
class _BaseProgressBar(object):
 
123
 
79
124
    def __init__(self,
80
125
                 to_file=sys.stderr,
81
126
                 show_pct=False,
82
127
                 show_spinner=False,
83
128
                 show_eta=True,
84
129
                 show_bar=True,
85
 
                 show_count=True):
 
130
                 show_count=True,
 
131
                 to_messages_file=sys.stdout,
 
132
                 _stack=None):
86
133
        object.__init__(self)
87
134
        self.to_file = to_file
88
 
 
 
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
97
 
 
 
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)
 
151
 
 
152
    def note(self, fmt_string, *args, **kwargs):
 
153
        """Record a note without disrupting the progress bar."""
 
154
        self.clear()
 
155
        self.to_messages_file.write(fmt_string % args)
 
156
        self.to_messages_file.write('\n')
 
157
 
 
158
    def child_progress(self, **kwargs):
 
159
        return ChildProgress(**kwargs)
98
160
 
99
161
 
100
162
class DummyProgress(_BaseProgressBar):
108
170
    def update(self, msg=None, current=None, total=None):
109
171
        pass
110
172
 
 
173
    def child_update(self, message, current, total):
 
174
        pass
 
175
 
111
176
    def clear(self):
112
177
        pass
113
178
        
114
 
    
 
179
    def note(self, fmt_string, *args, **kwargs):
 
180
        """See _BaseProgressBar.note()."""
 
181
 
 
182
    def child_progress(self, **kwargs):
 
183
        return DummyProgress(**kwargs)
 
184
 
115
185
class DotsProgressBar(_BaseProgressBar):
 
186
 
116
187
    def __init__(self, **kwargs):
117
188
        _BaseProgressBar.__init__(self, **kwargs)
118
189
        self.last_msg = None
135
206
        if self.need_nl:
136
207
            self.to_file.write('\n')
137
208
        
 
209
    def child_update(self, message, current, total):
 
210
        self.tick()
138
211
    
139
212
class TTYProgressBar(_BaseProgressBar):
140
213
    """Progress bar display object.
161
234
 
162
235
 
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
169
245
    
170
246
 
171
247
    def throttle(self):
179
255
            if interval > 0 and interval < self.MIN_PAUSE:
180
256
                return True
181
257
 
 
258
        self.last_updates.append(now - self.last_update)
182
259
        self.last_update = now
183
260
        return False
184
261
        
185
262
 
186
263
    def tick(self):
187
 
        self.update(self.last_msg, self.last_cnt, self.last_total)
188
 
                 
189
 
 
190
 
 
191
 
    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:
 
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):
192
283
        """Update and redraw progress bar."""
 
284
        self.child_fraction = child_fraction
193
285
 
 
286
        if current_cnt < 0:
 
287
            current_cnt = 0
 
288
            
 
289
        if current_cnt > total_cnt:
 
290
            total_cnt = current_cnt
 
291
        
 
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
198
297
            
199
 
        if self.throttle():
 
298
        if old_msg == self.last_msg and self.throttle():
200
299
            return 
201
300
        
202
 
        if total_cnt:
203
 
            assert current_cnt <= total_cnt
204
 
        if current_cnt:
205
 
            assert current_cnt >= 0
206
 
        
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)
210
305
        else:
211
306
            eta_str = ""
219
314
        self.spin_pos += 1
220
315
 
221
316
        if self.show_pct and total_cnt and current_cnt:
222
 
            pct = 100.0 * current_cnt / total_cnt
 
317
            pct = 100.0 * ((current_cnt + child_fraction) / total_cnt)
223
318
            pct_str = ' (%5.1f%%)' % pct
224
319
        else:
225
320
            pct_str = ''
243
338
 
244
339
            if total_cnt:
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) + '] '
248
344
            elif False:
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()
265
361
            
266
 
 
267
362
    def clear(self):        
268
363
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
269
364
        #self.to_file.flush()        
270
 
    
271
 
 
272
 
        
 
365
 
 
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:
 
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 "-:--:--"
279
411
                             delt % 60)
280
412
 
281
413
 
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:
284
416
        return None
285
417
 
301
433
 
302
434
    assert total_duration >= elapsed
303
435
 
 
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)
 
441
 
 
442
        old_time_left = total_duration - elapsed
 
443
 
 
444
        # We could return the average, or some other value here
 
445
        return (time_left + old_time_left) / 2
 
446
 
304
447
    return total_duration - elapsed
305
448
 
306
449