~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Martin Pool
  • Date: 2008-12-15 08:28:57 UTC
  • mto: (3882.7.11 progress)
  • mto: This revision was merged to the branch mainline in revision 3940.
  • Revision ID: mbp@sourcefrog.net-20081215082857-asjzld70e2s1i0ta
Change progress bars to a more MVC style

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
28
28
bzrlib it really is best to use bzrlib.ui.ui_factory.
29
29
"""
30
30
 
31
 
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
32
 
# when the rate is unpredictable
33
31
 
34
32
import sys
35
33
import time
36
34
import os
37
35
 
38
 
from bzrlib.lazy_import import lazy_import
39
 
lazy_import(globals(), """
 
36
 
40
37
from bzrlib import (
41
38
    errors,
 
39
    osutils,
 
40
    trace,
 
41
    ui,
42
42
    )
43
 
""")
44
 
 
45
43
from bzrlib.trace import mutter
46
44
 
47
45
 
62
60
    return True
63
61
 
64
62
 
65
 
_progress_bar_types = {}
 
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
 
 
70
    def __init__(self, parent_task=None):
 
71
        self._parent_task = parent_task
 
72
        self._last_update = 0
 
73
        self.total_cnt = None
 
74
        self.current_cnt = None
 
75
        self.msg = ''
 
76
 
 
77
    def update(self, msg, current_cnt=None, total_cnt=None):
 
78
        self.msg = msg
 
79
        self.current_cnt = current_cnt
 
80
        if total_cnt:
 
81
            self.total_cnt = total_cnt
 
82
        ui.ui_factory.show_progress(self)
 
83
 
 
84
    def finished(self):
 
85
        ui.ui_factory.progress_finished(self)
 
86
 
 
87
    def make_sub_task(self):
 
88
        return ProgressTask(parent_task=self)
 
89
 
 
90
    def _overall_completion_fraction(self, child_fraction=0.0):
 
91
        """Return fractional completion of this task and its parents
 
92
        
 
93
        Returns None if no completion can be computed."""
 
94
        if self.total_cnt:
 
95
            own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
 
96
        else:
 
97
            own_fraction = None
 
98
        if self._parent_task is None:
 
99
            return own_fraction
 
100
        else:
 
101
            if own_fraction is None:
 
102
                own_fraction = 0.0
 
103
            return self._parent_task._overall_completion_fraction(own_fraction)
 
104
 
 
105
    def note(self, fmt_string, *args, **kwargs):
 
106
        """Record a note without disrupting the progress bar."""
 
107
        # XXX: shouldn't be here; put it in mutter or the ui instead
 
108
        ui.ui_factory.clear_term()
 
109
        trace.note(fmt_string % args)
 
110
 
 
111
    def clear(self):
 
112
        # XXX: shouldn't be here; put it in mutter or the ui instead
 
113
        ui.ui_factory.clear_term()
66
114
 
67
115
 
68
116
def ProgressBar(to_file=None, **kwargs):
87
135
                                                _progress_bar_types.keys())
88
136
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
89
137
 
90
 
 
 
138
 
 
139
 
 
140
class TextProgressView(object):
 
141
    """Display of progress bar and other information on a tty.
 
142
    
 
143
    This shows one line of text, including possibly a network indicator, spinner, 
 
144
    progress bar, message, etc.
 
145
 
 
146
    One instance of this is created and held by the UI, and fed updates when a
 
147
    task wants to be painted.
 
