/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
649 by Martin Pool
- some cleanups for the progressbar method
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
2
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
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
648 by Martin Pool
- import aaron's progress-indicator code
32
import sys
660 by Martin Pool
- use plain unix time, not datetime module
33
import time
964 by Martin Pool
- show progress on dumb terminals by printing dots
34
import os
649 by Martin Pool
- some cleanups for the progressbar method
35
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
36
1996.3.32 by John Arbash Meinel
from bzrlib.ui lazy import progress, and make progress import lazily
37
from bzrlib import (
38
    errors,
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
39
    osutils,
40
    trace,
41
    ui,
1996.3.32 by John Arbash Meinel
from bzrlib.ui lazy import progress, and make progress import lazily
42
    )
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
43
from bzrlib.trace import mutter
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
44
45
649 by Martin Pool
- some cleanups for the progressbar method
46
def _supports_progress(f):
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
47
    """Detect if we can use pretty progress bars on the output stream f.
48
49
    If this returns true we expect that a human may be looking at that 
50
    output, and that we can repaint a line to update it.
51
    """
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
52
    isatty = getattr(f, 'isatty', None)
53
    if isatty is None:
695 by Martin Pool
- don't display progress bars on really dumb terminals
54
        return False
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
55
    if not isatty():
695 by Martin Pool
- don't display progress bars on really dumb terminals
56
        return False
57
    if os.environ.get('TERM') == 'dumb':
58
        # e.g. emacs compile window
59
        return False
60
    return True
649 by Martin Pool
- some cleanups for the progressbar method
61
62
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
63
class ProgressTask(object):
64
    """Model component of a progress indicator.
65
66
    Most code that needs to indicate progress should update one of these, 
67
    and it will in turn update the display, if one is present.
68
    """
69
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
70
    def __init__(self, parent_task=None, ui_factory=None):
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
71
        self._parent_task = parent_task
72
        self._last_update = 0
73
        self.total_cnt = None
74
        self.current_cnt = None
75
        self.msg = ''
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
76
        self.ui_factory = ui_factory
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
77
78
    def update(self, msg, current_cnt=None, total_cnt=None):
79
        self.msg = msg
80
        self.current_cnt = current_cnt
81
        if total_cnt:
82
            self.total_cnt = total_cnt
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
83
        self.ui_factory.show_progress(self)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
84
85
    def finished(self):
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
86
        self.ui_factory.progress_finished(self)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
87
88
    def make_sub_task(self):
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
89
        return ProgressTask(self, self.ui_factory)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
90
91
    def _overall_completion_fraction(self, child_fraction=0.0):
92
        """Return fractional completion of this task and its parents
93
        
94
        Returns None if no completion can be computed."""
95
        if self.total_cnt:
96
            own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
97
        else:
98
            own_fraction = None
99
        if self._parent_task is None:
100
            return own_fraction
101
        else:
102
            if own_fraction is None:
103
                own_fraction = 0.0
104
            return self._parent_task._overall_completion_fraction(own_fraction)
105
106
    def note(self, fmt_string, *args, **kwargs):
107
        """Record a note without disrupting the progress bar."""
108
        # XXX: shouldn't be here; put it in mutter or the ui instead
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
109
        self.ui_factory.clear_term()
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
110
        trace.note(fmt_string % args)
111
112
    def clear(self):
113
        # XXX: shouldn't be here; put it in mutter or the ui instead
3882.8.2 by Martin Pool
ProgressTask holds a reference to the ui that displays it
114
        self.ui_factory.clear_term()
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
115
649 by Martin Pool
- some cleanups for the progressbar method
116
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
117
def ProgressBar(to_file=None, **kwargs):
964 by Martin Pool
- show progress on dumb terminals by printing dots
118
    """Abstract factory"""
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
119
    if to_file is None:
120
        to_file = sys.stderr
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
121
    requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
122
    # An value of '' or not set reverts to standard processing
123
    if requested_bar_type in (None, ''):
124
        if _supports_progress(to_file):
125
            return TTYProgressBar(to_file=to_file, **kwargs)
126
        else:
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
127
            return DummyProgress(to_file=to_file, **kwargs)
964 by Martin Pool
- show progress on dumb terminals by printing dots
128
    else:
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
129
        # Minor sanitation to prevent spurious errors
130
        requested_bar_type = requested_bar_type.lower().strip()
