1 /* cclive
2 * Copyright (C) 2010-2013 Toni Gundogdu <legatvs@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <ccinternal>
19
20 #include <iomanip>
21 #include <cstdio>
22 #include <ctime>
23
24 #ifdef HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif
27
28 #ifdef HAVE_SYS_TYPES_H
29 #include <sys/types.h>
30 #endif
31
32 #ifdef HAVE_SIGNAL_H
33 #include <signal.h>
34 #endif
35
36 #ifdef HAVE_SYS_IOCTL_H
37 #include <sys/ioctl.h>
38 #endif
39
40 #include <boost/date_time/posix_time/posix_time.hpp>
41 #include <boost/program_options/variables_map.hpp>
42 #include <boost/filesystem.hpp>
43
44 #include <ccoptions>
45 #include <ccquvi>
46 #include <ccfile>
47 #include <cclog>
48 #include <ccprogressbar>
49
50 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
51 #define WITH_RESIZE
52 #endif
53
54 namespace cc
55 {
56 #ifdef WITH_RESIZE
57 static volatile sig_atomic_t recv_sigwinch;
58
handle_sigwinch(int s)59 static void handle_sigwinch(int s)
60 {
61 recv_sigwinch = 1;
62 }
63
get_term_width()64 static size_t get_term_width()
65 {
66 const int fd = fileno(stderr);
67
68 winsize wsz;
69
70 if (ioctl(fd, TIOCGWINSZ, &wsz) < 0)
71 return 0;
72
73 return wsz.ws_col;
74 }
75 #endif // WITH_RESIZE
76
77 namespace po = boost::program_options;
78
progressbar(const file & f,const quvi::media & m)79 progressbar::progressbar(const file& f, const quvi::media& m)
80 : _update_interval(1),
81 _expected_bytes(m.content_length()),
82 _initial_bytes(f.initial_length()),
83 _time_started(0),
84 _last_update(0),
85 _term_width(0),
86 _dot_count(0),
87 _count(0),
88 _width(0),
89 _file(f),
90 _done(false),
91 _mode(normal)
92 {
93 if (_initial_bytes > _expected_bytes)
94 _expected_bytes = _initial_bytes;
95
96 #ifdef WITH_RESIZE
97 signal(SIGWINCH, handle_sigwinch);
98
99 if (!_term_width || recv_sigwinch)
100 {
101 _term_width = get_term_width();
102
103 if (!_term_width)
104 _term_width = default_term_width;
105 }
106 #else
107 _term_width = default_term_width;
108 #endif
109
110 _width = _term_width;
111
112 const po::variables_map map = cc::opts.map();
113 time(&_time_started);
114
115 if (opts.flags.background)
116 _mode = dotline;
117 else
118 {
119 const std::string s = map["progressbar"].as<std::string>();
120 if (s == "simple")
121 _mode = simple;
122 else if (s == "dotline")
123 _mode = dotline;
124 }
125
126 _update_interval = fabs(map["update-interval"].as<double>());
127 }
128
to_mb(const double bytes)129 static double to_mb(const double bytes)
130 {
131 return bytes/(1024*1024);
132 }
133
134 namespace pt = boost::posix_time;
135
to_s(const int secs)136 static std::string to_s(const int secs)
137 {
138 pt::time_duration td = pt::seconds(secs);
139 return pt::to_simple_string(td);
140 }
141
to_unit(double & rate)142 static std::string to_unit(double& rate)
143 {
144 std::string units = "K/s";
145 if (rate >= 1024.0*1024.0*1024.0)
146 {
147 rate /= 1024.0*1024.0*1024.0;
148 units = "G/s";
149 }
150 else if (rate >= 1024.0*1024.0)
151 {
152 rate /= 1024.0*1024.0;
153 units = "M/s";
154 }
155 else
156 rate /= 1024.0;
157 return units;
158 }
159
160 namespace fs = boost::filesystem;
161
update(double now)162 int progressbar::update(double now)
163 {
164 time_t tnow;
165
166 time(&tnow);
167
168 const time_t elapsed = tnow - _time_started;
169
170 bool force_update = false;
171
172 #ifdef WITH_RESIZE
173 if (recv_sigwinch && _mode == normal)
174 {
175 const size_t old_term_width = _term_width;
176
177 _term_width = get_term_width();
178
179 if (!_term_width)
180 _term_width = default_term_width;
181
182 if (_term_width != old_term_width)
183 {
184 _width = _term_width;
185 force_update = true;
186 }
187
188 recv_sigwinch = 0;
189 }
190 #endif // WITH_RESIZE
191
192 const bool inactive = now == 0;
193
194 if (!_done)
195 {
196 if ((elapsed - _last_update) < _update_interval
197 && !force_update)
198 {
199 return 0;
200 }
201 }
202 else
203 now = _expected_bytes;
204
205 // Current size.
206
207 const double size =
208 (!_done)
209 ? _initial_bytes + now
210 : now;
211
212 std::stringstream size_s;
213
214 size_s.setf(std::ios::fixed);
215
216 size_s
217 << std::setprecision(1)
218 << to_mb(size)
219 << "M";
220
221 // Rate.
222
223 double rate = elapsed ? (now/elapsed):0;
224
225 std::stringstream rate_s, eta_s;
226
227 rate_s.setf(std::ios::fixed);
228 eta_s.setf(std::ios::fixed);
229
230 if (!inactive)
231 {
232 // ETA.
233
234 std::string eta;
235
236 if (!_done)
237 {
238 const double left =
239 (_expected_bytes - (now + _initial_bytes)) / rate;
240
241 eta = to_s(static_cast<int>(left+0.5));
242 }
243 else
244 {
245 rate = (_expected_bytes - _initial_bytes) / elapsed;
246 eta = to_s(elapsed);
247 }
248
249 std::string unit = to_unit(rate);
250
251 rate_s
252 << std::setw(4)
253 << std::setprecision(1)
254 << rate
255 << unit;
256
257 eta_s
258 << std::setw(6)
259 << eta;
260 }
261 else // ETA: inactive (default).
262 {
263 rate_s << "--.-K/s";
264 eta_s << "--:--:--";
265 }
266
267 // Percent.
268
269 std::stringstream percent_s;
270 int percent = 0;
271
272 if (_expected_bytes > 0)
273 {
274 percent = static_cast<int>(100.0*size/_expected_bytes);
275
276 if (percent < 100)
277 percent_s << std::setw(2) << percent << "%";
278 else
279 percent_s << "100%";
280 }
281
282 // Filename.
283
284 fs::path p = fs::system_complete(_file.path());
285
286 #if BOOST_FILESYSTEM_VERSION > 2
287 std::string fname = p.filename().string();
288 #else
289 std::string fname = p.filename();
290 #endif
291
292 switch (_mode)
293 {
294 default:
295 case normal:
296 _normal(size_s, rate_s, eta_s, percent, percent_s);
297 break;
298 case dotline:
299 _dotline(size_s, rate_s, eta_s, percent_s);
300 break;
301 case simple:
302 _simple(size_s, percent_s);
303 break;
304 }
305
306 _last_update = elapsed;
307 _count = now;
308
309 return 0;
310 }
311
_normal(const std::stringstream & size_s,const std::stringstream & rate_s,const std::stringstream & eta_s,const int percent,const std::stringstream & percent_s)312 void progressbar::_normal(const std::stringstream& size_s,
313 const std::stringstream& rate_s,
314 const std::stringstream& eta_s,
315 const int percent,
316 const std::stringstream& percent_s)
317 {
318 std::stringstream info;
319
320 info.setf(std::ios::fixed);
321
322 info
323 << " "
324 << percent_s.str()
325 << " "
326 << std::setw(4)
327 << size_s.str()
328 << " "
329 << rate_s.str()
330 << " "
331 << eta_s.str();
332
333 const size_t space_left = _width - info.str().length() - 1;
334
335 if (_width <= space_left)
336 return;
337
338 std::stringstream bar;
339
340 _render_meter(bar, percent, space_left);
341
342 bar << info.str();
343
344 cc::log << bar.str() << "\r" << std::flush;
345 }
346
_dotline(const std::stringstream & size_s,const std::stringstream & rate_s,const std::stringstream & eta_s,const std::stringstream & percent_s)347 void progressbar::_dotline(const std::stringstream& size_s,
348 const std::stringstream& rate_s,
349 const std::stringstream& eta_s,
350 const std::stringstream& percent_s)
351 {
352 #define details \
353 " " \
354 << std::setw(6) \
355 << size_s.str() \
356 << " " \
357 << rate_s.str() \
358 << " " \
359 << eta_s.str() \
360 << " " \
361 << percent_s.str()
362
363 #define dot \
364 do { \
365 cc::log \
366 << "." \
367 << (_dot_count % 3 == 0 ? " ":"") \
368 << std::flush; \
369 } while (0)
370
371 ++_dot_count;
372
373 if (_done)
374 {
375 for (; _dot_count < 31; ++_dot_count) dot;
376 cc::log << details << std::flush;
377 return;
378 }
379 if (_dot_count >= 31)
380 {
381 cc::log << details << std::endl;
382 _dot_count = 0;
383 }
384 #undef details
385 else
386 dot;
387 #undef dot
388 }
389
_simple(const std::stringstream & size_s,const std::stringstream & percent_s) const390 void progressbar::_simple(const std::stringstream& size_s,
391 const std::stringstream& percent_s) const
392 {
393 cc::log << percent_s.str()
394 << " - "
395 << size_s.str()
396 << " received\r"
397 << std::flush;
398 }
399
_render_meter(std::stringstream & bar,const int percent,const size_t space_left)400 void progressbar::_render_meter(std::stringstream& bar,
401 const int percent,
402 const size_t space_left)
403 {
404 const int m = static_cast<int>(space_left*percent/100.0);
405 bar << "[";
406 int i = 0;
407 while (bar.str().length() < space_left)
408 {
409 bar << (i<m ? "#":"-");
410 ++i;
411 }
412 bar << "]";
413 }
414
finish()415 void progressbar::finish()
416 {
417 if (_expected_bytes > 0
418 && _count + _initial_bytes > _expected_bytes)
419 {
420 _expected_bytes = _initial_bytes + _count;
421 }
422
423 _done = true;
424 update(-1);
425 }
426
427 } // namespace cc
428
429 // vim: set ts=2 sw=2 tw=72 expandtab:
430