00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 import sys
00034 import time
00035
00036 class ProgressMeter:
00037 SUFFIXES = { 1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
00038 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] }
00039
00040 def __init__(self, path, bytes_total, refresh_rate=1.0):
00041 self.path = path
00042 self.bytes_total = bytes_total
00043 self.refresh_rate = refresh_rate
00044
00045 self.elapsed = 0.0
00046 self.update_elapsed = 0.0
00047 self.bytes_read = 0.0
00048
00049 self.start_time = time.time()
00050
00051 self._update_progress()
00052
00053 def step(self, bytes_read, force_update=False):
00054 self.bytes_read = bytes_read
00055 self.elapsed = time.time() - self.start_time
00056
00057 if force_update or self.elapsed - self.update_elapsed > self.refresh_rate:
00058 self._update_progress()
00059 self.update_elapsed = self.elapsed
00060
00061 def _update_progress(self):
00062 max_path_len = self.terminal_width() - 36
00063 path = self.path
00064 if len(path) > max_path_len:
00065 path = '...' + self.path[-max_path_len + 3:]
00066
00067 bytes_read_str = self.approximate_size(float(self.bytes_read))
00068 bytes_total_str = self.approximate_size(float(self.bytes_total))
00069
00070 if self.bytes_read < self.bytes_total:
00071 complete_fraction = float(self.bytes_read) / self.bytes_total
00072 pct_complete = int(100.0 * complete_fraction)
00073
00074 if complete_fraction > 0.0:
00075 eta = self.elapsed * (1.0 / complete_fraction - 1.0)
00076 eta_min, eta_sec = eta / 60, eta % 60
00077 if eta_min > 99:
00078 eta_str = '--:--'
00079 else:
00080 eta_str = '%02d:%02d' % (eta_min, eta_sec)
00081 else:
00082 eta_str = '--:--'
00083
00084 progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str)
00085 else:
00086 progress = '%-*s 100%% %19s %02d:%02d ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60)
00087
00088 print '\r', progress,
00089 sys.stdout.flush()
00090
00091 def finish(self):
00092 self.step(self.bytes_total, force_update=True)
00093 print
00094
00095 @classmethod
00096 def approximate_size(cls, size, a_kilobyte_is_1024_bytes=False):
00097 """
00098 Convert file size to human-readable form
00099 """
00100 if size < 0:
00101 raise ValueError('number must be non-negative')
00102
00103 multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
00104 for suffix in cls.SUFFIXES[multiple]:
00105 size /= multiple
00106 if size < multiple:
00107 return '%.1f %s' % (size, suffix)
00108
00109 raise ValueError('number too large')
00110
00111 @staticmethod
00112 def terminal_width():
00113 """
00114 Estimate the width of the terminal
00115 """
00116 width = 0
00117 try:
00118 import struct, fcntl, termios
00119 s = struct.pack('HHHH', 0, 0, 0, 0)
00120 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
00121 width = struct.unpack('HHHH', x)[1]
00122 except IOError:
00123 pass
00124 if width <= 0:
00125 try:
00126 width = int(os.environ['COLUMNS'])
00127 except Exception:
00128 pass
00129 if width <= 0:
00130 width = 80
00131
00132 return width