131
        # TODO: jam 20060710 Arguably we shouldn't raise an exception
132
        #       but should instead just disable progress bars if we
133
        #       don't recognize the type
134
        if requested_bar_type not in _progress_bar_types:
135
            raise errors.InvalidProgressBarType(requested_bar_type,
136
                                                _progress_bar_types.keys())
137
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
138
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
139
140
141
class TextProgressView(object):
142
    """Display of progress bar and other information on a tty.
143
    
144
    This shows one line of text, including possibly a network indicator, spinner, 
145
    progress bar, message, etc.
146
147
    One instance of this is created and held by the UI, and fed updates when a
148
    task wants to be painted.
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
149
150
    Transports feed data to this through the ui_factory object.
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
151
    """
152
153
    def __init__(self, term_file):
154
        self._term_file = term_file
155
        # true when there's output on the screen we may need to clear
156
        self._have_output = False
3882.8.1 by Martin Pool
Remove experimental transport display from TTYProgressBar
157
        # XXX: We could listen for SIGWINCH and update the terminal width...
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
158
        self._width = osutils.terminal_width()
159
        self._last_transport_msg = ''
160
        self._spin_pos = 0
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
161
        # time we last repainted the screen
162
        self._last_repaint = 0
163
        # time we last got information about transport activity
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
164
        self._transport_update_time = 0
165
        self._task_fraction = None
3882.7.8 by Martin Pool
Progress layout tweaks
166
        self._last_task = None
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
167
        self._total_byte_count = 0
168
        self._bytes_since_update = 0
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
169
170
    def _show_line(self, s):
171
        n = self._width - 1
172
        self._term_file.write('\r%-*.*s\r' % (n, n, s))
173
174
    def clear(self):
175
        if self._have_output:
176
            self._show_line('')
177
        self._have_output = False
178
179
    def _render_bar(self):
180
        # return a string for the progress bar itself
3882.7.8 by Martin Pool
Progress layout tweaks
181
        spin_str =  r'/-\|'[self._spin_pos % 4]
182
        self._spin_pos += 1
183
        f = self._task_fraction or 0
184
        cols = 20
185
        # number of markers highlighted in bar
186
        markers = int(round(float(cols) * f)) - 1
187
        bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
188
        return bar_str
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
189
190
    def _format_task(self, task):
191
        if task.total_cnt is not None:
192
            s = ' %d/%d' % (task.current_cnt, task.total_cnt)
193
        elif task.current_cnt is not None:
194
            s = ' %d' % (task.current_cnt)
195
        else:
196
            s = ''
197
        self._task_fraction = task._overall_completion_fraction()
198
        # compose all the parent messages
199
        t = task
200
        m = task.msg
201
        while t._parent_task:
202
            t = t._parent_task
203
            if t.msg:
204
                m = t.msg + ':' + m
205
        return m + s
206
207
    def _repaint(self):
208
        bar_string = self._render_bar()
3882.7.8 by Martin Pool
Progress layout tweaks
209
        if self._last_task:
210
            task_msg = self._format_task(self._last_task)
211
        else:
212
            task_msg = ''
213
        trans = self._last_transport_msg
214
        if trans:
215
            trans += ' | '
216
        s = (bar_string
217
             + trans
218
             + task_msg
219
             )
220
        self._show_line(s)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
221
        self._have_output = True
222
223
    def show_progress(self, task):
3882.7.8 by Martin Pool
Progress layout tweaks
224
        self._last_task = task
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
225
        now = time.time()
226
        if now < self._last_repaint + 0.1:
227
            return
228
        if now > self._transport_update_time + 5:
229
            # no recent activity; expire it
230
            self._last_transport_msg = ''
231
        self._last_repaint = now
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
232
        self._repaint()
233
3882.8.3 by Martin Pool
Move display of transport throughput into TextProgressView
234
    def show_transport_activity(self, byte_count):
235
        """Called by transports as they do IO.
236
        
237
        This may update a progress bar, spinner, or similar display.
238
        By default it does nothing.
239
        """
240
        # XXX: Probably there should be a transport activity model, and that
241
        # too should be seen by the progress view, rather than being poked in
242
        # here.
243
        self._total_byte_count += byte_count
244
        self._bytes_since_update += byte_count
245
        now = time.time()
246
        if self._transport_update_time is None:
247
            self._transport_update_time = now
