diff options
Diffstat (limited to 'scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py')
-rw-r--r-- | scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py b/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py new file mode 100644 index 0000000000..02db524e76 --- /dev/null +++ b/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py | |||
@@ -0,0 +1,530 @@ | |||
1 | # This library is free software; you can redistribute it and/or | ||
2 | # modify it under the terms of the GNU Lesser General Public | ||
3 | # License as published by the Free Software Foundation; either | ||
4 | # version 2.1 of the License, or (at your option) any later version. | ||
5 | # | ||
6 | # This library is distributed in the hope that it will be useful, | ||
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
9 | # Lesser General Public License for more details. | ||
10 | # | ||
11 | # You should have received a copy of the GNU Lesser General Public | ||
12 | # License along with this library; if not, write to the | ||
13 | # Free Software Foundation, Inc., | ||
14 | # 59 Temple Place, Suite 330, | ||
15 | # Boston, MA 02111-1307 USA | ||
16 | |||
17 | # This file is part of urlgrabber, a high-level cross-protocol url-grabber | ||
18 | # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko | ||
19 | |||
20 | # $Id: progress.py,v 1.7 2005/08/19 21:59:07 mstenner Exp $ | ||
21 | |||
22 | import sys | ||
23 | import time | ||
24 | import math | ||
25 | import thread | ||
26 | |||
27 | class BaseMeter: | ||
28 | def __init__(self): | ||
29 | self.update_period = 0.3 # seconds | ||
30 | |||
31 | self.filename = None | ||
32 | self.url = None | ||
33 | self.basename = None | ||
34 | self.text = None | ||
35 | self.size = None | ||
36 | self.start_time = None | ||
37 | self.last_amount_read = 0 | ||
38 | self.last_update_time = None | ||
39 | self.re = RateEstimator() | ||
40 | |||
41 | def start(self, filename=None, url=None, basename=None, | ||
42 | size=None, now=None, text=None): | ||
43 | self.filename = filename | ||
44 | self.url = url | ||
45 | self.basename = basename | ||
46 | self.text = text | ||
47 | |||
48 | #size = None ######### TESTING | ||
49 | self.size = size | ||
50 | if not size is None: self.fsize = format_number(size) + 'B' | ||
51 | |||
52 | if now is None: now = time.time() | ||
53 | self.start_time = now | ||
54 | self.re.start(size, now) | ||
55 | self.last_amount_read = 0 | ||
56 | self.last_update_time = now | ||
57 | self._do_start(now) | ||
58 | |||
59 | def _do_start(self, now=None): | ||
60 | pass | ||
61 | |||
62 | def update(self, amount_read, now=None): | ||
63 | # for a real gui, you probably want to override and put a call | ||
64 | # to your mainloop iteration function here | ||
65 | if now is None: now = time.time() | ||
66 | if (now >= self.last_update_time + self.update_period) or \ | ||
67 | not self.last_update_time: | ||
68 | self.re.update(amount_read, now) | ||
69 | self.last_amount_read = amount_read | ||
70 | self.last_update_time = now | ||
71 | self._do_update(amount_read, now) | ||
72 | |||
73 | def _do_update(self, amount_read, now=None): | ||
74 | pass | ||
75 | |||
76 | def end(self, amount_read, now=None): | ||
77 | if now is None: now = time.time() | ||
78 | self.re.update(amount_read, now) | ||
79 | self.last_amount_read = amount_read | ||
80 | self.last_update_time = now | ||
81 | self._do_end(amount_read, now) | ||
82 | |||
83 | def _do_end(self, amount_read, now=None): | ||
84 | pass | ||
85 | |||
86 | class TextMeter(BaseMeter): | ||
87 | def __init__(self, fo=sys.stderr): | ||
88 | BaseMeter.__init__(self) | ||
89 | self.fo = fo | ||
90 | |||
91 | def _do_update(self, amount_read, now=None): | ||
92 | etime = self.re.elapsed_time() | ||
93 | fetime = format_time(etime) | ||
94 | fread = format_number(amount_read) | ||
95 | #self.size = None | ||
96 | if self.text is not None: | ||
97 | text = self.text | ||
98 | else: | ||
99 | text = self.basename | ||
100 | if self.size is None: | ||
101 | out = '\r%-60.60s %5sB %s ' % \ | ||
102 | (text, fread, fetime) | ||
103 | else: | ||
104 | rtime = self.re.remaining_time() | ||
105 | frtime = format_time(rtime) | ||
106 | frac = self.re.fraction_read() | ||
107 | bar = '='*int(25 * frac) | ||
108 | |||
109 | out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s ETA ' % \ | ||
110 | (text, frac*100, bar, fread, frtime) | ||
111 | |||
112 | self.fo.write(out) | ||
113 | self.fo.flush() | ||
114 | |||
115 | def _do_end(self, amount_read, now=None): | ||
116 | total_time = format_time(self.re.elapsed_time()) | ||
117 | total_size = format_number(amount_read) | ||
118 | if self.text is not None: | ||
119 | text = self.text | ||
120 | else: | ||
121 | text = self.basename | ||
122 | if self.size is None: | ||
123 | out = '\r%-60.60s %5sB %s ' % \ | ||
124 | (text, total_size, total_time) | ||
125 | else: | ||
126 | bar = '='*25 | ||
127 | out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s ' % \ | ||
128 | (text, 100, bar, total_size, total_time) | ||
129 | self.fo.write(out + '\n') | ||
130 | self.fo.flush() | ||
131 | |||
132 | text_progress_meter = TextMeter | ||
133 | |||
134 | class MultiFileHelper(BaseMeter): | ||
135 | def __init__(self, master): | ||
136 | BaseMeter.__init__(self) | ||
137 | self.master = master | ||
138 | |||
139 | def _do_start(self, now): | ||
140 | self.master.start_meter(self, now) | ||
141 | |||
142 | def _do_update(self, amount_read, now): | ||
143 | # elapsed time since last update | ||
144 | self.master.update_meter(self, now) | ||
145 | |||
146 | def _do_end(self, amount_read, now): | ||
147 | self.ftotal_time = format_time(now - self.start_time) | ||
148 | self.ftotal_size = format_number(self.last_amount_read) | ||
149 | self.master.end_meter(self, now) | ||
150 | |||
151 | def failure(self, message, now=None): | ||
152 | self.master.failure_meter(self, message, now) | ||
153 | |||
154 | def message(self, message): | ||
155 | self.master.message_meter(self, message) | ||
156 | |||
157 | class MultiFileMeter: | ||
158 | helperclass = MultiFileHelper | ||
159 | def __init__(self): | ||
160 | self.meters = [] | ||
161 | self.in_progress_meters = [] | ||
162 | self._lock = thread.allocate_lock() | ||
163 | self.update_period = 0.3 # seconds | ||
164 | |||
165 | self.numfiles = None | ||
166 | self.finished_files = 0 | ||
167 | self.failed_files = 0 | ||
168 | self.open_files = 0 | ||
169 | self.total_size = None | ||
170 | self.failed_size = 0 | ||
171 | self.start_time = None | ||
172 | self.finished_file_size = 0 | ||
173 | self.last_update_time = None | ||
174 | self.re = RateEstimator() | ||
175 | |||
176 | def start(self, numfiles=None, total_size=None, now=None): | ||
177 | if now is None: now = time.time() | ||
178 | self.numfiles = numfiles | ||
179 | self.finished_files = 0 | ||
180 | self.failed_files = 0 | ||
181 | self.open_files = 0 | ||
182 | self.total_size = total_size | ||
183 | self.failed_size = 0 | ||
184 | self.start_time = now | ||
185 | self.finished_file_size = 0 | ||
186 | self.last_update_time = now | ||
187 | self.re.start(total_size, now) | ||
188 | self._do_start(now) | ||
189 | |||
190 | def _do_start(self, now): | ||
191 | pass | ||
192 | |||
193 | def end(self, now=None): | ||
194 | if now is None: now = time.time() | ||
195 | self._do_end(now) | ||
196 | |||
197 | def _do_end(self, now): | ||
198 | pass | ||
199 | |||
200 | def lock(self): self._lock.acquire() | ||
201 | def unlock(self): self._lock.release() | ||
202 | |||
203 | ########################################################### | ||
204 | # child meter creation and destruction | ||
205 | def newMeter(self): | ||
206 | newmeter = self.helperclass(self) | ||
207 | self.meters.append(newmeter) | ||
208 | return newmeter | ||
209 | |||
210 | def removeMeter(self, meter): | ||
211 | self.meters.remove(meter) | ||
212 | |||
213 | ########################################################### | ||
214 | # child functions - these should only be called by helpers | ||
215 | def start_meter(self, meter, now): | ||
216 | if not meter in self.meters: | ||
217 | raise ValueError('attempt to use orphaned meter') | ||
218 | self._lock.acquire() | ||
219 | try: | ||
220 | if not meter in self.in_progress_meters: | ||
221 | self.in_progress_meters.append(meter) | ||
222 | self.open_files += 1 | ||
223 | finally: | ||
224 | self._lock.release() | ||
225 | self._do_start_meter(meter, now) | ||
226 | |||
227 | def _do_start_meter(self, meter, now): | ||
228 | pass | ||
229 | |||
230 | def update_meter(self, meter, now): | ||
231 | if not meter in self.meters: | ||
232 | raise ValueError('attempt to use orphaned meter') | ||
233 | if (now >= self.last_update_time + self.update_period) or \ | ||
234 | not self.last_update_time: | ||
235 | self.re.update(self._amount_read(), now) | ||
236 | self.last_update_time = now | ||
237 | self._do_update_meter(meter, now) | ||
238 | |||
239 | def _do_update_meter(self, meter, now): | ||
240 | pass | ||
241 | |||
242 | def end_meter(self, meter, now): | ||
243 | if not meter in self.meters: | ||
244 | raise ValueError('attempt to use orphaned meter') | ||
245 | self._lock.acquire() | ||
246 | try: | ||
247 | try: self.in_progress_meters.remove(meter) | ||
248 | except ValueError: pass | ||
249 | self.open_files -= 1 | ||
250 | self.finished_files += 1 | ||
251 | self.finished_file_size += meter.last_amount_read | ||
252 | finally: | ||
253 | self._lock.release() | ||
254 | self._do_end_meter(meter, now) | ||
255 | |||
256 | def _do_end_meter(self, meter, now): | ||
257 | pass | ||
258 | |||
259 | def failure_meter(self, meter, message, now): | ||
260 | if not meter in self.meters: | ||
261 | raise ValueError('attempt to use orphaned meter') | ||
262 | self._lock.acquire() | ||
263 | try: | ||
264 | try: self.in_progress_meters.remove(meter) | ||
265 | except ValueError: pass | ||
266 | self.open_files -= 1 | ||
267 | self.failed_files += 1 | ||
268 | if meter.size and self.failed_size is not None: | ||
269 | self.failed_size += meter.size | ||
270 | else: | ||
271 | self.failed_size = None | ||
272 | finally: | ||
273 | self._lock.release() | ||
274 | self._do_failure_meter(meter, message, now) | ||
275 | |||
276 | def _do_failure_meter(self, meter, message, now): | ||
277 | pass | ||
278 | |||
279 | def message_meter(self, meter, message): | ||
280 | pass | ||
281 | |||
282 | ######################################################## | ||
283 | # internal functions | ||
284 | def _amount_read(self): | ||
285 | tot = self.finished_file_size | ||
286 | for m in self.in_progress_meters: | ||
287 | tot += m.last_amount_read | ||
288 | return tot | ||
289 | |||
290 | |||
291 | class TextMultiFileMeter(MultiFileMeter): | ||
292 | def __init__(self, fo=sys.stderr): | ||
293 | self.fo = fo | ||
294 | MultiFileMeter.__init__(self) | ||
295 | |||
296 | # files: ###/### ###% data: ######/###### ###% time: ##:##:##/##:##:## | ||
297 | def _do_update_meter(self, meter, now): | ||
298 | self._lock.acquire() | ||
299 | try: | ||
300 | format = "files: %3i/%-3i %3i%% data: %6.6s/%-6.6s %3i%% " \ | ||
301 | "time: %8.8s/%8.8s" | ||
302 | df = self.finished_files | ||
303 | tf = self.numfiles or 1 | ||
304 | pf = 100 * float(df)/tf + 0.49 | ||
305 | dd = self.re.last_amount_read | ||
306 | td = self.total_size | ||
307 | pd = 100 * (self.re.fraction_read() or 0) + 0.49 | ||
308 | dt = self.re.elapsed_time() | ||
309 | rt = self.re.remaining_time() | ||
310 | if rt is None: tt = None | ||
311 | else: tt = dt + rt | ||
312 | |||
313 | fdd = format_number(dd) + 'B' | ||
314 | ftd = format_number(td) + 'B' | ||
315 | fdt = format_time(dt, 1) | ||
316 | ftt = format_time(tt, 1) | ||
317 | |||
318 | out = '%-79.79s' % (format % (df, tf, pf, fdd, ftd, pd, fdt, ftt)) | ||
319 | self.fo.write('\r' + out) | ||
320 | self.fo.flush() | ||
321 | finally: | ||
322 | self._lock.release() | ||
323 | |||
324 | def _do_end_meter(self, meter, now): | ||
325 | self._lock.acquire() | ||
326 | try: | ||
327 | format = "%-30.30s %6.6s %8.8s %9.9s" | ||
328 | fn = meter.basename | ||
329 | size = meter.last_amount_read | ||
330 | fsize = format_number(size) + 'B' | ||
331 | et = meter.re.elapsed_time() | ||
332 | fet = format_time(et, 1) | ||
333 | frate = format_number(size / et) + 'B/s' | ||
334 | |||
335 | out = '%-79.79s' % (format % (fn, fsize, fet, frate)) | ||
336 | self.fo.write('\r' + out + '\n') | ||
337 | finally: | ||
338 | self._lock.release() | ||
339 | self._do_update_meter(meter, now) | ||
340 | |||
341 | def _do_failure_meter(self, meter, message, now): | ||
342 | self._lock.acquire() | ||
343 | try: | ||
344 | format = "%-30.30s %6.6s %s" | ||
345 | fn = meter.basename | ||
346 | if type(message) in (type(''), type(u'')): | ||
347 | message = message.splitlines() | ||
348 | if not message: message = [''] | ||
349 | out = '%-79s' % (format % (fn, 'FAILED', message[0] or '')) | ||
350 | self.fo.write('\r' + out + '\n') | ||
351 | for m in message[1:]: self.fo.write(' ' + m + '\n') | ||
352 | self._lock.release() | ||
353 | finally: | ||
354 | self._do_update_meter(meter, now) | ||
355 | |||
356 | def message_meter(self, meter, message): | ||
357 | self._lock.acquire() | ||
358 | try: | ||
359 | pass | ||
360 | finally: | ||
361 | self._lock.release() | ||
362 | |||
363 | def _do_end(self, now): | ||
364 | self._do_update_meter(None, now) | ||
365 | self._lock.acquire() | ||
366 | try: | ||
367 | self.fo.write('\n') | ||
368 | self.fo.flush() | ||
369 | finally: | ||
370 | self._lock.release() | ||
371 | |||
372 | ###################################################################### | ||
373 | # support classes and functions | ||
374 | |||
375 | class RateEstimator: | ||
376 | def __init__(self, timescale=5.0): | ||
377 | self.timescale = timescale | ||
378 | |||
379 | def start(self, total=None, now=None): | ||
380 | if now is None: now = time.time() | ||
381 | self.total = total | ||
382 | self.start_time = now | ||
383 | self.last_update_time = now | ||
384 | self.last_amount_read = 0 | ||
385 | self.ave_rate = None | ||
386 | |||
387 | def update(self, amount_read, now=None): | ||
388 | if now is None: now = time.time() | ||
389 | if amount_read == 0: | ||
390 | # if we just started this file, all bets are off | ||
391 | self.last_update_time = now | ||
392 | self.last_amount_read = 0 | ||
393 | self.ave_rate = None | ||
394 | return | ||
395 | |||
396 | #print 'times', now, self.last_update_time | ||
397 | time_diff = now - self.last_update_time | ||
398 | read_diff = amount_read - self.last_amount_read | ||
399 | self.last_update_time = now | ||
400 | self.last_amount_read = amount_read | ||
401 | self.ave_rate = self._temporal_rolling_ave(\ | ||
402 | time_diff, read_diff, self.ave_rate, self.timescale) | ||
403 | #print 'results', time_diff, read_diff, self.ave_rate | ||
404 | |||
405 | ##################################################################### | ||
406 | # result methods | ||
407 | def average_rate(self): | ||
408 | "get the average transfer rate (in bytes/second)" | ||
409 | return self.ave_rate | ||
410 | |||
411 | def elapsed_time(self): | ||
412 | "the time between the start of the transfer and the most recent update" | ||
413 | return self.last_update_time - self.start_time | ||
414 | |||
415 | def remaining_time(self): | ||
416 | "estimated time remaining" | ||
417 | if not self.ave_rate or not self.total: return None | ||
418 | return (self.total - self.last_amount_read) / self.ave_rate | ||
419 | |||
420 | def fraction_read(self): | ||
421 | """the fraction of the data that has been read | ||
422 | (can be None for unknown transfer size)""" | ||
423 | if self.total is None: return None | ||
424 | elif self.total == 0: return 1.0 | ||
425 | else: return float(self.last_amount_read)/self.total | ||
426 | |||
427 | ######################################################################### | ||
428 | # support methods | ||
429 | def _temporal_rolling_ave(self, time_diff, read_diff, last_ave, timescale): | ||
430 | """a temporal rolling average performs smooth averaging even when | ||
431 | updates come at irregular intervals. This is performed by scaling | ||
432 | the "epsilon" according to the time since the last update. | ||
433 | Specifically, epsilon = time_diff / timescale | ||
434 | |||
435 | As a general rule, the average will take on a completely new value | ||
436 | after 'timescale' seconds.""" | ||
437 | epsilon = time_diff / timescale | ||
438 | if epsilon > 1: epsilon = 1.0 | ||
439 | return self._rolling_ave(time_diff, read_diff, last_ave, epsilon) | ||
440 | |||
441 | def _rolling_ave(self, time_diff, read_diff, last_ave, epsilon): | ||
442 | """perform a "rolling average" iteration | ||
443 | a rolling average "folds" new data into an existing average with | ||
444 | some weight, epsilon. epsilon must be between 0.0 and 1.0 (inclusive) | ||
445 | a value of 0.0 means only the old value (initial value) counts, | ||
446 | and a value of 1.0 means only the newest value is considered.""" | ||
447 | |||
448 | try: | ||
449 | recent_rate = read_diff / time_diff | ||
450 | except ZeroDivisionError: | ||
451 | recent_rate = None | ||
452 | if last_ave is None: return recent_rate | ||
453 | elif recent_rate is None: return last_ave | ||
454 | |||
455 | # at this point, both last_ave and recent_rate are numbers | ||
456 | return epsilon * recent_rate + (1 - epsilon) * last_ave | ||
457 | |||
458 | def _round_remaining_time(self, rt, start_time=15.0): | ||
459 | """round the remaining time, depending on its size | ||
460 | If rt is between n*start_time and (n+1)*start_time round downward | ||
461 | to the nearest multiple of n (for any counting number n). | ||
462 | If rt < start_time, round down to the nearest 1. | ||
463 | For example (for start_time = 15.0): | ||
464 | 2.7 -> 2.0 | ||
465 | 25.2 -> 25.0 | ||
466 | 26.4 -> 26.0 | ||
467 | 35.3 -> 34.0 | ||
468 | 63.6 -> 60.0 | ||
469 | """ | ||
470 | |||
471 | if rt < 0: return 0.0 | ||
472 | shift = int(math.log(rt/start_time)/math.log(2)) | ||
473 | rt = int(rt) | ||
474 | if shift <= 0: return rt | ||
475 | return float(int(rt) >> shift << shift) | ||
476 | |||
477 | |||
478 | def format_time(seconds, use_hours=0): | ||
479 | if seconds is None or seconds < 0: | ||
480 | if use_hours: return '--:--:--' | ||
481 | else: return '--:--' | ||
482 | else: | ||
483 | seconds = int(seconds) | ||
484 | minutes = seconds / 60 | ||
485 | seconds = seconds % 60 | ||
486 | if use_hours: | ||
487 | hours = minutes / 60 | ||
488 | minutes = minutes % 60 | ||
489 | return '%02i:%02i:%02i' % (hours, minutes, seconds) | ||
490 | else: | ||
491 | return '%02i:%02i' % (minutes, seconds) | ||
492 | |||
493 | def format_number(number, SI=0, space=' '): | ||
494 | """Turn numbers into human-readable metric-like numbers""" | ||
495 | symbols = ['', # (none) | ||
496 | 'k', # kilo | ||
497 | 'M', # mega | ||
498 | 'G', # giga | ||
499 | 'T', # tera | ||
500 | 'P', # peta | ||
501 | 'E', # exa | ||
502 | 'Z', # zetta | ||
503 | 'Y'] # yotta | ||
504 | |||
505 | if SI: step = 1000.0 | ||
506 | else: step = 1024.0 | ||
507 | |||
508 | thresh = 999 | ||
509 | depth = 0 | ||
510 | max_depth = len(symbols) - 1 | ||
511 | |||
512 | # we want numbers between 0 and thresh, but don't exceed the length | ||
513 | # of our list. In that event, the formatting will be screwed up, | ||
514 | # but it'll still show the right number. | ||
515 | while number > thresh and depth < max_depth: | ||
516 | depth = depth + 1 | ||
517 | number = number / step | ||
518 | |||
519 | if type(number) == type(1) or type(number) == type(1L): | ||
520 | # it's an int or a long, which means it didn't get divided, | ||
521 | # which means it's already short enough | ||
522 | format = '%i%s%s' | ||
523 | elif number < 9.95: | ||
524 | # must use 9.95 for proper sizing. For example, 9.99 will be | ||
525 | # rounded to 10.0 with the .1f format string (which is too long) | ||
526 | format = '%.1f%s%s' | ||
527 | else: | ||
528 | format = '%.0f%s%s' | ||
529 | |||
530 | return(format % (float(number or 0), space, symbols[depth])) | ||