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