~bzr-pqm/bzr/bzr.dev

649 by Martin Pool
- some cleanups for the progressbar method
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
2
# Copyright (C) 2005, 2006 Canonical Ltd
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
648 by Martin Pool
- import aaron's progress-indicator code
17
649 by Martin Pool
- some cleanups for the progressbar method
18
3006.3.3 by Robert Collins
Docstring improvement and remove TODO's from progres.py.
19
"""Progress indicators.
20
21
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
22
will maintain a ProgressBarStack for you.
23
24
For direct use, the factory ProgressBar will return an auto-detected progress
25
bar that should match your terminal type. You can manually create a
26
ProgressBarStack too if you need multiple levels of cooperating progress bars.
27
Note that bzrlib's internal functions use the ui module, so if you are using
28
bzrlib it really is best to use bzrlib.ui.ui_factory.
649 by Martin Pool
- some cleanups for the progressbar method
29
"""
30
934 by Martin Pool
todo
31
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
32
# when the rate is unpredictable
33
648 by Martin Pool
- import aaron's progress-indicator code
34
import sys
660 by Martin Pool
- use plain unix time, not datetime module
35
import time
964 by Martin Pool
- show progress on dumb terminals by printing dots
36
import os
649 by Martin Pool
- some cleanups for the progressbar method
37
1996.3.32 by John Arbash Meinel
from bzrlib.ui lazy import progress, and make progress import lazily
38
from bzrlib.lazy_import import lazy_import
39
lazy_import(globals(), """
40
from bzrlib import (
41
    errors,
42
    )
43
""")
44
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
45
from bzrlib.trace import mutter
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
46
47
649 by Martin Pool
- some cleanups for the progressbar method
48
def _supports_progress(f):
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
49
    """Detect if we can use pretty progress bars on the output stream f.
50
51
    If this returns true we expect that a human may be looking at that 
52
    output, and that we can repaint a line to update it.
53
    """
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
54
    isatty = getattr(f, 'isatty', None)
55
    if isatty is None:
695 by Martin Pool
- don't display progress bars on really dumb terminals
56
        return False
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
57
    if not isatty():
695 by Martin Pool
- don't display progress bars on really dumb terminals
58
        return False
59
    if os.environ.get('TERM') == 'dumb':
60
        # e.g. emacs compile window
61
        return False
62
    return True
649 by Martin Pool
- some cleanups for the progressbar method
63
64
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
65
_progress_bar_types = {}
66
649 by Martin Pool
- some cleanups for the progressbar method
67
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
68
def ProgressBar(to_file=None, **kwargs):
964 by Martin Pool
- show progress on dumb terminals by printing dots
69
    """Abstract factory"""
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
70
    if to_file is None:
71
        to_file = sys.stderr
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
72
    requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
73
    # An value of '' or not set reverts to standard processing
74
    if requested_bar_type in (None, ''):
75
        if _supports_progress(to_file):
76
            return TTYProgressBar(to_file=to_file, **kwargs)
77
        else:
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
78
            return DummyProgress(to_file=to_file, **kwargs)
964 by Martin Pool
- show progress on dumb terminals by printing dots
79
    else:
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
80
        # Minor sanitation to prevent spurious errors
81
        requested_bar_type = requested_bar_type.lower().strip()
82
        # TODO: jam 20060710 Arguably we shouldn't raise an exception
83
        #       but should instead just disable progress bars if we
84
        #       don't recognize the type
85
        if requested_bar_type not in _progress_bar_types:
86
            raise errors.InvalidProgressBarType(requested_bar_type,
87
                                                _progress_bar_types.keys())
88
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
89
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
90
 
91
class ProgressBarStack(object):
92
    """A stack of progress bars."""
93
94
    def __init__(self,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
95
                 to_file=None,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
96
                 show_pct=False,
1551.2.33 by Aaron Bentley
Hide ETA, show spinner by default
97
                 show_spinner=True,
98
                 show_eta=False,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
99
                 show_bar=True,
100
                 show_count=True,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
101
                 to_messages_file=None,
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
102
                 klass=None):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
103
        """Setup the stack with the parameters the progress bars should have."""
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
104
        if to_file is None:
105
            to_file = sys.stderr
106
        if to_messages_file is None:
107
            to_messages_file = sys.stdout
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
108
        self._to_file = to_file
109
        self._show_pct = show_pct
110
        self._show_spinner = show_spinner
111
        self._show_eta = show_eta
112
        self._show_bar = show_bar
113
        self._show_count = show_count
114
        self._to_messages_file = to_messages_file
115
        self._stack = []
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
116
        self._klass = klass or ProgressBar
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
117
1551.2.29 by Aaron Bentley
Got stack handling under test
118
    def top(self):
119
        if len(self._stack) != 0:
120
            return self._stack[-1]
121
        else:
122
            return None
123
1558.8.1 by Aaron Bentley
Fix overall progress bar's interaction with 'note' and 'warning'
124
    def bottom(self):
125
        if len(self._stack) != 0:
126
            return self._stack[0]
127
        else:
128
            return None
129
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
130
    def get_nested(self):
131
        """Return a nested progress bar."""
1551.2.29 by Aaron Bentley
Got stack handling under test
132
        if len(self._stack) == 0:
133
            func = self._klass
134
        else:
135
            func = self.top().child_progress
136
        new_bar = func(to_file=self._to_file,
137
                       show_pct=self._show_pct,
138
                       show_spinner=self._show_spinner,
139
                       show_eta=self._show_eta,
140
                       show_bar=self._show_bar,
141
                       show_count=self._show_count,
142
                       to_messages_file=self._to_messages_file,
143
                       _stack=self)
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
144
        self._stack.append(new_bar)
145
        return new_bar
146
147
    def return_pb(self, bar):
148
        """Return bar after its been used."""
1594.1.4 by Robert Collins
Fix identity test in ProgressBarStack.return_pb
149
        if bar is not self._stack[-1]:
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
150
            raise errors.MissingProgressBarFinish()
151
        self._stack.pop()
152
153
 
964 by Martin Pool
- show progress on dumb terminals by printing dots
154
class _BaseProgressBar(object):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
155
964 by Martin Pool
- show progress on dumb terminals by printing dots
156
    def __init__(self,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
157
                 to_file=None,
964 by Martin Pool
- show progress on dumb terminals by printing dots
158
                 show_pct=False,
159
                 show_spinner=False,
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
160
                 show_eta=False,
964 by Martin Pool
- show progress on dumb terminals by printing dots
161
                 show_bar=True,
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
162
                 show_count=True,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
163
                 to_messages_file=None,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
164
                 _stack=None):
964 by Martin Pool
- show progress on dumb terminals by printing dots
165
        object.__init__(self)
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
166
        if to_file is None:
167
            to_file = sys.stderr
168
        if to_messages_file is None:
169
            to_messages_file = sys.stdout
964 by Martin Pool
- show progress on dumb terminals by printing dots
170
        self.to_file = to_file
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
171
        self.to_messages_file = to_messages_file
964 by Martin Pool
- show progress on dumb terminals by printing dots
172
        self.last_msg = None
173
        self.last_cnt = None
174
        self.last_total = None
175
        self.show_pct = show_pct
176
        self.show_spinner = show_spinner
177
        self.show_eta = show_eta
178
        self.show_bar = show_bar
179
        self.show_count = show_count
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
180
        self._stack = _stack
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
181
        # seed throttler
182
        self.MIN_PAUSE = 0.1 # seconds
2120.1.1 by John Arbash Meinel
Use time.time() because time.clock() is CPU time, not wall time
183
        now = time.time()
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
184
        # starting now
2745.6.52 by Andrew Bennetts
Revert bad change to bzrlib/progress.py
185
        self.start_time = now
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
186
        # next update should not throttle
187
        self.last_update = now - self.MIN_PAUSE - 1
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
188
189
    def finished(self):
190
        """Return this bar to its progress stack."""
191
        self.clear()
192
        self._stack.return_pb(self)
1104 by Martin Pool
- Add a simple UIFactory
193
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
194
    def note(self, fmt_string, *args, **kwargs):
195
        """Record a note without disrupting the progress bar."""
1558.8.5 by Aaron Bentley
Pass note up the stack instead of using bzrlib.ui_factory
196
        self.clear()