248
        elif now >= (self._transport_update_time + 0.2):
249
            # guard against clock stepping backwards, and don't update too
250
            # often
251
            rate = self._bytes_since_update / (now - self._transport_update_time)
252
            msg = ("%6dkB @ %4dkB/s" %
253
                (self._total_byte_count>>10, int(rate)>>10,))
254
            self._transport_update_time = now
255
            self._last_repaint = now
256
            self._bytes_since_update = 0
257
            self._last_transport_msg = msg
258
            self._repaint()
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
259
260
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
261
class ProgressBarStack(object):
262
    """A stack of progress bars."""
263
264
    def __init__(self,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
265
                 to_file=None,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
266
                 show_pct=False,
1551.2.33 by Aaron Bentley
Hide ETA, show spinner by default
267
                 show_spinner=True,
268
                 show_eta=False,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
269
                 show_bar=True,
270
                 show_count=True,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
271
                 to_messages_file=None,
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
272
                 klass=None):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
273
        """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
274
        if to_file is None:
275
            to_file = sys.stderr
276
        if to_messages_file is None:
277
            to_messages_file = sys.stdout
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
278
        self._to_file = to_file
279
        self._show_pct = show_pct
280
        self._show_spinner = show_spinner
281
        self._show_eta = show_eta
282
        self._show_bar = show_bar
283
        self._show_count = show_count
284
        self._to_messages_file = to_messages_file
285
        self._stack = []
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
286
        self._klass = klass or ProgressBar
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
287
1551.2.29 by Aaron Bentley
Got stack handling under test
288
    def top(self):
289
        if len(self._stack) != 0:
290
            return self._stack[-1]
291
        else:
292
            return None
293
1558.8.1 by Aaron Bentley
Fix overall progress bar's interaction with 'note' and 'warning'
294
    def bottom(self):
295
        if len(self._stack) != 0:
296
            return self._stack[0]
297
        else:
298
            return None
299
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
300
    def get_nested(self):
301
        """Return a nested progress bar."""
1551.2.29 by Aaron Bentley
Got stack handling under test
302
        if len(self._stack) == 0:
303
            func = self._klass
304
        else:
305
            func = self.top().child_progress
306
        new_bar = func(to_file=self._to_file,
307
                       show_pct=self._show_pct,
308
                       show_spinner=self._show_spinner,
309
                       show_eta=self._show_eta,
310
                       show_bar=self._show_bar,
311
                       show_count=self._show_count,
312
                       to_messages_file=self._to_messages_file,
313
                       _stack=self)
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
314
        self._stack.append(new_bar)
315
        return new_bar
316
317
    def return_pb(self, bar):
318
        """Return bar after its been used."""
1594.1.4 by Robert Collins
Fix identity test in ProgressBarStack.return_pb
319
        if bar is not self._stack[-1]:
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
320
            raise errors.MissingProgressBarFinish()
321
        self._stack.pop()
322
323
 
964 by Martin Pool
- show progress on dumb terminals by printing dots
324
class _BaseProgressBar(object):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
325
964 by Martin Pool
- show progress on dumb terminals by printing dots
326
    def __init__(self,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
327
                 to_file=None,
964 by Martin Pool
- show progress on dumb terminals by printing dots
328
                 show_pct=False,
329
                 show_spinner=False,
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
330
                 show_eta=False,
964 by Martin Pool
- show progress on dumb terminals by printing dots
331
                 show_bar=True,
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
332
                 show_count=True,
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
333
                 to_messages_file=None,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
334
                 _stack=None):
964 by Martin Pool
- show progress on dumb terminals by printing dots
335
        object.__init__(self)
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
336
        if to_file is None:
337
            to_file = sys.stderr
338
        if to_messages_file is None:
339
            to_messages_file = sys.stdout
964 by Martin Pool
- show progress on dumb terminals by printing dots
340
        self.to_file = to_file
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
341
        self.to_messages_file = to_messages_file
964 by Martin Pool
- show progress on dumb terminals by printing dots
342
        self.last_msg = None
343
        self.last_cnt = None
344
        self.last_total = None
345
        self.show_pct = show_pct
346
        self.show_spinner = show_spinner
347
        self.show_eta = show_eta
348
        self.show_bar = show_bar
349
        self.show_count = show_count
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
350
        self._stack = _stack
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
351
        # seed throttler
352
        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
353
        now = time.time()
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
354
        # starting now
2745.6.52 by Andrew Bennetts
Revert bad change to bzrlib/progress.py
355
        self.start_time = now
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
356
        # next update should not throttle
357
        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.
358
359
    def finished(self):
360
        """Return this bar to its progress stack."""
361
        self.clear()
362
        self._stack.return_pb(self)
1104 by Martin Pool
- Add a simple UIFactory
363
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
364
    def note(self, fmt_string, *args, **kwargs):
365
        """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
