##// END OF EJS Templates
progress: extract stubs to restart ferr.flush() and .write() on EINTR
Yuya Nishihara -
r32048:c3ef33fd default
parent child Browse files
Show More
@@ -1,255 +1,261 b''
1 1 # progress.py progress bars related code
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import threading
11 11 import time
12 12
13 13 from .i18n import _
14 14 from . import encoding
15 15
16 16 def spacejoin(*args):
17 17 return ' '.join(s for s in args if s)
18 18
19 19 def shouldprint(ui):
20 20 return not (ui.quiet or ui.plain('progress')) and (
21 21 ui._isatty(ui.ferr) or ui.configbool('progress', 'assume-tty'))
22 22
23 23 def fmtremaining(seconds):
24 24 """format a number of remaining seconds in human readable way
25 25
26 26 This will properly display seconds, minutes, hours, days if needed"""
27 27 if seconds < 60:
28 28 # i18n: format XX seconds as "XXs"
29 29 return _("%02ds") % (seconds)
30 30 minutes = seconds // 60
31 31 if minutes < 60:
32 32 seconds -= minutes * 60
33 33 # i18n: format X minutes and YY seconds as "XmYYs"
34 34 return _("%dm%02ds") % (minutes, seconds)
35 35 # we're going to ignore seconds in this case
36 36 minutes += 1
37 37 hours = minutes // 60
38 38 minutes -= hours * 60
39 39 if hours < 30:
40 40 # i18n: format X hours and YY minutes as "XhYYm"
41 41 return _("%dh%02dm") % (hours, minutes)
42 42 # we're going to ignore minutes in this case
43 43 hours += 1
44 44 days = hours // 24
45 45 hours -= days * 24
46 46 if days < 15:
47 47 # i18n: format X days and YY hours as "XdYYh"
48 48 return _("%dd%02dh") % (days, hours)
49 49 # we're going to ignore hours in this case
50 50 days += 1
51 51 weeks = days // 7
52 52 days -= weeks * 7
53 53 if weeks < 55:
54 54 # i18n: format X weeks and YY days as "XwYYd"
55 55 return _("%dw%02dd") % (weeks, days)
56 56 # we're going to ignore days and treat a year as 52 weeks
57 57 weeks += 1
58 58 years = weeks // 52
59 59 weeks -= years * 52
60 60 # i18n: format X years and YY weeks as "XyYYw"
61 61 return _("%dy%02dw") % (years, weeks)
62 62
63 63 class progbar(object):
64 64 def __init__(self, ui):
65 65 self.ui = ui
66 66 self._refreshlock = threading.Lock()
67 67 self.resetstate()
68 68
69 69 def resetstate(self):
70 70 self.topics = []
71 71 self.topicstates = {}
72 72 self.starttimes = {}
73 73 self.startvals = {}
74 74 self.printed = False
75 75 self.lastprint = time.time() + float(self.ui.config(
76 76 'progress', 'delay', default=3))
77 77 self.curtopic = None
78 78 self.lasttopic = None
79 79 self.indetcount = 0
80 80 self.refresh = float(self.ui.config(
81 81 'progress', 'refresh', default=0.1))
82 82 self.changedelay = max(3 * self.refresh,
83 83 float(self.ui.config(
84 84 'progress', 'changedelay', default=1)))
85 85 self.order = self.ui.configlist(
86 86 'progress', 'format',
87 87 default=['topic', 'bar', 'number', 'estimate'])
88 88
89 89 def show(self, now, topic, pos, item, unit, total):
90 90 if not shouldprint(self.ui):
91 91 return
92 92 termwidth = self.width()
93 93 self.printed = True
94 94 head = ''
95 95 needprogress = False
96 96 tail = ''
97 97 for indicator in self.order:
98 98 add = ''
99 99 if indicator == 'topic':
100 100 add = topic
101 101 elif indicator == 'number':
102 102 if total:
103 103 add = ('% ' + str(len(str(total))) +
104 104 's/%s') % (pos, total)
105 105 else:
106 106 add = str(pos)
107 107 elif indicator.startswith('item') and item:
108 108 slice = 'end'
109 109 if '-' in indicator:
110 110 wid = int(indicator.split('-')[1])
111 111 elif '+' in indicator:
112 112 slice = 'beginning'
113 113 wid = int(indicator.split('+')[1])
114 114 else:
115 115 wid = 20
116 116 if slice == 'end':
117 117 add = encoding.trim(item, wid, leftside=True)
118 118 else:
119 119 add = encoding.trim(item, wid)
120 120 add += (wid - encoding.colwidth(add)) * ' '
121 121 elif indicator == 'bar':
122 122 add = ''
123 123 needprogress = True
124 124 elif indicator == 'unit' and unit:
125 125 add = unit
126 126 elif indicator == 'estimate':
127 127 add = self.estimate(topic, pos, total, now)
128 128 elif indicator == 'speed':
129 129 add = self.speed(topic, pos, unit, now)
130 130 if not needprogress:
131 131 head = spacejoin(head, add)
132 132 else:
133 133 tail = spacejoin(tail, add)
134 134 if needprogress:
135 135 used = 0
136 136 if head:
137 137 used += encoding.colwidth(head) + 1
138 138 if tail:
139 139 used += encoding.colwidth(tail) + 1
140 140 progwidth = termwidth - used - 3
141 141 if total and pos <= total:
142 142 amt = pos * progwidth // total
143 143 bar = '=' * (amt - 1)
144 144 if amt > 0:
145 145 bar += '>'
146 146 bar += ' ' * (progwidth - amt)
147 147 else:
148 148 progwidth -= 3
149 149 self.indetcount += 1
150 150 # mod the count by twice the width so we can make the
151 151 # cursor bounce between the right and left sides
152 152 amt = self.indetcount % (2 * progwidth)
153 153 amt -= progwidth
154 154 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
155 155 ' ' * int(abs(amt)))
156 156 prog = ''.join(('[', bar , ']'))
157 157 out = spacejoin(head, prog, tail)
158 158 else:
159 159 out = spacejoin(head, tail)
160 self.ui.ferr.write('\r' + encoding.trim(out, termwidth))
160 self._writeerr('\r' + encoding.trim(out, termwidth))
161 161 self.lasttopic = topic
162 self.ui.ferr.flush()
162 self._flusherr()
163 163
164 164 def clear(self):
165 165 if not self.printed or not self.lastprint or not shouldprint(self.ui):
166 166 return
167 self.ui.ferr.write('\r%s\r' % (' ' * self.width()))
167 self._writeerr('\r%s\r' % (' ' * self.width()))
168 168 if self.printed:
169 169 # force immediate re-paint of progress bar
170 170 self.lastprint = 0
171 171
172 172 def complete(self):
173 173 if not shouldprint(self.ui):
174 174 return
175 175 if self.ui.configbool('progress', 'clear-complete', default=True):
176 176 self.clear()
177 177 else:
178 self.ui.ferr.write('\n')
178 self._writeerr('\n')
179 self._flusherr()
180
181 def _flusherr(self):
179 182 self.ui.ferr.flush()
180 183
184 def _writeerr(self, msg):
185 self.ui.ferr.write(msg)
186
181 187 def width(self):
182 188 tw = self.ui.termwidth()
183 189 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
184 190
185 191 def estimate(self, topic, pos, total, now):
186 192 if total is None:
187 193 return ''
188 194 initialpos = self.startvals[topic]
189 195 target = total - initialpos
190 196 delta = pos - initialpos
191 197 if delta > 0:
192 198 elapsed = now - self.starttimes[topic]
193 199 # experimental config: progress.estimate
194 200 if elapsed > float(
195 201 self.ui.config('progress', 'estimate', default=2)):
196 202 seconds = (elapsed * (target - delta)) // delta + 1
197 203 return fmtremaining(seconds)
198 204 return ''
199 205
200 206 def speed(self, topic, pos, unit, now):
201 207 initialpos = self.startvals[topic]
202 208 delta = pos - initialpos
203 209 elapsed = now - self.starttimes[topic]
204 210 if elapsed > float(
205 211 self.ui.config('progress', 'estimate', default=2)):
206 212 return _('%d %s/sec') % (delta / elapsed, unit)
207 213 return ''
208 214
209 215 def _oktoprint(self, now):
210 216 '''Check if conditions are met to print - e.g. changedelay elapsed'''
211 217 if (self.lasttopic is None # first time we printed
212 218 # not a topic change
213 219 or self.curtopic == self.lasttopic
214 220 # it's been long enough we should print anyway
215 221 or now - self.lastprint >= self.changedelay):
216 222 return True
217 223 else:
218 224 return False
219 225
220 226 def progress(self, topic, pos, item='', unit='', total=None):
221 227 now = time.time()
222 228 self._refreshlock.acquire()
223 229 try:
224 230 if pos is None:
225 231 self.starttimes.pop(topic, None)
226 232 self.startvals.pop(topic, None)
227 233 self.topicstates.pop(topic, None)
228 234 # reset the progress bar if this is the outermost topic
229 235 if self.topics and self.topics[0] == topic and self.printed:
230 236 self.complete()
231 237 self.resetstate()
232 238 # truncate the list of topics assuming all topics within
233 239 # this one are also closed
234 240 if topic in self.topics:
235 241 self.topics = self.topics[:self.topics.index(topic)]
236 242 # reset the last topic to the one we just unwound to,
237 243 # so that higher-level topics will be stickier than
238 244 # lower-level topics
239 245 if self.topics:
240 246 self.lasttopic = self.topics[-1]
241 247 else:
242 248 self.lasttopic = None
243 249 else:
244 250 if topic not in self.topics:
245 251 self.starttimes[topic] = now
246 252 self.startvals[topic] = pos
247 253 self.topics.append(topic)
248 254 self.topicstates[topic] = pos, item, unit, total
249 255 self.curtopic = topic
250 256 if now - self.lastprint >= self.refresh and self.topics:
251 257 if self._oktoprint(now):
252 258 self.lastprint = now
253 259 self.show(now, topic, *self.topicstates[topic])
254 260 finally:
255 261 self._refreshlock.release()
General Comments 0
You need to be logged in to leave comments. Login now