~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-23 22:16:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1955.
  • Revision ID: john@arbash-meinel.com-20060823221627-fc64105bb12ae770
Ghozzy: Fix Bzr's support of Active FTP (aftp://)

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
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
17
 
 
18
 
 
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.
 
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
 
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
 
17
 
 
18
 
 
19
"""Simple text-mode progress indicator.
 
20
 
 
21
To display an indicator, create a ProgressBar object.  Call it,
 
22
passing Progress objects indicating the current state.  When done,
 
23
call clear().
 
24
 
 
25
Progress is suppressed when output is not sent to a terminal, so as
 
26
not to clutter log files.
29
27
"""
30
28
 
 
29
# TODO: should be a global option e.g. --silent that disables progress
 
30
# indicators, preferably without needing to adjust all code that
 
31
# potentially calls them.
 
32
 
 
33
# TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
 
34
 
31
35
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
32
36
# when the rate is unpredictable
33
37
 
 
38
 
34
39
import sys
35
40
import time
36
41
import os
37
42
 
38
 
from bzrlib.lazy_import import lazy_import
39
 
lazy_import(globals(), """
40
 
from bzrlib import (
41
 
    errors,
42
 
    )
43
 
""")
44
 
 
 
43
import bzrlib.errors as errors
45
44
from bzrlib.trace import mutter
46
45
 
47
46
 
48
47
def _supports_progress(f):
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
 
    """
54
48
    isatty = getattr(f, 'isatty', None)
55
49
    if isatty is None:
56
50
        return False
75
69
        if _supports_progress(to_file):
76
70
            return TTYProgressBar(to_file=to_file, **kwargs)
77
71
        else:
78
 
            return DummyProgress(to_file=to_file, **kwargs)
 
72
            return DotsProgressBar(to_file=to_file, **kwargs)
79
73
    else:
80
74
        # Minor sanitation to prevent spurious errors
81
75
        requested_bar_type = requested_bar_type.lower().strip()
180
174
        self._stack = _stack
181
175
        # seed throttler
182
176
        self.MIN_PAUSE = 0.1 # seconds
183
 
        now = time.time()
 
177
        now = time.clock()
184
178
        # starting now
185
179
        self.start_time = now
186
180
        # next update should not throttle
189
183
    def finished(self):
190
184
        """Return this bar to its progress stack."""
191
185
        self.clear()
 
186
        assert self._stack is not None
192
187
        self._stack.return_pb(self)
193
188
 
194
189
    def note(self, fmt_string, *args, **kwargs):
293
288
        self.child_fraction = 0
294
289
        self._have_output = False
295
290
    
 
291
 
296
292
    def throttle(self, old_msg):
297
293
        """Return True if the bar was updated too recently"""
298
294
        # time.time consistently takes 40/4000 ms = 0.01 ms.
299
 
        # time.clock() is faster, but gives us CPU time, not wall-clock time
300
 
        now = time.time()
 
295
        # but every single update to the pb invokes it.
 
296
        # so we use time.clock which takes 20/4000 ms = 0.005ms
 
297
        # on the downside, time.clock() appears to have approximately
 
298
        # 10ms granularity, so we treat a zero-time change as 'throttled.'
 
299
        now = time.clock()
301
300
        if self.start_time is not None and (now - self.start_time) < 1:
302
301
            return True
303
302
        if old_msg != self.last_msg:
314
313
        return False
315
314
        
316
315
    def tick(self):
317
 
        self.update(self.last_msg, self.last_cnt, self.last_total,
 
316
        self.update(self.last_msg, self.last_cnt, self.last_total, 
318
317
                    self.child_fraction)
319
318
 
320
319
    def child_update(self, message, current, total):
324
323
                pass
325
324
            elif self.last_cnt + child_fraction <= self.last_total:
326
325
                self.child_fraction = child_fraction
 
326
            else:
 
327
                mutter('not updating child fraction')
327
328
        if self.last_msg is None:
328
329
            self.last_msg = ''
329
330
        self.tick()
330
331
 
331
 
    def update(self, msg, current_cnt=None, total_cnt=None,
 
332
    def update(self, msg, current_cnt=None, total_cnt=None, 
332
333
               child_fraction=0):
333
334
        """Update and redraw progress bar."""
334
335
        if msg is None:
426
427
            bar_str = ''
427
428
 
428
429
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
429
 
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
 
430
 
 
431
        assert len(m) < self.width
 
432
        self.to_file.write('\r' + m.ljust(self.width - 1))
430
433
        self._have_output = True
431
434
        #self.to_file.flush()
432
435
            
433
 
    def clear(self):
 
436
    def clear(self):        
434
437
        if self._have_output:
435
438
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
436
439
        self._have_output = False
453
456
 
454
457
    def update(self, msg, current_cnt=None, total_cnt=None):
455
458
        self.current = current_cnt
456
 
        if total_cnt is not None:
457
 
            self.total = total_cnt
 
459
        self.total = total_cnt
458
460
        self.message = msg
459
461
        self.child_fraction = 0
460
462
        self.tick()
483
485
    def note(self, *args, **kwargs):
484
486
        self.parent.note(*args, **kwargs)
485
487
 
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
 
 
 
488
 
504
489
def str_tdelta(delt):
505
490
    if delt is None:
506
491
        return "-:--:--"
523
508
    if current > total:
524
509
        return None                     # wtf?
525
510
 
526
 
    elapsed = time.time() - start_time
 
511
    elapsed = time.clock() - start_time
527
512
 
528
513
    if elapsed < 2.0:                   # not enough time to estimate
529
514
        return None
530
515
    
531
516
    total_duration = float(elapsed) * float(total) / float(current)
532
517
 
 
518
    assert total_duration >= elapsed
 
519
 
533
520
    if last_updates and len(last_updates) >= n_recent:
534
521
        avg = sum(last_updates) / float(len(last_updates))
535
522
        time_left = avg * (total - current)
556
543
            self.cur_phase = 0
557
544
        else:
558
545
            self.cur_phase += 1
 
546
        assert self.cur_phase < self.total 
559
547
        self.pb.update(self.message, self.cur_phase, self.total)
 
548
 
 
549
 
 
550
def run_tests():
 
551
    import doctest
 
552
    result = doctest.testmod()
 
553
    if result[1] > 0:
 
554
        if result[0] == 0:
 
555
            print "All tests passed"
 
556
    else:
 
557
        print "No tests to run"
 
558
 
 
559
 
 
560
def demo():
 
561
    sleep = time.sleep
 
562
    
 
563
    print 'dumb-terminal test:'
 
564
    pb = DotsProgressBar()
 
565
    for i in range(100):
 
566
        pb.update('Leoparden', i, 99)
 
567
        sleep(0.1)
 
568
    sleep(1.5)
 
569
    pb.clear()
 
570
    sleep(1.5)
 
571
    
 
572
    print 'smart-terminal test:'
 
573
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
 
574
    for i in range(100):
 
575
        pb.update('Elephanten', i, 99)
 
576
        sleep(0.1)
 
577
    sleep(2)
 
578
    pb.clear()
 
579
    sleep(1)
 
580
 
 
581
    print 'done!'
 
582
 
 
583
if __name__ == "__main__":
 
584
    demo()