366
        self.clear()
1558.7.9 by Aaron Bentley
Bad change. (broke tests). Reverted.
367
        self.to_messages_file.write(fmt_string % args)
368
        self.to_messages_file.write('\n')
1104 by Martin Pool
- Add a simple UIFactory
369
1551.2.29 by Aaron Bentley
Got stack handling under test
370
    def child_progress(self, **kwargs):
371
        return ChildProgress(**kwargs)
372
1534.11.7 by Robert Collins
Test and correct the problem with nested test logs breaking further in-test logs.
373
1104 by Martin Pool
- Add a simple UIFactory
374
class DummyProgress(_BaseProgressBar):
375
    """Progress-bar standin that does nothing.
376
377
    This can be used as the default argument for methods that
378
    take an optional progress indicator."""
379
    def tick(self):
380
        pass
381
382
    def update(self, msg=None, current=None, total=None):
383
        pass
384
1551.2.27 by Aaron Bentley
Got propogation under test
385
    def child_update(self, message, current, total):
386
        pass
387
1104 by Martin Pool
- Add a simple UIFactory
388
    def clear(self):
389
        pass
964 by Martin Pool
- show progress on dumb terminals by printing dots
390
        
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
391
    def note(self, fmt_string, *args, **kwargs):
392
        """See _BaseProgressBar.note()."""
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
393
1551.2.29 by Aaron Bentley
Got stack handling under test
394
    def child_progress(self, **kwargs):
395
        return DummyProgress(**kwargs)
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
396
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
397
964 by Martin Pool
- show progress on dumb terminals by printing dots
398
class DotsProgressBar(_BaseProgressBar):
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
399
964 by Martin Pool
- show progress on dumb terminals by printing dots
400
    def __init__(self, **kwargs):
401
        _BaseProgressBar.__init__(self, **kwargs)
402
        self.last_msg = None
403
        self.need_nl = False
404
        
405
    def tick(self):
406
        self.update()
407
        
408
    def update(self, msg=None, current_cnt=None, total_cnt=None):
409
        if msg and msg != self.last_msg:
410
            if self.need_nl:
411
                self.to_file.write('\n')
412
            self.to_file.write(msg + ': ')
413
            self.last_msg = msg
414
        self.need_nl = True
415
        self.to_file.write('.')
416
        
417
    def clear(self):
418
        if self.need_nl:
419
            self.to_file.write('\n')
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
420
        self.need_nl = False
964 by Martin Pool
- show progress on dumb terminals by printing dots
421
        
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
422
    def child_update(self, message, current, total):
423
        self.tick()
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
424
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
425
426
964 by Martin Pool
- show progress on dumb terminals by printing dots
427
    
428
class TTYProgressBar(_BaseProgressBar):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
429
    """Progress bar display object.
430
431
    Several options are available to control the display.  These can
432
    be passed as parameters to the constructor or assigned at any time:
433
434
    show_pct
435
        Show percentage complete.
436
    show_spinner
437
        Show rotating baton.  This ticks over on every update even
438
        if the values don't change.
439
    show_eta
440
        Show predicted time-to-completion.
441
    show_bar
442
        Show bar graph.
443
    show_count
444
        Show numerical counts.
445
446
    The output file should be in line-buffered or unbuffered mode.
447
    """
448
    SPIN_CHARS = r'/-\|'
661 by Martin Pool
- limit rate at which progress bar is updated
449
964 by Martin Pool
- show progress on dumb terminals by printing dots
450
451
    def __init__(self, **kwargs):
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
452
        from bzrlib.osutils import terminal_width
964 by Martin Pool
- show progress on dumb terminals by printing dots
453
        _BaseProgressBar.__init__(self, **kwargs)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
454
        self.spin_pos = 0
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
455
        self.width = terminal_width()
