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