148
    """
 
149
 
 
150
    def __init__(self, term_file):
 
151
        self._term_file = term_file
 
152
        # true when there's output on the screen we may need to clear
 
153
        self._have_output = False
 
154
        self._width = osutils.terminal_width()
 
155
        self._last_transport_msg = ''
 
156
        self._last_task_msg = ''
 
157
        self._spin_pos = 0
 
158
        self._last_update = 0
 
159
        self._transport_update_time = 0
 
160
        self._task_fraction = None
 
161
 
 
162
    def _show_line(self, s):
 
163
        n = self._width - 1
 
164
        self._term_file.write('\r%-*.*s\r' % (n, n, s))
 
165
 
 
166
    def clear(self):
 
167
        if self._have_output:
 
168
            self._show_line('')
 
169
        self._have_output = False
 
170
 
 
171
    def _render_bar(self):
 
172
        # return a string for the progress bar itself
 
173
        if self._task_fraction is not None:
 
174
            cols = 20
 
175
            # number of markers highlighted in bar
 
176
            markers = int(round(float(cols) * self._task_fraction))
 
177
            bar_str = ' [' + ('#' * markers).ljust(cols) + '] '
 
178
            return bar_str
 
179
        else:
 
180
            return ''
 
181
 
 
182
    def _format_task(self, task):
 
183
        if task.total_cnt is not None:
 
184
            s = ' %d/%d' % (task.current_cnt, task.total_cnt)
 
185
        elif task.current_cnt is not None:
 
186
            s = ' %d' % (task.current_cnt)
 
187
        else:
 
188
            s = ''
 
189
        self._task_fraction = task._overall_completion_fraction()
 
190
        # compose all the parent messages
 
191
        t = task
 
192
        m = task.msg
 
193
        while t._parent_task:
 
194
            t = t._parent_task
 
195
            if t.msg:
 
196
                m = t.msg + ':' + m
 
197
        return m + s
 
198
 
 
199
    def _repaint(self):
 
200
        now = time.time()
 
201
        if now < self._last_update + 0.1:
 
202
            return
 
203
        if now > self._transport_update_time + 5:
 
204
            # no recent activity; expire it
 
205
            self._last_transport_msg = ''
 
206
        self._last_update = now
 
207
        spin_str =  r'/-\|'[self._spin_pos % 4]
 
208
        self._spin_pos += 1
 
209
        bar_string = self._render_bar()
 
210
        s = (self._last_transport_msg
 
211
             + ' ' + spin_str + ' ' +
 
212
             self._last_task_msg)
 
213
        s_max = self._width - 1 - len(bar_string)
 
214
        if len(s) > s_max:
 
215
            s = s[:s_max+1]
 
216
        elif len(s) < s_max:
 
217
            s = s.ljust(s_max)
 
218
        self._show_line(s + bar_string)
 
219
        self._have_output = True
 
220
 
 
221
    def show_progress(self, task):
 
222
        self._last_task_msg = self._format_task(task)
 
223
        self._repaint()
 
224
 
 
225
    def show_transport_activity(self, msg):
 
226
        self._last_transport_msg = msg
 
227
        self._transport_update_time = time.time()
 
228
        self._repaint()
 
229
 
 
230
 
91
231
class ProgressBarStack(object):
92
232
    """A stack of progress bars."""
93
233
 
225
365
        return DummyProgress(**kwargs)
226
366
 
227
367
 
228
 
_progress_bar_types['dummy'] = DummyProgress
229
 
_progress_bar_types['none'] = DummyProgress
230
 
 
231
 
 
232
368
class DotsProgressBar(_BaseProgressBar):
233
369
 
234
370
    def __init__(self, **kwargs):
257
393
        self.tick()
258
394
 
259
395
 
260
 
_progress_bar_types['dots'] = DotsProgressBar
261
396
 
262
397
    
263
398
class TTYProgressBar(_BaseProgressBar):
287
422
        from bzrlib.osutils import terminal_width
288
423
        _BaseProgressBar.__init__(self, **kwargs)
289
424
        self.spin_pos = 0
 
425
        # XXX: We could listen for SIGWINCH and update the terminal width...
290
426
        self.width = terminal_width()
291
427
        self.last_updates = []
292
428
        self._max_last_updates = 10
448
584
        #self.to_file.flush()        
449
585
 
450
586
 
451
 
_progress_bar_types['tty'] = TTYProgressBar
452
587
 
453
588
 
454
589
class ChildProgress(_BaseProgressBar):
568
703
        else:
569
704
            self.cur_phase += 1
570
705
        self.pb.update(self.message, self.cur_phase, self.total)
 
706
 
 
707
 
 
708
_progress_bar_types = {}
 
709
_progress_bar_types['dummy'] = DummyProgress
 
710
_progress_bar_types['none'] = DummyProgress
 
711
_progress_bar_types['tty'] = TTYProgressBar
 
712
_progress_bar_types['dots'] = DotsProgressBar