1843.3.3 by John Arbash Meinel
Don't let the last_updates list grow without bound.
456
        self.last_updates = []
1843.3.4 by John Arbash Meinel
Remove get_eta's ability to modify last_updates.
457
        self._max_last_updates = 10
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
458
        self.child_fraction = 0
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
459
        self._have_output = False
964 by Martin Pool
- show progress on dumb terminals by printing dots
460
    
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
461
    def throttle(self, old_msg):
964 by Martin Pool
- show progress on dumb terminals by printing dots
462
        """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.
463
        # 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
464
        # time.clock() is faster, but gives us CPU time, not wall-clock time
465
        now = time.time()
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
466
        if self.start_time is not None and (now - self.start_time) < 1:
467
            return True
468
        if old_msg != self.last_msg:
469
            return False
1596.2.16 by Robert Collins
Microprofiling: progress.update was costing 0.01 ms per call in time.time.
470
        interval = now - self.last_update
471
        # if interval > 0
472
        if interval < self.MIN_PAUSE:
473
            return True
964 by Martin Pool
- show progress on dumb terminals by printing dots
474
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
475
        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.
476
        # Don't let the queue grow without bound
477
        self.last_updates = self.last_updates[-self._max_last_updates:]
964 by Martin Pool
- show progress on dumb terminals by printing dots
478
        self.last_update = now
479
        return False
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
480
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
481
    def tick(self):
3006.3.2 by Robert Collins
More formatting corrections.
482
        self.update(self.last_msg, self.last_cnt, self.last_total,
1551.2.27 by Aaron Bentley
Got propogation under test
483
                    self.child_fraction)
484
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
485
    def child_update(self, message, current, total):
1551.2.35 by Aaron Bentley
Fix division-by-zero
486
        if current is not None and total != 0:
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
487
            child_fraction = float(current) / total
488
            if self.last_cnt is None:
489
                pass
490
            elif self.last_cnt + child_fraction <= self.last_total:
491
                self.child_fraction = child_fraction
492
        if self.last_msg is None:
493
            self.last_msg = ''
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
494
        self.tick()
495
3006.3.1 by Robert Collins
Minor PEP8 changes.
496
    def update(self, msg, current_cnt=None, total_cnt=None,
3882.8.1 by Martin Pool
Remove experimental transport display from TTYProgressBar
497
            child_fraction=0):
3882.7.6 by Martin Pool
Preliminary support for drawing network io into the progress bar
498
        """Update and redraw progress bar.