1558.7.9 by Aaron Bentley
Bad change. (broke tests). Reverted.
197
        self.to_messages_file.write(fmt_string % args)
198
        self.to_messages_file.write('\n')
1104 by Martin Pool
- Add a simple UIFactory
199
1551.2.29 by Aaron Bentley
Got stack handling under test
200
    def child_progress(self, **kwargs):
201
        return ChildProgress(**kwargs)
202
1534.11.7 by Robert Collins
Test and correct the problem with nested test logs breaking further in-test logs.
203
1104 by Martin Pool
- Add a simple UIFactory
204
class DummyProgress(_BaseProgressBar):
205
    """Progress-bar standin that does nothing.
206
207
    This can be used as the default argument for methods that
208
    take an optional progress indicator."""
209
    def tick(self):
210
        pass
211
212
    def update(self, msg=None, current=None, total=None):
213
        pass
214
1551.2.27 by Aaron Bentley
Got propogation under test
215
    def child_update(self, message, current, total):
216
        pass
217
1104 by Martin Pool
- Add a simple UIFactory
218
    def clear(self):
219
        pass
964 by Martin Pool
- show progress on dumb terminals by printing dots
220
        
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
221
    def note(self, fmt_string, *args, **kwargs):
222
        """See _BaseProgressBar.note()."""
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
223
1551.2.29 by Aaron Bentley
Got stack handling under test
224
    def child_progress(self, **kwargs):
225
        return DummyProgress(**kwargs)
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
226
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
227
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
228
_progress_bar_types['dummy'] = DummyProgress
229
_progress_bar_types['none'] = DummyProgress
230
231
964 by Martin Pool
- show progress on dumb terminals by printing dots
232
class DotsProgressBar(_BaseProgressBar):
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
233
964 by Martin Pool
- show progress on dumb terminals by printing dots
234
    def __init__(self, **kwargs):
235
        _BaseProgressBar.__init__(self, **kwargs)
236
        self.last_msg = None
237
        self.need_nl = False
238
        
239
    def tick(self):
240
        self.update()
241
        
242
    def update(self, msg=None, current_cnt=None, total_cnt=None):
243
        if msg and msg != self.last_msg:
244
            if self.need_nl:
245
                self.to_file.write('\n')
246
            self.to_file.write(msg + ': ')
247
            self.last_msg = msg
248
        self.need_nl = True
249
        self.to_file.write('.')
250
        
251
    def clear(self):
252
        if self.need_nl:
253
            self.to_file.write('\n')
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
254
        self.need_nl = False
964 by Martin Pool
- show progress on dumb terminals by printing dots
255
        
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
256
    def child_update(self, message, current, total):
257
        self.tick()
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
258
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
259
260
_progress_bar_types['dots'] = DotsProgressBar
261
964 by Martin Pool
- show progress on dumb terminals by printing dots
262
    
263
class TTYProgressBar(_BaseProgressBar):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
264
    """Progress bar display object.
265
266
    Several options are available to control the display.  These can
267
    be passed as parameters to the constructor or assigned at any time:
268
269
    show_pct
270
        Show percentage complete.
271
    show_spinner
272
        Show rotating baton.  This ticks over on every update even
273
        if the values don't change.
274
    show_eta
275
        Show predicted time-to-completion.
276
    show_bar
277
        Show bar graph.
278
    show_count
279
        Show numerical counts.
280
281
    The output file should be in line-buffered or unbuffered mode.
282
    """
283
    SPIN_CHARS = r'/-\|'
661 by Martin Pool
- limit rate at which progress bar is updated
284
964 by Martin Pool
- show progress on dumb terminals by printing dots
285
286
    def __init__(self, **kwargs):
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
287
        from bzrlib.osutils import terminal_width
964 by Martin Pool
- show progress on dumb terminals by printing dots
288
        _BaseProgressBar.__init__(self, **kwargs)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
289
        self.spin_pos = 0
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
290
        self.width = terminal_width()
1843.3.3 by John Arbash Meinel
Don't let the last_updates list grow without bound.
291
        self.last_updates = []
