~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-15 05:30:30 UTC
  • mto: (4973.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4975.
  • Revision ID: andrew.bennetts@canonical.com-20100115053030-1d6qd89pnj8hmb55
Pass kinds (not pairs) to MergeHookParams.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
"""Progress indicators.
25
25
import sys
26
26
import time
27
27
import os
28
 
import warnings
29
28
 
30
29
 
31
30
from bzrlib import (
32
31
    errors,
33
 
    osutils,
34
 
    trace,
35
 
    ui,
36
32
    )
37
33
from bzrlib.trace import mutter
38
34
from bzrlib.symbol_versioning import (
 
35
    deprecated_function,
39
36
    deprecated_in,
40
37
    deprecated_method,
41
38
    )
42
39
 
43
40
 
44
41
def _supports_progress(f):
45
 
    """Detect if we can use pretty progress bars on the output stream f.
 
42
    """Detect if we can use pretty progress bars on file F.
46
43
 
47
 
    If this returns true we expect that a human may be looking at that 
 
44
    If this returns true we expect that a human may be looking at that
48
45
    output, and that we can repaint a line to update it.
 
46
 
 
47
    This doesn't check the policy for whether we *should* use them.
49
48
    """
50
49
    isatty = getattr(f, 'isatty', None)
51
50
    if isatty is None:
52
51
        return False
53
52
    if not isatty():
54
53
        return False
 
54
    # The following case also handles Win32 - on that platform $TERM is
 
55
    # typically never set, so the case None is treated as a smart terminal,
 
56
    # not dumb.  <https://bugs.launchpad.net/bugs/334808>  win32 files do have
 
57
    # isatty methods that return true.
55
58
    if os.environ.get('TERM') == 'dumb':
56
59
        # e.g. emacs compile window
57
60
        return False
61
64
class ProgressTask(object):
62
65
    """Model component of a progress indicator.
63
66
 
64
 
    Most code that needs to indicate progress should update one of these, 
 
67
    Most code that needs to indicate progress should update one of these,
65
68
    and it will in turn update the display, if one is present.
66
69
 
67
70
    Code updating the task may also set fields as hints about how to display
68
71
    it: show_pct, show_spinner, show_eta, show_count, show_bar.  UIs
69
72
    will not necessarily respect all these fields.
 
73
    
 
74
    :ivar update_latency: The interval (in seconds) at which the PB should be
 
75
        updated.  Setting this to zero suggests every update should be shown
 
76
        synchronously.
 
77
 
 
78
    :ivar show_transport_activity: If true (default), transport activity
 
79
        will be shown when this task is drawn.  Disable it if you're sure 
 
80
        that only irrelevant or uninteresting transport activity can occur
 
81
        during this task.
70
82
    """
71
83
 
72
 
    def __init__(self, parent_task=None, ui_factory=None):
 
84
    def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
 
85
        """Construct a new progress task.
 
86
 
 
87
        :param parent_task: Enclosing ProgressTask or None.
 
88
 
 
89
        :param progress_view: ProgressView to display this ProgressTask.
 
90
 
 
91
        :param ui_factory: The UI factory that will display updates; 
 
92
            deprecated in favor of passing progress_view directly.
 
93
 
 
94
        Normally you should not call this directly but rather through
 
95
        `ui_factory.nested_progress_bar`.
 
96
        """
73
97
        self._parent_task = parent_task
74
98
        self._last_update = 0
75
99
        self.total_cnt = None
76
100
        self.current_cnt = None
77
101
        self.msg = ''
 
102
        # TODO: deprecate passing ui_factory
78
103
        self.ui_factory = ui_factory
 
104
        self.progress_view = progress_view
79
105
        self.show_pct = False
80
106
        self.show_spinner = True
81
107
        self.show_eta = False,
82
108
        self.show_count = True
83
109
        self.show_bar = True
 
110
        self.update_latency = 0.1
 
111
        self.show_transport_activity = True
84
112
 
85
113
    def __repr__(self):
86
114
        return '%s(%r/%r, msg=%r)' % (
94
122
        self.current_cnt = current_cnt
95
123
        if total_cnt:
96
124
            self.total_cnt = total_cnt
97
 
        self.ui_factory._progress_updated(self)
 
125
        if self.progress_view:
 
126
            self.progress_view.show_progress(self)
 
127
        else:
 
128
            self.ui_factory._progress_updated(self)
98
129
 
99
130
    def tick(self):
100
131
        self.update(self.msg)
101
132
 
102
133
    def finished(self):
103
 
        self.ui_factory._progress_finished(self)
 
134
        if self.progress_view:
 
135
            self.progress_view.task_finished(self)
 
136
        else:
 
137
            self.ui_factory._progress_finished(self)
104
138
 
105
139
    def make_sub_task(self):
106
 
        return ProgressTask(self, self.ui_factory)
 
140
        return ProgressTask(self, ui_factory=self.ui_factory,
 
141
            progress_view=self.progress_view)
107
142
 
108
143
    def _overall_completion_fraction(self, child_fraction=0.0):
109
144
        """Return fractional completion of this task and its parents
110
 
        
 
145
 
111
146
        Returns None if no completion can be computed."""
112
 
        if self.total_cnt:
 
147
        if self.current_cnt is not None and self.total_cnt:
113
148
            own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
114
149
        else:
115
 
            own_fraction = None
 
150
            # if this task has no estimation, it just passes on directly
 
151
            # whatever the child has measured...
 
152
            own_fraction = child_fraction
116
153
        if self._parent_task is None:
117
154
            return own_fraction
118
155
        else:
120
157
                own_fraction = 0.0
121
158
            return self._parent_task._overall_completion_fraction(own_fraction)
122
159
 
 
160
    @deprecated_method(deprecated_in((2, 1, 0)))
123
161
    def note(self, fmt_string, *args):
124
 
        """Record a note without disrupting the progress bar."""
125
 
        # XXX: shouldn't be here; put it in mutter or the ui instead
 
162
        """Record a note without disrupting the progress bar.
 
163
        
 
164
        Deprecated: use ui_factory.note() instead or bzrlib.trace.  Note that
 
165
        ui_factory.note takes just one string as the argument, not a format
 
166
        string and arguments.
 
167
        """
126
168
        if args:
127
169
            self.ui_factory.note(fmt_string % args)
128
170
        else:
129
171
            self.ui_factory.note(fmt_string)
130
172
 
131
173
    def clear(self):
132
 
        # XXX: shouldn't be here; put it in mutter or the ui instead
133
 
        self.ui_factory.clear_term()
134
 
 
135
 
 
 
174
        # TODO: deprecate this method; the model object shouldn't be concerned
 
175
        # with whether it's shown or not.  Most callers use this because they
 
176
        # want to write some different non-progress output to the screen, but
 
177
        # they should probably instead use a stream that's synchronized with
 
178
        # the progress output.  It may be there is a model-level use for
 
179
        # saying "this task's not active at the moment" but I don't see it. --
 
180
        # mbp 20090623
 
181
        if self.progress_view:
 
182
            self.progress_view.clear()
 
183
        else:
 
184
            self.ui_factory.clear_term()
 
185
 
 
186
 
 
187
@deprecated_function(deprecated_in((1, 16, 0)))
136
188
def ProgressBar(to_file=None, **kwargs):
137
 
    """Abstract factory"""
 
189
    """Construct a progress bar.
 
190
 
 
191
    Deprecated; ask the ui_factory for a progress task instead.
 
192
    """
138
193
    if to_file is None:
139
194
        to_file = sys.stderr
140
195
    requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
156
211
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
157
212
 
158
213
 
159
 
class ProgressBarStack(object):
160
 
    """A stack of progress bars.
161
 
    
162
 
    This class is deprecated: instead, ask the ui factory for a new progress
163
 
    task and finish it when it's done.
164
 
    """
165
 
 
166
 
    @deprecated_method(deprecated_in((1, 12, 0)))
167
 
    def __init__(self,
168
 
                 to_file=None,
169
 
                 show_pct=False,
170
 
                 show_spinner=True,
171
 
                 show_eta=False,
172
 
                 show_bar=True,
173
 
                 show_count=True,
174
 
                 to_messages_file=None,
175
 
                 klass=None):
176
 
        """Setup the stack with the parameters the progress bars should have."""
177
 
        if to_file is None:
178
 
            to_file = sys.stderr
179
 
        if to_messages_file is None:
180
 
            to_messages_file = sys.stdout
181
 
        self._to_file = to_file
182
 
        self._show_pct = show_pct
183
 
        self._show_spinner = show_spinner
184
 
        self._show_eta = show_eta
185
 
        self._show_bar = show_bar
186
 
        self._show_count = show_count
187
 
        self._to_messages_file = to_messages_file
188
 
        self._stack = []
189
 
        self._klass = klass or ProgressBar
190
 
 
191
 
    def top(self):
192
 
        if len(self._stack) != 0:
193
 
            return self._stack[-1]
194
 
        else:
195
 
            return None
196
 
 
197
 
    def bottom(self):
198
 
        if len(self._stack) != 0:
199
 
            return self._stack[0]
200
 
        else:
201
 
            return None
202
 
 
203
 
    def get_nested(self):
204
 
        """Return a nested progress bar."""
205
 
        if len(self._stack) == 0:
206
 
            func = self._klass
207
 
        else:
208
 
            func = self.top().child_progress
209
 
        new_bar = func(to_file=self._to_file,
210
 
                       show_pct=self._show_pct,
211
 
                       show_spinner=self._show_spinner,
212
 
                       show_eta=self._show_eta,
213
 
                       show_bar=self._show_bar,
214
 
                       show_count=self._show_count,
215
 
                       to_messages_file=self._to_messages_file,
216
 
                       _stack=self)
217
 
        self._stack.append(new_bar)
218
 
        return new_bar
219
 
 
220
 
    def return_pb(self, bar):
221
 
        """Return bar after its been used."""
222
 
        if bar is not self._stack[-1]:
223
 
            warnings.warn("%r is not currently active" % (bar,))
224
 
        else:
225
 
            self._stack.pop()
226
 
 
227
 
 
 
214
# NOTE: This is also deprecated; you should provide a ProgressView instead.
228
215
class _BaseProgressBar(object):
229
216
 
230
217
    def __init__(self,
271
258
        self.to_messages_file.write(fmt_string % args)
272
259
        self.to_messages_file.write('\n')
273
260
 
 
261
    @deprecated_function(deprecated_in((1, 16, 0)))
274
262
    def child_progress(self, **kwargs):
275
263
        return ChildProgress(**kwargs)
276
264
 
292
280
 
293
281
    def clear(self):
294
282
        pass
295
 
        
 
283
 
296
284
    def note(self, fmt_string, *args, **kwargs):
297
285
        """See _BaseProgressBar.note()."""
298
286
 
302
290
 
303
291
class DotsProgressBar(_BaseProgressBar):
304
292
 
 
293
    @deprecated_function(deprecated_in((1, 16, 0)))
305
294
    def __init__(self, **kwargs):
306
295
        _BaseProgressBar.__init__(self, **kwargs)
307
296
        self.last_msg = None
308
297
        self.need_nl = False
309
 
        
 
298
 
310
299
    def tick(self):
311
300
        self.update()
312
 
        
 
301
 
313
302
    def update(self, msg=None, current_cnt=None, total_cnt=None):
314
303
        if msg and msg != self.last_msg:
315
304
            if self.need_nl:
318
307
            self.last_msg = msg
319
308
        self.need_nl = True
320
309
        self.to_file.write('.')
321
 
        
 
310
 
322
311
    def clear(self):
323
312
        if self.need_nl:
324
313
            self.to_file.write('\n')
325
314
        self.need_nl = False
326
 
        
 
315
 
327
316
    def child_update(self, message, current, total):
328
317
        self.tick()
329
318
 
330
319
 
331
 
 
332
 
    
333
320
class TTYProgressBar(_BaseProgressBar):
334
321
    """Progress bar display object.
335
322
 
352
339
    """
353
340
    SPIN_CHARS = r'/-\|'
354
341
 
355
 
 
 
342
    @deprecated_function(deprecated_in((1, 16, 0)))
356
343
    def __init__(self, **kwargs):
357
344
        from bzrlib.osutils import terminal_width
358
345
        _BaseProgressBar.__init__(self, **kwargs)
362
349
        self._max_last_updates = 10
363
350
        self.child_fraction = 0
364
351
        self._have_output = False
365
 
    
 
352
 
366
353
    def throttle(self, old_msg):
367
354
        """Return True if the bar was updated too recently"""
368
355
        # time.time consistently takes 40/4000 ms = 0.01 ms.
382
369
        self.last_updates = self.last_updates[-self._max_last_updates:]
383
370
        self.last_update = now
384
371
        return False
385
 
        
 
372
 
386
373
    def tick(self):
387
374
        self.update(self.last_msg, self.last_cnt, self.last_total,
388
375
                    self.child_fraction)
410
397
 
411
398
        if current_cnt < 0:
412
399
            current_cnt = 0
413
 
            
 
400
 
414
401
        if current_cnt > total_cnt:
415
402
            total_cnt = current_cnt
416
 
        
417
 
        ## # optional corner case optimisation 
 
403
 
 
404
        ## # optional corner case optimisation
418
405
        ## # currently does not seem to fire so costs more than saved.
419
406
        ## # trivial optimal case:
420
407
        ## # NB if callers are doing a clear and restore with
437
424
        self.last_total = total_cnt
438
425
        self.child_fraction = child_fraction
439
426
 
440
 
        # each function call takes 20ms/4000 = 0.005 ms, 
 
427
        # each function call takes 20ms/4000 = 0.005 ms,
441
428
        # but multiple that by 4000 calls -> starts to cost.
442
429
        # so anything to make this function call faster
443
430
        # will improve base 'diff' time by up to 0.1 seconds.
445
432
            return
446
433
 
447
434
        if self.show_eta and self.start_time and self.last_total:
448
 
            eta = get_eta(self.start_time, self.last_cnt + self.child_fraction, 
 
435
            eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
449
436
                    self.last_total, last_updates = self.last_updates)
450
437
            eta_str = " " + str_tdelta(eta)
451
438
        else:
452
439
            eta_str = ""
453
440
 
454
441
        if self.show_spinner:
455
 
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
 
442
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
456
443
        else:
457
444
            spin_str = ''
458
445
 
484
471
 
485
472
            if self.last_total:
486
473
                # number of markers highlighted in bar
487
 
                markers = int(round(float(cols) * 
 
474
                markers = int(round(float(cols) *
488
475
                              (self.last_cnt + self.child_fraction) / self.last_total))
489
476
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
490
477
            elif False:
492
479
                # so just show an expanded spinning thingy
493
480
                m = self.spin_pos % cols
494
481
                ms = (' ' * m + '*').ljust(cols)
495
 
                
 
482
 
496
483
                bar_str = '[' + ms + '] '
497
484
            else:
498
485
                bar_str = ''
504
491
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
505
492
        self._have_output = True
506
493
        #self.to_file.flush()
507
 
            
 
494
 
508
495
    def clear(self):
509
496
        if self._have_output:
510
497
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
511
498
        self._have_output = False
512
 
        #self.to_file.flush()        
513
 
 
514
 
 
515
 
 
516
 
 
 
499
        #self.to_file.flush()
 
500
 
 
501
 
 
502
 
 
503
# DEPRECATED
517
504
class ChildProgress(_BaseProgressBar):
518
505
    """A progress indicator that pushes its data to the parent"""
519
506
 
 
507
    @deprecated_function(deprecated_in((1, 16, 0)))
520
508
    def __init__(self, _stack, **kwargs):
521
509
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
522
510
        self.parent = _stack.top()
558
546
        self.parent.note(*args, **kwargs)
559
547
 
560
548
 
561
 
class InstrumentedProgress(TTYProgressBar):
562
 
    """TTYProgress variant that tracks outcomes"""
563
 
 
564
 
    def __init__(self, *args, **kwargs):
565
 
        self.always_throttled = True
566
 
        self.never_throttle = False
567
 
        TTYProgressBar.__init__(self, *args, **kwargs)
568
 
 
569
 
    def throttle(self, old_message):
570
 
        if self.never_throttle:
571
 
            result =  False
572
 
        else:
573
 
            result = TTYProgressBar.throttle(self, old_message)
574
 
        if result is False:
575
 
            self.always_throttled = False
576
 
 
577
 
 
578
549
def str_tdelta(delt):
579
550
    if delt is None:
580
551
        return "-:--:--"
601
572
 
602
573
    if elapsed < 2.0:                   # not enough time to estimate
603
574
        return None
604
 
    
 
575
 
605
576
    total_duration = float(elapsed) * float(total) / float(current)
606
577
 
607
578
    if last_updates and len(last_updates) >= n_recent: