Gentoo Archives: gentoo-commits

From: "André Erdmann" <dywi@×××××××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/R_overlay:master commit in: roverlay/util/, roverlay/remote/
Date: Sat, 22 Feb 2014 14:56:03
Message-Id: 1392569055.ca1020e87ff4421499df80c1d1af9e6c33f9afe2.dywi@gentoo
1 commit: ca1020e87ff4421499df80c1d1af9e6c33f9afe2
2 Author: André Erdmann <dywi <AT> mailerd <DOT> de>
3 AuthorDate: Sun Feb 16 16:44:15 2014 +0000
4 Commit: André Erdmann <dywi <AT> mailerd <DOT> de>
5 CommitDate: Sun Feb 16 16:44:15 2014 +0000
6 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=ca1020e8
7
8 roverlay/remote/websync: show download status
9
10 ---
11 roverlay/remote/websync.py | 39 ++++++--
12 roverlay/util/progressbar.py | 210 +++++++++++++++++++++++++++++++++++++++++++
13 2 files changed, 242 insertions(+), 7 deletions(-)
14
15 diff --git a/roverlay/remote/websync.py b/roverlay/remote/websync.py
16 index 24f5cd5..06689ad 100644
17 --- a/roverlay/remote/websync.py
18 +++ b/roverlay/remote/websync.py
19 @@ -4,6 +4,7 @@
20 # Distributed under the terms of the GNU General Public License;
21 # either version 2 of the License, or (at your option) any later version.
22
23 +from __future__ import division
24 from __future__ import print_function
25
26 """websync, sync packages via http"""
27 @@ -31,6 +32,7 @@ HTTPError = _urllib_error.HTTPError
28
29 from roverlay import config, digest, util
30 from roverlay.remote.basicrepo import BasicRepo
31 +from roverlay.util.progressbar import DownloadProgressBar, NullProgressBar
32
33 # number of sync retries
34 # changed 2014-02-15: does no longer include the first run
35 @@ -50,6 +52,15 @@ class WebsyncBase ( BasicRepo ):
36 HTTP_ERROR_RETRY_CODES = frozenset ({ 404, 410, 500, 503 })
37 URL_ERROR_RETRY_CODES = frozenset ({ errno.ETIMEDOUT, })
38 RETRY_ON_TIMEOUT = True
39 + PROGRESS_BAR_CLS = None
40 +
41 + def __new__ ( cls, *args, **kwargs ):
42 + if cls.PROGRESS_BAR_CLS is None:
43 + cls.PROGRESS_BAR_CLS = (
44 + DownloadProgressBar if VERBOSE else NullProgressBar
45 + )
46 + return super ( WebsyncBase, cls ).__new__ ( cls )
47 + # --- end of __new__ (...) ---
48
49 def __init__ ( self,
50 name,
51 @@ -170,27 +181,41 @@ class WebsyncBase ( BasicRepo ):
52 bytes_fetched = 0
53 assert blocksize
54
55 - # FIXME: debug print (?)
56 - if VERBOSE:
57 - print (
58 - "Fetching {f} from {u} ...".format ( f=package_file, u=src_uri )
59 - )
60 -
61 # unlink the existing file first (if it exists)
62 # this is necessary for keeping hardlinks intact (-> package mirror)
63 util.try_unlink ( distfile )
64
65 - with open ( distfile, mode='wb' ) as fh:
66 + with \
67 + open ( distfile, mode='wb' ) as fh, \
68 + self.PROGRESS_BAR_CLS (
69 + package_file.ljust(50), expected_filesize
70 + ) as progress_bar:
71 +
72 + progress_bar.update ( 0 )
73 block = webh.read ( blocksize )
74 +
75 while block:
76 # write block to file
77 fh.write ( block )
78 # ? bytelen
79 bytes_fetched += len ( block )
80
81 + # update progress bar on every 4th block
82 + # blocks_fetched := math.ceil ( bytes_fetched / blocksize )
83 + #
84 + # Usually, only the last block's size is <= blocksize,
85 + # so floordiv is sufficient here
86 + # (the progress bar gets updated for the last block anyway)
87 + #
88 + if 0 == ( bytes_fetched // blocksize ) % 4:
89 + progress_bar.update ( bytes_fetched )
90 +
91 # get the next block
92 block = webh.read ( blocksize )
93 # -- end while
94 +
95 + # final progress bar update (before closing the file)
96 + progress_bar.update ( bytes_fetched )
97 # -- with
98
99 if bytes_fetched == expected_filesize:
100
101 diff --git a/roverlay/util/progressbar.py b/roverlay/util/progressbar.py
102 new file mode 100644
103 index 0000000..24c934e
104 --- /dev/null
105 +++ b/roverlay/util/progressbar.py
106 @@ -0,0 +1,210 @@
107 +# R overlay -- util, progressbar
108 +# -*- coding: utf-8 -*-
109 +# Copyright (C) 2014 André Erdmann <dywi@×××××××.de>
110 +# Distributed under the terms of the GNU General Public License;
111 +# either version 2 of the License, or (at your option) any later version.
112 +from __future__ import division
113 +
114 +import abc
115 +import sys
116 +
117 +import roverlay.util.objects
118 +
119 +
120 +class AbstractProgressBarBase ( roverlay.util.objects.AbstractObject ):
121 + """Abstract base class for progress bars."""
122 +
123 + @abc.abstractmethod
124 + def setup ( self, *args, **kwargs ):
125 + """Initialization code for __init__() and reset().
126 +
127 + Returns: None
128 +
129 + arguments:
130 + * *args, **kwargs -- progress bar data
131 + """
132 + pass
133 + # --- end of setup (...) ---
134 +
135 + def reset ( self, *args, **kwargs ):
136 + """Finalizes the current progress bar and resets it afterwards.
137 +
138 + Returns: None
139 +
140 + arguments:
141 + * *args, **kwargs -- passed to setup()
142 + """
143 + self.print_newline()
144 + self.setup ( *args, **kwargs )
145 + # --- end of reset (...) ---
146 +
147 + def __init__ ( self, *args, **kwargs ):
148 + """Initializes a progress bar instance by calling its setup() method.
149 +
150 + arguments:
151 + * *args, **kwargs -- passed to setup()
152 + """
153 + super ( AbstractProgressBarBase, self ).__init__()
154 + self.setup ( *args, **kwargs )
155 + # --- end of __init__ (...) ---
156 +
157 + @abc.abstractmethod
158 + def write ( self, message ):
159 + """(Over-)writes the progress bar, using the given message.
160 +
161 + Note: message should not contain newline chars.
162 +
163 + Returns: None
164 +
165 + arguments:
166 + * message --
167 + """
168 + raise NotImplementedError()
169 + # --- end of write (...) ---
170 +
171 + @abc.abstractmethod
172 + def print_newline ( self ):
173 + """
174 + Finalizes the current progress bar, usually by printing a newline char.
175 +
176 + Returns: None
177 + """
178 + raise NotImplementedError()
179 + # --- end of print_newline (...) ---
180 +
181 + @abc.abstractmethod
182 + def update ( self, *args, **kwargs ):
183 + """Updates the progress bar using the given data.
184 +
185 + Returns: None
186 +
187 + arguments:
188 + * *args, **kwargs -- not specified by this class
189 + """
190 + raise NotImplementedError()
191 + # --- end of update (...) ---
192 +
193 + def __enter__ ( self ):
194 + # "with"-statement, setup code
195 + return self
196 +
197 + def __exit__ ( self, _type, value, traceback ):
198 + # "with"-statement, teardown code
199 + self.print_newline()
200 +
201 +# --- end of AbstractProgressBarBase ---
202 +
203 +
204 +class AbstractProgressBar ( AbstractProgressBarBase ):
205 + """
206 + Abstract base class for progress bars that write to a stream, e.g. stdout.
207 + """
208 +
209 + CARRIAGE_RET_CHR = chr(13)
210 + #BACKSPACE_CHR = chr(8)
211 +
212 + def setup ( self, stream=None ):
213 + self.stream = ( sys.stdout if stream is None else stream )
214 + # --- end of __init__ (...) ---
215 +
216 + def write ( self, message ):
217 + self.stream.write ( self.CARRIAGE_RET_CHR + message )
218 + self.stream.flush()
219 + # --- end of write (...) ---
220 +
221 + def print_newline ( self ):
222 + self.stream.write ( "\n" )
223 + self.stream.flush()
224 + # --- end of print_newline (...) ---
225 +
226 +# --- end of AbstractProgressBar ---
227 +
228 +
229 +class AbstractPercentageProgressBar ( AbstractProgressBar ):
230 + """Base class for displaying progress as percentage 0.00%..100.00%."""
231 + # not a real progress bar, just a progress indicator
232 +
233 + # str for formatting the percentage
234 + # by default, reserve space for 7 chars ("ddd.dd%")
235 + # might be set by derived classes and/or instances
236 + PERCENTAGE_FMT = "{:>7.2%}"
237 +
238 + def setup ( self, message_header=None, stream=None ):
239 + super ( AbstractPercentageProgressBar, self ).setup ( stream=stream )
240 + self.message_header = message_header
241 + # --- end of setup (...) ---
242 +
243 + @abc.abstractmethod
244 + def get_percentage ( self, *args, **kwargs ):
245 + """Returns a float or int expressing a percentage.
246 +
247 + Any value < 0 is interpreted as "UNKNOWN".
248 +
249 + arguments:
250 + * *args, **kwargs -- progress information (from update())
251 + """
252 + raise NotImplementedError()
253 + # --- end of get_percentage (...) ---
254 +
255 + def _update ( self, percentage ):
256 + if self.message_header:
257 + message = str(self.message_header) + " "
258 + else:
259 + message = ""
260 +
261 + if percentage < 0:
262 + message += "UNKNOWN"
263 + else:
264 + message += self.PERCENTAGE_FMT.format ( percentage )
265 +
266 + self.write ( message )
267 + # --- end of _update (...) ---
268 +
269 + def update ( self, *args, **kwargs ):
270 + self._update ( self.get_percentage ( *args, **kwargs ) )
271 + # --- end of update (...) ---
272 +
273 +# --- end of AbstractPercentageProgressBar ---
274 +
275 +
276 +class NullProgressBar ( AbstractProgressBarBase ):
277 + """A progress bar that discards any information."""
278 +
279 + def setup ( self, *args, **kwargs ):
280 + pass
281 +
282 + def write ( self, *args, **kwargs ):
283 + pass
284 +
285 + def print_newline ( self, *args, **kwargs ):
286 + pass
287 +
288 + def update ( self, *args, **kwargs ):
289 + pass
290 +
291 +# --- end of NullProgressBar ---
292 +
293 +
294 +class DownloadProgressBar ( AbstractPercentageProgressBar ):
295 + """A progress bar for file transfers,
296 + expressing a percentage "bytes transferred / total size".
297 +
298 + Note:
299 + update() shouldn't be called too often as writing to console is rather slow
300 + """
301 +
302 + def setup ( self, filename=None, filesize=None, stream=None ):
303 + super ( DownloadProgressBar, self ).setup (
304 + message_header = (
305 + ( "Fetching " + str(filename) ) if filename else None
306 + ),
307 + stream = stream
308 + )
309 + self.filesize = filesize
310 + # --- end of setup (...) ---
311 +
312 + def get_percentage ( self, current_filesize ):
313 + return ( current_filesize / self.filesize ) if self.filesize else -1.0
314 + # --- end of get_percentage (...) ---
315 +
316 +# --- end of DownloadProgressBar ---