1843.3.4 by John Arbash Meinel
Remove get_eta's ability to modify last_updates.
292
        self._max_last_updates = 10
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
293
        self.child_fraction = 0
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
294
        self._have_output = False
964 by Martin Pool
- show progress on dumb terminals by printing dots
295
    
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
296
    def throttle(self, old_msg):
964 by Martin Pool
- show progress on dumb terminals by printing dots
297
        """Return True if the bar was updated too recently"""
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
298
        # time.time consistently takes 40/4000 ms = 0.01 ms.
2120.1.1 by John Arbash Meinel
Use time.time() because time.clock() is CPU time, not wall time
299
        # time.clock() is faster, but gives us CPU time, not wall-clock time
300
        now = time.time()
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
301
        if self.start_time is not None and (now - self.start_time) < 1:
302
            return True
303
        if old_msg != self.last_msg:
304
            return False
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
305
        interval = now - self.last_update
306
        # if interval > 0
307
        if interval < self.MIN_PAUSE:
308
            return True
964 by Martin Pool
- show progress on dumb terminals by printing dots
309
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
310
        self.last_updates.append(now - self.last_update)
1843.3.3 by John Arbash Meinel
Don't let the last_updates list grow without bound.
311
        # Don't let the queue grow without bound
312
        self.last_updates = self.last_updates[-self._max_last_updates:]
964 by Martin Pool
- show progress on dumb terminals by printing dots
313
        self.last_update = now
314
        return False
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
315
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
316
    def tick(self):
3006.3.2 by Robert Collins
More formatting corrections.
317
        self.update(self.last_msg, self.last_cnt, self.last_total,
1551.2.27 by Aaron Bentley
Got propogation under test
318
                    self.child_fraction)
319
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
320
    def child_update(self, message, current, total):
1551.2.35 by Aaron Bentley
Fix division-by-zero
321
        if current is not None and total != 0:
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
322
            child_fraction = float(current) / total
323
            if self.last_cnt is None:
324
                pass
325
            elif self.last_cnt + child_fraction <= self.last_total:
326
                self.child_fraction = child_fraction
327
        if self.last_msg is None:
328
            self.last_msg = ''
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
329
        self.tick()
330
3006.3.1 by Robert Collins
Minor PEP8 changes.
331
    def update(self, msg, current_cnt=None, total_cnt=None,
1551.2.27 by Aaron Bentley
Got propogation under test
332
               child_fraction=0):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
333
        """Update and redraw progress bar."""
1534.11.1 by Robert Collins
Teach bzr selftest to use a progress bar in non verbose mode.
334
        if msg is None:
335
            msg = self.last_msg
336
337
        if total_cnt is None:
338
            total_cnt = self.last_total
339
1308 by Martin Pool
- make progress bar more tolerant of out-of-range values
340
        if current_cnt < 0:
341
            current_cnt = 0
342
            
343
        if current_cnt > total_cnt:
344
            total_cnt = current_cnt
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
345
        
1596.2.17 by Robert Collins
Notes on further progress tuning.
346
        ## # optional corner case optimisation 
347
        ## # currently does not seem to fire so costs more than saved.
348
        ## # trivial optimal case:
349
        ## # NB if callers are doing a clear and restore with
350
        ## # the saved values, this will prevent that:
351
        ## # in that case add a restore method that calls
352
        ## # _do_update or some such
353
        ## if (self.last_msg == msg and
354
        ##     self.last_cnt == current_cnt and
355
        ##     self.last_total == total_cnt and
356
        ##     self.child_fraction == child_fraction):
357
        ##     return
358
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
359
        old_msg = self.last_msg
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
360
        # save these for the tick() function
361
        self.last_msg = msg
362
        self.last_cnt = current_cnt
363
        self.last_total = total_cnt
1596.2.17 by Robert Collins
Notes on further progress tuning.
364
        self.child_fraction = child_fraction
365
366
        # each function call takes 20ms/4000 = 0.005 ms, 
367
        # but multiple that by 4000 calls -> starts to cost.
368
        # so anything to make this function call faster
369
        # will improve base 'diff' time by up to 0.1 seconds.
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
370
        if self.throttle(old_msg):
1596.2.17 by Robert Collins
Notes on further progress tuning.
371
            return