3882.8.1 by Martin Pool
Remove experimental transport display from TTYProgressBar
499
        """
1534.11.1 by Robert Collins
Teach bzr selftest to use a progress bar in non verbose mode.
500
        if msg is None:
501
            msg = self.last_msg
502
503
        if total_cnt is None:
504
            total_cnt = self.last_total
505
1308 by Martin Pool
- make progress bar more tolerant of out-of-range values
506
        if current_cnt < 0:
507
            current_cnt = 0
508
            
509
        if current_cnt > total_cnt:
510
            total_cnt = current_cnt
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
511
        
1596.2.17 by Robert Collins
Notes on further progress tuning.
512
        ## # optional corner case optimisation 
513
        ## # currently does not seem to fire so costs more than saved.
514
        ## # trivial optimal case:
515
        ## # NB if callers are doing a clear and restore with
516
        ## # the saved values, this will prevent that:
517
        ## # in that case add a restore method that calls
518
        ## # _do_update or some such
519
        ## if (self.last_msg == msg and
520
        ##     self.last_cnt == current_cnt and
521
        ##     self.last_total == total_cnt and
522
        ##     self.child_fraction == child_fraction):
523
        ##     return
524
3882.7.6 by Martin Pool
Preliminary support for drawing network io into the progress bar
525
        if msg is None:
526
            msg = ''
527
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
528
        old_msg = self.last_msg
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
529
        # save these for the tick() function
530
        self.last_msg = msg
531
        self.last_cnt = current_cnt
532
        self.last_total = total_cnt
1596.2.17 by Robert Collins
Notes on further progress tuning.
533
        self.child_fraction = child_fraction
534
535
        # each function call takes 20ms/4000 = 0.005 ms, 
536
        # but multiple that by 4000 calls -> starts to cost.
537
        # so anything to make this function call faster
538
        # 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
539
        if self.throttle(old_msg):
1596.2.17 by Robert Collins
Notes on further progress tuning.
540
            return
541
542
        if self.show_eta and self.start_time and self.last_total:
543
            eta = get_eta(self.start_time, self.last_cnt + self.child_fraction, 
544
                    self.last_total, last_updates = self.last_updates)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
545
            eta_str = " " + str_tdelta(eta)
546
        else:
547
            eta_str = ""
548
549
        if self.show_spinner:
550
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
551
        else:
552
            spin_str = ''
553
554
        # always update this; it's also used for the bar
555
        self.spin_pos += 1
556
1596.2.17 by Robert Collins
Notes on further progress tuning.
557
        if self.show_pct and self.last_total and self.last_cnt:
558
            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
559
            pct_str = ' (%5.1f%%)' % pct
560
        else:
561
            pct_str = ''
562
563
        if not self.show_count:
564
            count_str = ''
1596.2.17 by Robert Collins
Notes on further progress tuning.
565
        elif self.last_cnt is None:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
566
            count_str = ''
1596.2.17 by Robert Collins
Notes on further progress tuning.
567
        elif self.last_total is None:
568
            count_str = ' %i' % (self.last_cnt)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
569
        else:
570
            # make both fields the same size
1596.2.17 by Robert Collins
Notes on further progress tuning.
571
            t = '%i' % (self.last_total)
572
            c = '%*i' % (len(t), self.last_cnt)
3882.7.6 by Martin Pool
Preliminary support for drawing network io into the progress bar
573
            count_str = ' ' + c + '/' + t
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
574
575
        if self.show_bar:
576
            # progress bar, if present, soaks up all remaining space
1596.2.17 by Robert Collins
Notes on further progress tuning.
577
            cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
3882.8.1 by Martin Pool
Remove experimental transport display from TTYProgressBar
578
                   - len(eta_str) - len(count_str) - 3
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
579
1596.2.17 by Robert Collins
Notes on further progress tuning.
580
            if self.last_total:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
581
                # number of markers highlighted in bar
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
582
                markers = int(round(float(cols) * 
1596.2.17 by Robert Collins
Notes on further progress tuning.
583
                              (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
584
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
585
            elif False:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
586
                # don't know total, so can't show completion.
587
                # so just show an expanded spinning thingy
588
                m = self.spin_pos % cols
668 by Martin Pool
- fix sweeping bar progress indicator
589
                ms = (' ' * m + '*').ljust(cols)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
590
                
591
                bar_str = '[' + ms + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
592
            else:
593
                bar_str = ''
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
594
        else:
595
            bar_str = ''
596
3882.8.1 by Martin Pool
Remove experimental transport display from TTYProgressBar
597
        m = spin_str + bar_str + self.last_msg + count_str \
3882.7.6 by Martin Pool
Preliminary support for drawing network io into the progress bar
598
            + pct_str + eta_str
2095.4.4 by mbp at sourcefrog
Truncate progress bar rather than complaining if it's too long
599
        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.
600
        self._have_output = True
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
601
        #self.to_file.flush()
602
            
3006.3.2 by Robert Collins
More formatting corrections.
603
    def clear(self):
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
604
        if self._have_output:
605
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
606
        self._have_output = False
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
607
        #self.to_file.flush()        
649 by Martin Pool
- some cleanups for the progressbar method
608
1551.2.27 by Aaron Bentley
Got propogation under test
609
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
610
611
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
612
class ChildProgress(_BaseProgressBar):
1551.2.27 by Aaron Bentley
Got propogation under test
613
    """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
614
1551.2.29 by Aaron Bentley
Got stack handling under test
615
    def __init__(self, _stack, **kwargs):
616
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
617
        self.parent = _stack.top()
1551.2.27 by Aaron Bentley
Got propogation under test
618
        self.current = None
619
        self.total = None
620
        self.child_fraction = 0
621
        self.message = None
622
623
    def update(self, msg, current_cnt=None, total_cnt=None):
624
        self.current = current_cnt
2592.6.11 by Robert Collins
* A progress bar has been added for knitpack -> knitpack fetching.
625
        if total_cnt is not None:
626
            self.total = total_cnt
1551.2.27 by Aaron Bentley
Got propogation under test
627
        self.message = msg
628
        self.child_fraction = 0
629
        self.tick()
630
631
    def child_update(self, message, current, total):
