1
/** \file cmdline_download_progress_display.cc */
3
// Copyright (C) 2010-2011 Daniel Burrows
5
// This program is free software; you can redistribute it and/or
6
// modify it under the terms of the GNU General Public License as
7
// published by the Free Software Foundation; either version 2 of the
8
// License, or (at your option) any later version.
10
// This program is distributed in the hope that it will be useful, but
11
// WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
// General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program; see the file COPYING. If not, write to
17
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
// Boston, MA 02111-1307, USA.
21
#include "cmdline_download_progress_display.h"
24
#include "transient_message.h"
28
#include <generic/views/download_progress.h>
31
#include <apt-pkg/strutl.h>
33
#include <boost/algorithm/string/join.hpp>
34
#include <boost/format.hpp>
35
#include <boost/make_shared.hpp>
37
#include <cwidget/generic/util/transcode.h>
41
using boost::algorithm::join;
43
using boost::make_shared;
44
using boost::shared_ptr;
46
using cwidget::util::transcode;
54
class download_progress : public views::download_progress
56
bool display_messages;
57
shared_ptr<transient_message> message;
58
shared_ptr<download_status_display> status_display;
59
shared_ptr<terminal_input> term_input;
61
download_progress(bool _display_messages,
62
const shared_ptr<transient_message> &_message,
63
const shared_ptr<download_status_display> &_status_display,
64
const shared_ptr<terminal_input> &_term_input);
66
friend shared_ptr<download_progress>
67
make_shared<download_progress>(const bool &,
68
const shared_ptr<transient_message> &,
69
const shared_ptr<download_status_display> &,
70
const shared_ptr<terminal_input> &);
73
bool update_progress(const status ¤t_status);
75
void file_started(const std::string &description,
76
const boost::optional<unsigned long> &id,
77
const boost::optional<unsigned long long> &file_size);
79
void file_already_downloaded(const std::string &description,
80
const boost::optional<unsigned long> &id,
81
const boost::optional<unsigned long long> &file_size);
83
void error(bool ignored,
84
const std::string &error,
85
const std::string &description,
86
const boost::optional<unsigned long> &id);
88
void file_finished(const std::string &description,
89
const boost::optional<unsigned long> &id);
91
void done(double fetched_bytes,
92
unsigned long elapsed_time,
93
double latest_download_rate);
95
void media_change(const std::string &media,
96
const std::string &drive,
97
const sigc::slot1<void, bool> &k);
99
virtual void complete(double fetched_bytes,
100
unsigned long elapsed_time,
101
double latest_download_rate);
104
download_progress::download_progress(bool _display_messages,
105
const shared_ptr<transient_message> &_message,
106
const shared_ptr<download_status_display> &_status_display,
107
const shared_ptr<terminal_input> &_term_input)
108
: display_messages(_display_messages),
110
status_display(_status_display),
111
term_input(_term_input)
116
bool download_progress::update_progress(const status ¤t_status)
118
status_display->display_status(current_status);
123
void download_progress::file_started(const std::string &description,
124
const boost::optional<unsigned long> &id,
125
const boost::optional<unsigned long long> &file_size)
129
std::vector<std::string> entries;
131
entries.push_back(_("Get:"));
134
entries.push_back( (format("%d") % *id).str() );
136
if(!description.empty())
137
entries.push_back(description);
140
entries.push_back( (format("[%sB]") % SizeToStr(*file_size)).str() );
142
message->display_and_advance(transcode(join(entries, " ")));
146
void download_progress::file_already_downloaded(const std::string &description,
147
const boost::optional<unsigned long> &id,
148
const boost::optional<unsigned long long> &file_size)
152
std::vector<std::string> entries;
154
entries.push_back(_("Hit"));
157
entries.push_back( (format("%d") % *id).str() );
159
if(!description.empty())
160
entries.push_back(description);
163
entries.push_back( (format("[%sB]") % SizeToStr(*file_size)).str() );
165
message->display_and_advance(transcode(join(entries, " ")));
169
void download_progress::error(bool ignored,
170
const std::string &error,
171
const std::string &description,
172
const boost::optional<unsigned long> &id)
176
std::vector<std::string> entries;
179
// ForTranslators: this stands for "ignored" and should be
180
// the same width as the translation of "Err".
181
entries.push_back(_("Ign"));
183
// ForTranslators: this stands for "error" and should be the
184
// same width as the translation of "Ign".
185
entries.push_back(_("Err"));
187
if(!description.empty())
188
entries.push_back(description);
190
message->display_and_advance(transcode(join(entries, " ")));
192
if(!ignored && !error.empty())
193
message->display_and_advance(transcode(" " + error));
197
void download_progress::file_finished(const std::string &description,
198
const boost::optional<unsigned long> &id)
202
void download_progress::done(double fetched_bytes,
203
unsigned long elapsed_time,
204
double latest_download_rate)
208
if(fetched_bytes != 0)
211
(format(_("Fetched %sB in %s (%sB/s)"))
212
% SizeToStr(fetched_bytes)
213
% TimeToStr(elapsed_time)
214
% SizeToStr(latest_download_rate)).str();
216
message->display_and_advance(transcode(text));
221
void download_progress::media_change(const std::string &media,
222
const std::string &drive,
223
const sigc::slot1<void, bool> &k)
225
// Clear any existing text to ensure that the prompt starts at
226
// the beginning of the line.
227
message->set_text(L"");
230
(format(_("Media change: Please insert the disc labeled '%s' into "
231
"the drive '%s' and press [Enter]."))
232
% media % drive).str();
233
// Note that the value the user enters is discarded.
236
term_input->prompt_for_input(transcode(prompt));
238
catch(StdinEOFException)
243
// Now say it's OK to continue.
247
void download_progress::complete(double fetched_bytes,
248
unsigned long elapsed_time,
249
double latest_download_rate)
253
class dummy_status_display : public download_status_display
255
dummy_status_display();
257
friend shared_ptr<dummy_status_display>
258
make_shared<dummy_status_display>();
261
void display_status(const download_progress::status &status);
264
dummy_status_display::dummy_status_display()
268
void dummy_status_display::display_status(const download_progress::status &)
272
class download_status_display_impl : public download_status_display
274
shared_ptr<transient_message> message;
275
shared_ptr<terminal_locale> term_locale;
276
shared_ptr<terminal_metrics> term_metrics;
278
download_status_display_impl(const shared_ptr<transient_message> &_message,
279
const shared_ptr<terminal_locale> &_term_locale,
280
const shared_ptr<terminal_metrics> &_term_metrics);
282
friend shared_ptr<download_status_display_impl>
283
make_shared<download_status_display_impl>(const shared_ptr<transient_message> &,
284
const shared_ptr<terminal_locale> &,
285
const shared_ptr<terminal_metrics> &);
288
void display_status(const download_progress::status &status);
291
download_status_display_impl::download_status_display_impl(const shared_ptr<transient_message> &_message,
292
const shared_ptr<terminal_locale> &_term_locale,
293
const shared_ptr<terminal_metrics> &_term_metrics)
295
term_locale(_term_locale),
296
term_metrics(_term_metrics)
300
// \todo This should be generic code:
301
int as_percent(double fraction)
303
const int result = static_cast<int>(round(fraction * 100));
306
else if(result > 100)
312
/** \brief Visitor for worker status objects that appends their
313
* rendering to a string.
315
* Not responsible for bracketing the rendering in [].
317
class append_worker_status : public boost::static_visitor<>
319
std::wstring &output;
320
typedef views::download_progress::file_progress file_progress;
323
append_worker_status(std::wstring &_output)
328
void operator()(const std::string &progress) const
330
output += transcode(progress);
333
void operator()(const file_progress &progress) const
335
const unsigned long current_size = progress.get_current_size();
336
const unsigned long total_size = progress.get_total_size();
338
const bool complete = progress.get_complete();
339
const std::string &description = progress.get_description();
340
const boost::optional<unsigned long> &id = progress.get_id();
341
const std::string &mode = progress.get_mode();
343
std::vector<std::wstring> components;
346
components.push_back((wformat(L"%lu") % *id).str());
348
if(!description.empty())
349
components.push_back(transcode(description));
352
components.push_back(transcode(mode));
354
if(total_size != 0 && !complete)
355
components.push_back((wformat(L"%lu/%sB %lu%%")
357
% transcode(SizeToStr(total_size))
358
% as_percent( ((double) current_size) / total_size)).str());
359
else if(current_size != 0)
360
// The old download indicator displayed a size of 0 if
361
// current_size was 0. I figure if we have no total size
362
// and we haven't downloaded anything, there's no poing.
363
components.push_back((wformat(L"%lu") % current_size).str());
365
output += join(components, L" ");
369
/** \brief Find the index in the given string of a display
372
* \param s The string to process.
374
* \param target_column The column to locate, where the first
375
* character of s starts at column 0.
377
* \param term_locale Locale information that should be used when
378
* determining the column.
380
* \param result_index Set to the index of the first character
381
* which is partly or wholly at or beyond
382
* the given display column. If target_column
383
* is negative, this will always be 0.
385
* \param result_column Set to the first column in which the
386
* character indicated by result_index
389
* \todo Could move to a common module and get a unit test.
391
void find_column_index(const std::wstring &s,
393
const shared_ptr<terminal_locale> &term_locale,
400
std::wstring::const_iterator it = s.begin();
402
while(it != s.end() && result_column < target_column)
404
const int curr_width = term_locale->wcwidth(*it);
406
// Don't include this character if it starts before the
407
// target column and extends past it. Be careful not to
408
// return a character that ends just before the target
410
if(result_column + curr_width > target_column)
415
result_column += curr_width;
419
void download_status_display_impl::display_status(const download_progress::status &status)
421
typedef views::download_progress::status::worker_status worker_status;
422
const double download_rate = status.get_download_rate();
423
const std::vector<worker_status> &active_downloads =
424
status.get_active_downloads();
425
const double fraction_complete = status.get_fraction_complete();
426
const unsigned long time_remaining = status.get_time_remaining();
428
const int percent = as_percent(fraction_complete);
430
std::wstring message_text;
432
message_text += (wformat(L"%d%% ") % percent).str();
434
if(active_downloads.empty())
435
message_text += transcode(_("[Working]"));
438
for(std::vector<worker_status>::const_iterator it =
439
active_downloads.begin(); it != active_downloads.end(); ++it)
441
if(it != active_downloads.begin())
442
message_text.push_back(L' ');
444
message_text.push_back(L'[');
445
boost::apply_visitor(append_worker_status(message_text), *it);
446
message_text.push_back(L']');
450
if(download_rate > 0 || time_remaining > 0)
452
std::wstring progress_str;
454
if(download_rate > 0)
455
progress_str = (wformat(L" %sB/s %s")
456
% transcode(SizeToStr(download_rate))
457
% transcode(TimeToStr(time_remaining))).str();
459
progress_str = (wformat(L" %s")
460
% transcode(TimeToStr(time_remaining))).str();
462
int progress_str_width = 0;
463
for(std::wstring::const_iterator it = progress_str.begin();
464
it != progress_str.end(); ++it)
465
progress_str_width += term_locale->wcwidth(*it);
467
int message_text_width = 0;
468
for(std::wstring::const_iterator it = message_text.begin();
469
it != message_text.end(); ++it)
470
message_text_width += term_locale->wcwidth(*it);
472
const int screen_width = term_metrics->get_screen_width();
474
// Format the progress string so that it overlaps the
475
// previous message text and is right-justified.
477
int progress_str_start_column = screen_width - progress_str_width;
479
// First, pad the message text with spaces so that there's
480
// a place to insert the progress string.
481
while(message_text_width < screen_width - progress_str_start_column)
483
message_text.push_back(L' ');
484
message_text_width += term_locale->wcwidth(L' ');
487
// Now, find the location in the message text where the
488
// progress string will begin and drop the rest of the
490
int first_overwritten_character_index;
491
int first_overwritten_character_column;
493
find_column_index(message_text,
494
progress_str_start_column,
496
first_overwritten_character_index,
497
first_overwritten_character_column);
499
// Drop the part of the string that's overwritten;
500
// everything from first_overwritten_character_index
502
message_text.erase(first_overwritten_character_index);
504
// We might have split a wide character; replace its first
505
// portion with spaces.
506
while(first_overwritten_character_column < progress_str_start_column)
508
message_text.push_back(L' ');
509
first_overwritten_character_column += term_locale->wcwidth(L' ');
512
// OK, now message_text is exactly long enough to
513
// concatenate it with the progress string. The progress
514
// string might running off the right side of the terminal
515
// if it replaced the whole message text and was still too
516
// long; I leave it to the transient message to deal with
518
message_text += progress_str;
521
message->set_text(message_text);
525
download_status_display::~download_status_display()
529
shared_ptr<views::download_progress>
530
create_download_progress_display(const boost::shared_ptr<transient_message> &message,
531
const boost::shared_ptr<download_status_display> &status_display,
532
const boost::shared_ptr<terminal_input> &term_input,
533
bool display_messages)
535
return make_shared<download_progress>(display_messages,
541
shared_ptr<download_status_display>
542
create_cmdline_download_status_display(const shared_ptr<transient_message> &message,
543
const shared_ptr<terminal_locale> &term_locale,
544
const shared_ptr<terminal_metrics> &term_metrics,
548
return make_shared<dummy_status_display>();
550
return make_shared<download_status_display_impl>(message,