372
373
        if self.show_eta and self.start_time and self.last_total:
374
            eta = get_eta(self.start_time, self.last_cnt + self.child_fraction, 
375
                    self.last_total, last_updates = self.last_updates)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
376
            eta_str = " " + str_tdelta(eta)
377
        else:
378
            eta_str = ""
379
380
        if self.show_spinner:
381
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
382
        else:
383
            spin_str = ''
384
385
        # always update this; it's also used for the bar
386
        self.spin_pos += 1
387
1596.2.17 by Robert Collins
Notes on further progress tuning.
388
        if self.show_pct and self.last_total and self.last_cnt:
389
            pct = 100.0 * ((self.last_cnt + self.child_fraction) / self.last_total)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
390
            pct_str = ' (%5.1f%%)' % pct
391
        else:
392
            pct_str = ''
393
394
        if not self.show_count:
395
            count_str = ''
1596.2.17 by Robert Collins
Notes on further progress tuning.
396
        elif self.last_cnt is None:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
397
            count_str = ''
1596.2.17 by Robert Collins
Notes on further progress tuning.
398
        elif self.last_total is None:
399
            count_str = ' %i' % (self.last_cnt)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
400
        else:
401
            # make both fields the same size
1596.2.17 by Robert Collins
Notes on further progress tuning.
402
            t = '%i' % (self.last_total)
403
            c = '%*i' % (len(t), self.last_cnt)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
404
            count_str = ' ' + c + '/' + t 
405
406
        if self.show_bar:
407
            # progress bar, if present, soaks up all remaining space
1596.2.17 by Robert Collins
Notes on further progress tuning.
408
            cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
409
                   - len(eta_str) - len(count_str) - 3
410
1596.2.17 by Robert Collins
Notes on further progress tuning.
411
            if self.last_total:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
412
                # number of markers highlighted in bar
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
413
                markers = int(round(float(cols) * 
1596.2.17 by Robert Collins
Notes on further progress tuning.
414
                              (self.last_cnt + self.child_fraction) / self.last_total))
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
415
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
416
            elif False:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
417
                # don't know total, so can't show completion.
418
                # so just show an expanded spinning thingy
419
                m = self.spin_pos % cols
668 by Martin Pool
- fix sweeping bar progress indicator
420
                ms = (' ' * m + '*').ljust(cols)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
421
                
422
                bar_str = '[' + ms + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
423
            else:
424
                bar_str = ''
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
425
        else:
426
            bar_str = ''
427
1596.2.17 by Robert Collins
Notes on further progress tuning.
428
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
2095.4.4 by mbp at sourcefrog
Truncate progress bar rather than complaining if it's too long
429
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
430
        self._have_output = True
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
431
        #self.to_file.flush()
432
            
3006.3.2 by Robert Collins
More formatting corrections.
433
    def clear(self):
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
434
        if self._have_output:
435
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
436
        self._have_output = False
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
437
        #self.to_file.flush()        
649 by Martin Pool
- some cleanups for the progressbar method
438
1551.2.27 by Aaron Bentley
Got propogation under test
439
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
440
_progress_bar_types['tty'] = TTYProgressBar
441
442
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
443
class ChildProgress(_BaseProgressBar):
1551.2.27 by Aaron Bentley
Got propogation under test
444
    """A progress indicator that pushes its data to the parent"""
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
445
1551.2.29 by Aaron Bentley
Got stack handling under test
446
    def __init__(self, _stack, **kwargs):
447
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
448
        self.parent = _stack.top()
1551.2.27 by Aaron Bentley
Got propogation under test
449
        self.current = None
450
        self.total = None
451
        self.child_fraction = 0
452
        self.message = None
453
454
    def update(self, msg, current_cnt=None, total_cnt=None):
455
        self.current = current_cnt
2592.6.11 by Robert Collins
* A progress bar has been added for knitpack -> knitpack fetching.
456
        if total_cnt is not None:
457
            self.total = total_cnt
1551.2.27 by Aaron Bentley
Got propogation under test
458
        self.message = msg
459
        self.child_fraction = 0
460
        self.tick()
461
462
    def child_update(self, message, current, total):