1551.2.35 by Aaron Bentley
Fix division-by-zero
632
        if current is None or total == 0:
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
633
            self.child_fraction = 0
634
        else:
635
            self.child_fraction = float(current) / total
1551.2.27 by Aaron Bentley
Got propogation under test
636
        self.tick()
637
638
    def tick(self):
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
639
        if self.current is None:
640
            count = None
641
        else:
642
            count = self.current+self.child_fraction
643
            if count > self.total:
1596.2.35 by Robert Collins
Subclass SequenceMatcher to get a slightly faster (in our case) find_longest_match routine.
644
                if __debug__:
645
                    mutter('clamping count of %d to %d' % (count, self.total))
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
646
                count = self.total
1551.2.27 by Aaron Bentley
Got propogation under test
647
        self.parent.child_update(self.message, count, self.total)
648
1551.2.29 by Aaron Bentley
Got stack handling under test
649
    def clear(self):
1551.2.30 by Aaron Bentley
Bugfixes to progress stuff
650
        pass
1551.2.29 by Aaron Bentley
Got stack handling under test
651
1558.8.6 by Aaron Bentley
Fix note implementation
652
    def note(self, *args, **kwargs):
1558.8.5 by Aaron Bentley
Pass note up the stack instead of using bzrlib.ui_factory
653
        self.parent.note(*args, **kwargs)
654
3146.6.1 by Aaron Bentley
InterDifferingSerializer shows a progress bar
655
656
class InstrumentedProgress(TTYProgressBar):
657
    """TTYProgress variant that tracks outcomes"""
658
659
    def __init__(self, *args, **kwargs):
660
        self.always_throttled = True
661
        self.never_throttle = False
662
        TTYProgressBar.__init__(self, *args, **kwargs)
663
664
    def throttle(self, old_message):
665
        if self.never_throttle:
666
            result =  False
667
        else:
668
            result = TTYProgressBar.throttle(self, old_message)
669
        if result is False:
670
            self.always_throttled = False
671
672
648 by Martin Pool
- import aaron's progress-indicator code
673
def str_tdelta(delt):
674
    if delt is None:
675
        return "-:--:--"
660 by Martin Pool
- use plain unix time, not datetime module
676
    delt = int(round(delt))
677
    return '%d:%02d:%02d' % (delt/3600,
678
                             (delt/60) % 60,
679
                             delt % 60)
680
681
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
682
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
683
    if start_time is None:
684
        return None
685
686
    if not total:
687
        return None
688
689
    if current < enough_samples:
690
        return None
691
692
    if current > total:
693
        return None                     # wtf?
694
2120.1.1 by John Arbash Meinel
Use time.time() because time.clock() is CPU time, not wall time
695
    elapsed = time.time() - start_time
660 by Martin Pool
- use plain unix time, not datetime module
696
697
    if elapsed < 2.0:                   # not enough time to estimate
698
        return None
699
    
700
    total_duration = float(elapsed) * float(total) / float(current)
701
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
702
    if last_updates and len(last_updates) >= n_recent:
703
        avg = sum(last_updates) / float(len(last_updates))
704
        time_left = avg * (total - current)
705
706
        old_time_left = total_duration - elapsed
707
708
        # We could return the average, or some other value here
709
        return (time_left + old_time_left) / 2
710
660 by Martin Pool
- use plain unix time, not datetime module
711
    return total_duration - elapsed
648 by Martin Pool
- import aaron's progress-indicator code
712
649 by Martin Pool
- some cleanups for the progressbar method
713
1551.2.32 by Aaron Bentley
Handle progress phases more nicely in merge
714
class ProgressPhase(object):
715
    """Update progress object with the current phase"""
716
    def __init__(self, message, total, pb):
717
        object.__init__(self)
718
        self.pb = pb
719
        self.message = message
720
        self.total = total
721
        self.cur_phase = None
722
723
    def next_phase(self):
724
        if self.cur_phase is None:
725
            self.cur_phase = 0
726
        else:
727
            self.cur_phase += 1
728
        self.pb.update(self.message, self.cur_phase, self.total)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
729
730
731
_progress_bar_types = {}
732
_progress_bar_types['dummy'] = DummyProgress
733
_progress_bar_types['none'] = DummyProgress
734
_progress_bar_types['tty'] = TTYProgressBar
735
_progress_bar_types['dots'] = DotsProgressBar