1551.2.35 by Aaron Bentley
Fix division-by-zero
463
        if current is None or total == 0:
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
464
            self.child_fraction = 0
465
        else:
466
            self.child_fraction = float(current) / total
1551.2.27 by Aaron Bentley
Got propogation under test
467
        self.tick()
468
469
    def tick(self):
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
470
        if self.current is None:
471
            count = None
472
        else:
473
            count = self.current+self.child_fraction
474
            if count > self.total:
1596.2.35 by Robert Collins
Subclass SequenceMatcher to get a slightly faster (in our case) find_longest_match routine.
475
                if __debug__:
476
                    mutter('clamping count of %d to %d' % (count, self.total))
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
477
                count = self.total
1551.2.27 by Aaron Bentley
Got propogation under test
478
        self.parent.child_update(self.message, count, self.total)
479
1551.2.29 by Aaron Bentley
Got stack handling under test
480
    def clear(self):
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
481
        pass
1551.2.29 by Aaron Bentley
Got stack handling under test
482
1558.8.6 by Aaron Bentley
Fix note implementation
483
    def note(self, *args, **kwargs):
1558.8.5 by Aaron Bentley
Pass note up the stack instead of using bzrlib.ui_factory
484
        self.parent.note(*args, **kwargs)
485
3146.6.1 by Aaron Bentley
InterDifferingSerializer shows a progress bar
486
487
class InstrumentedProgress(TTYProgressBar):
488
    """TTYProgress variant that tracks outcomes"""
489
490
    def __init__(self, *args, **kwargs):
491
        self.always_throttled = True
492
        self.never_throttle = False
493
        TTYProgressBar.__init__(self, *args, **kwargs)
494
495
    def throttle(self, old_message):
496
        if self.never_throttle:
497
            result =  False
498
        else:
499
            result = TTYProgressBar.throttle(self, old_message)
500
        if result is False:
501
            self.always_throttled = False
502
503
648 by Martin Pool
- import aaron's progress-indicator code
504
def str_tdelta(delt):
505
    if delt is None:
506
        return "-:--:--"
660 by Martin Pool
- use plain unix time, not datetime module
507
    delt = int(round(delt))
508
    return '%d:%02d:%02d' % (delt/3600,
509
                             (delt/60) % 60,
510
                             delt % 60)
511
512
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
513
def get_eta(start_time, current, total, enough_samples=3, last_updates=None, n_recent=10):
660 by Martin Pool
- use plain unix time, not datetime module
514
    if start_time is None:
515
        return None
516
517
    if not total:
518
        return None
519
520
    if current < enough_samples:
521
        return None
522
523
    if current > total:
524
        return None                     # wtf?
525
2120.1.1 by John Arbash Meinel
Use time.time() because time.clock() is CPU time, not wall time
526
    elapsed = time.time() - start_time
660 by Martin Pool
- use plain unix time, not datetime module
527
528
    if elapsed < 2.0:                   # not enough time to estimate
529
        return None
530
    
531
    total_duration = float(elapsed) * float(total) / float(current)
532
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
533
    if last_updates and len(last_updates) >= n_recent:
534
        avg = sum(last_updates) / float(len(last_updates))
535
        time_left = avg * (total - current)
536
537
        old_time_left = total_duration - elapsed
538
539
        # We could return the average, or some other value here
540
        return (time_left + old_time_left) / 2
541
660 by Martin Pool
- use plain unix time, not datetime module
542
    return total_duration - elapsed
648 by Martin Pool
- import aaron's progress-indicator code
543
649 by Martin Pool
- some cleanups for the progressbar method
544
1551.2.32 by Aaron Bentley
Handle progress phases more nicely in merge
545
class ProgressPhase(object):
546
    """Update progress object with the current phase"""
547
    def __init__(self, message, total, pb):
548
        object.__init__(self)
549
        self.pb = pb
550
        self.message = message
551
        self.total = total
552
        self.cur_phase = None
553
554
    def next_phase(self):
555
        if self.cur_phase is None:
556
            self.cur_phase = 0
557
        else:
558
            self.cur_phase += 1
559
        self.pb.update(self.message, self.cur_phase, self.total)