~ubuntu-branches/ubuntu/vivid/aptitude/vivid

« back to all changes in this revision

Viewing changes to src/cmdline/cmdline_download_progress_display.cc

  • Committer: Bazaar Package Importer
  • Author(s): Michael Vogt
  • Date: 2011-06-22 12:32:56 UTC
  • mfrom: (1.8.6 sid)
  • Revision ID: james.westby@ubuntu.com-20110622123256-8aox9w9ch3x72dci
Tags: 0.6.4-1ubuntu1
* Merge from debian unstable.  Remaining changes:
  - debian/05aptitude: never autoremove kernels
  - drop aptitude-doc to Suggests
  - 03_branding.dpatch: ubuntu branding
  - 04_changelog.dpatch: take changelogs from changelogs.ubuntu.com
  - 09_ubuntu_fortify_source.dpatch: Suppress a number of warnings (turned
    into errors by -Werror) triggered by Ubuntu's default of
    -D_FORTIFY_SOURCE=2.
  - 11_ubuntu_uses_sudo.dpatch: fix status line of 'Become root' menu entry
    to not refer to su.
  - 12_point_manpage_to_doc_package.dpatch: point Finnish manpage to the
    correct place for further info
  - 14_html2text_preferred.dpatch: switch back to html2text in favor of
    elinks, since html2text is in main and elinks isn't.
* dropped 01_intltool_update.dpatch
* updated 15_ftbfs_new_apt

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/** \file cmdline_download_progress_display.cc */
 
2
 
 
3
// Copyright (C) 2010-2011 Daniel Burrows
 
4
//
 
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.
 
9
//
 
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.
 
14
//
 
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.
 
19
 
 
20
// Local includes:
 
21
#include "cmdline_download_progress_display.h"
 
22
 
 
23
#include "terminal.h"
 
24
#include "transient_message.h"
 
25
 
 
26
#include <aptitude.h>
 
27
 
 
28
#include <generic/views/download_progress.h>
 
29
 
 
30
// System includes:
 
31
#include <apt-pkg/strutl.h>
 
32
 
 
33
#include <boost/algorithm/string/join.hpp>
 
34
#include <boost/format.hpp>
 
35
#include <boost/make_shared.hpp>
 
36
 
 
37
#include <cwidget/generic/util/transcode.h>
 
38
 
 
39
#include <math.h>
 
40
 
 
41
using boost::algorithm::join;
 
42
using boost::format;
 
43
using boost::make_shared;
 
44
using boost::shared_ptr;
 
45
using boost::wformat;
 
46
using cwidget::util::transcode;
 
47
 
 
48
namespace aptitude
 
49
{
 
50
  namespace cmdline
 
51
  {
 
52
    namespace
 
53
    {
 
54
      class download_progress : public views::download_progress
 
55
      {
 
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;
 
60
 
 
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);
 
65
 
 
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> &);
 
71
 
 
72
      public:
 
73
        bool update_progress(const status &current_status);
 
74
 
 
75
        void file_started(const std::string &description,
 
76
                          const boost::optional<unsigned long> &id,
 
77
                          const boost::optional<unsigned long long> &file_size);
 
78
 
 
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);
 
82
 
 
83
        void error(bool ignored,
 
84
                   const std::string &error,
 
85
                   const std::string &description,
 
86
                   const boost::optional<unsigned long> &id);
 
87
 
 
88
        void file_finished(const std::string &description,
 
89
                           const boost::optional<unsigned long> &id);
 
90
 
 
91
        void done(double fetched_bytes,
 
92
                  unsigned long elapsed_time,
 
93
                  double latest_download_rate);
 
94
 
 
95
        void media_change(const std::string &media,
 
96
                          const std::string &drive,
 
97
                          const sigc::slot1<void, bool> &k);
 
98
 
 
99
        virtual void complete(double fetched_bytes,
 
100
                              unsigned long elapsed_time,
 
101
                              double latest_download_rate);
 
102
      };
 
103
 
 
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),
 
109
          message(_message),
 
110
          status_display(_status_display),
 
111
          term_input(_term_input)
 
112
      {
 
113
      }
 
114
 
 
115
 
 
116
      bool download_progress::update_progress(const status &current_status)
 
117
      {
 
118
        status_display->display_status(current_status);
 
119
 
 
120
        return true;
 
121
      }
 
122
 
 
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)
 
126
      {
 
127
        if(display_messages)
 
128
          {
 
129
            std::vector<std::string> entries;
 
130
 
 
131
            entries.push_back(_("Get:"));
 
132
 
 
133
            if(id)
 
134
              entries.push_back( (format("%d") % *id).str() );
 
135
 
 
136
            if(!description.empty())
 
137
              entries.push_back(description);
 
138
 
 
139
            if(file_size)
 
140
              entries.push_back( (format("[%sB]") % SizeToStr(*file_size)).str() );
 
141
 
 
142
            message->display_and_advance(transcode(join(entries, " ")));
 
143
          }
 
144
      }
 
145
 
 
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)
 
149
      {
 
150
        if(display_messages)
 
151
          {
 
152
            std::vector<std::string> entries;
 
153
 
 
154
            entries.push_back(_("Hit"));
 
155
 
 
156
            if(id)
 
157
              entries.push_back( (format("%d") % *id).str() );
 
158
 
 
159
            if(!description.empty())
 
160
              entries.push_back(description);
 
161
 
 
162
            if(file_size)
 
163
              entries.push_back( (format("[%sB]") % SizeToStr(*file_size)).str() );
 
164
 
 
165
            message->display_and_advance(transcode(join(entries, " ")));
 
166
          }
 
167
      }
 
168
 
 
169
      void download_progress::error(bool ignored,
 
170
                                    const std::string &error,
 
171
                                    const std::string &description,
 
172
                                    const boost::optional<unsigned long> &id)
 
173
      {
 
174
        if(display_messages)
 
175
          {
 
176
            std::vector<std::string> entries;
 
177
 
 
178
            if(ignored)
 
179
              // ForTranslators: this stands for "ignored" and should be
 
180
              // the same width as the translation of "Err".
 
181
              entries.push_back(_("Ign"));
 
182
            else
 
183
              // ForTranslators: this stands for "error" and should be the
 
184
              // same width as the translation of "Ign".
 
185
              entries.push_back(_("Err"));
 
186
 
 
187
            if(!description.empty())
 
188
              entries.push_back(description);
 
189
 
 
190
            message->display_and_advance(transcode(join(entries, " ")));
 
191
 
 
192
            if(!ignored && !error.empty())
 
193
              message->display_and_advance(transcode("  " + error));
 
194
          }
 
195
      }
 
196
 
 
197
      void download_progress::file_finished(const std::string &description,
 
198
                                            const boost::optional<unsigned long> &id)
 
199
      {
 
200
      }
 
201
 
 
202
      void download_progress::done(double fetched_bytes,
 
203
                                   unsigned long elapsed_time,
 
204
                                   double latest_download_rate)
 
205
      {
 
206
        if(display_messages)
 
207
          {
 
208
            if(fetched_bytes != 0)
 
209
              {
 
210
                std::string text =
 
211
                  (format(_("Fetched %sB in %s (%sB/s)"))
 
212
                   % SizeToStr(fetched_bytes)
 
213
                   % TimeToStr(elapsed_time)
 
214
                   % SizeToStr(latest_download_rate)).str();
 
215
 
 
216
                message->display_and_advance(transcode(text));
 
217
              }
 
218
          }
 
219
      }
 
220
 
 
221
      void download_progress::media_change(const std::string &media,
 
222
                                           const std::string &drive,
 
223
                                           const sigc::slot1<void, bool> &k)
 
224
      {
 
225
        // Clear any existing text to ensure that the prompt starts at
 
226
        // the beginning of the line.
 
227
        message->set_text(L"");
 
228
 
 
229
        std::string prompt =
 
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.
 
234
        try
 
235
          {
 
236
            term_input->prompt_for_input(transcode(prompt));
 
237
          }
 
238
        catch(StdinEOFException)
 
239
          {
 
240
            k(false);
 
241
            return;
 
242
          }
 
243
        // Now say it's OK to continue.
 
244
        k(true);
 
245
      }
 
246
 
 
247
      void download_progress::complete(double fetched_bytes,
 
248
                                       unsigned long elapsed_time,
 
249
                                       double latest_download_rate)
 
250
      {
 
251
      }
 
252
 
 
253
      class dummy_status_display : public download_status_display
 
254
      {
 
255
        dummy_status_display();
 
256
 
 
257
        friend shared_ptr<dummy_status_display>
 
258
        make_shared<dummy_status_display>();
 
259
 
 
260
      public:
 
261
        void display_status(const download_progress::status &status);
 
262
      };
 
263
 
 
264
      dummy_status_display::dummy_status_display()
 
265
      {
 
266
      }
 
267
 
 
268
      void dummy_status_display::display_status(const download_progress::status &)
 
269
      {
 
270
      }
 
271
 
 
272
      class download_status_display_impl : public download_status_display
 
273
      {
 
274
        shared_ptr<transient_message> message;
 
275
        shared_ptr<terminal_locale> term_locale;
 
276
        shared_ptr<terminal_metrics> term_metrics;
 
277
 
 
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);
 
281
 
 
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> &);
 
286
 
 
287
      public:
 
288
        void display_status(const download_progress::status &status);
 
289
      };
 
290
 
 
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)
 
294
        : message(_message),
 
295
          term_locale(_term_locale),
 
296
          term_metrics(_term_metrics)
 
297
      {
 
298
      }
 
299
 
 
300
      // \todo This should be generic code:
 
301
      int as_percent(double fraction)
 
302
      {
 
303
        const int result = static_cast<int>(round(fraction * 100));
 
304
        if(result < 0)
 
305
          return 0;
 
306
        else if(result > 100)
 
307
          return 100;
 
308
        else
 
309
          return result;
 
310
      }
 
311
 
 
312
      /** \brief Visitor for worker status objects that appends their
 
313
       *  rendering to a string.
 
314
       *
 
315
       *  Not responsible for bracketing the rendering in [].
 
316
       */
 
317
      class append_worker_status : public boost::static_visitor<>
 
318
      {
 
319
        std::wstring &output;
 
320
        typedef views::download_progress::file_progress file_progress;
 
321
 
 
322
      public:
 
323
        append_worker_status(std::wstring &_output)
 
324
          : output(_output)
 
325
        {
 
326
        }
 
327
 
 
328
        void operator()(const std::string &progress) const
 
329
        {
 
330
          output += transcode(progress);
 
331
        }
 
332
 
 
333
        void operator()(const file_progress &progress) const
 
334
        {
 
335
          const unsigned long current_size = progress.get_current_size();
 
336
          const unsigned long total_size = progress.get_total_size();
 
337
 
 
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();
 
342
 
 
343
          std::vector<std::wstring> components;
 
344
 
 
345
          if(id)
 
346
            components.push_back((wformat(L"%lu") % *id).str());
 
347
 
 
348
          if(!description.empty())
 
349
            components.push_back(transcode(description));
 
350
 
 
351
          if(!mode.empty())
 
352
            components.push_back(transcode(mode));
 
353
 
 
354
          if(total_size != 0 && !complete)
 
355
            components.push_back((wformat(L"%lu/%sB %lu%%")
 
356
                                  % current_size
 
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());
 
364
 
 
365
          output += join(components, L" ");
 
366
        }
 
367
      };
 
368
 
 
369
      /** \brief Find the index in the given string of a display
 
370
       *  column.
 
371
       *
 
372
       *  \param s      The string to process.
 
373
       *
 
374
       *  \param target_column The column to locate, where the first
 
375
       *                       character of s starts at column 0.
 
376
       *
 
377
       *  \param term_locale Locale information that should be used when
 
378
       *                     determining the column.
 
379
       *
 
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.
 
384
       *
 
385
       *  \param result_column Set to the first column in which the
 
386
       *                       character indicated by result_index
 
387
       *                       appears.
 
388
       *
 
389
       *  \todo Could move to a common module and get a unit test.
 
390
       */
 
391
      void find_column_index(const std::wstring &s,
 
392
                             int target_column,
 
393
                             const shared_ptr<terminal_locale> &term_locale,
 
394
                             int &result_index,
 
395
                             int &result_column)
 
396
      {
 
397
        result_index = 0;
 
398
        result_column = 0;
 
399
 
 
400
        std::wstring::const_iterator it = s.begin();
 
401
 
 
402
        while(it != s.end() && result_column < target_column)
 
403
          {
 
404
            const int curr_width = term_locale->wcwidth(*it);
 
405
 
 
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
 
409
            // column (>= vs >).
 
410
            if(result_column + curr_width > target_column)
 
411
              break;
 
412
 
 
413
            ++it;
 
414
            ++result_index;
 
415
            result_column += curr_width;
 
416
          }
 
417
      }
 
418
 
 
419
      void download_status_display_impl::display_status(const download_progress::status &status)
 
420
      {
 
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();
 
427
 
 
428
        const int percent = as_percent(fraction_complete);
 
429
 
 
430
        std::wstring message_text;
 
431
 
 
432
        message_text += (wformat(L"%d%% ") % percent).str();
 
433
 
 
434
        if(active_downloads.empty())
 
435
          message_text += transcode(_("[Working]"));
 
436
        else
 
437
          {
 
438
            for(std::vector<worker_status>::const_iterator it =
 
439
                  active_downloads.begin(); it != active_downloads.end(); ++it)
 
440
              {
 
441
                if(it != active_downloads.begin())
 
442
                  message_text.push_back(L' ');
 
443
 
 
444
                message_text.push_back(L'[');
 
445
                boost::apply_visitor(append_worker_status(message_text), *it);
 
446
                message_text.push_back(L']');
 
447
              }
 
448
          }
 
449
 
 
450
        if(download_rate > 0 || time_remaining > 0)
 
451
          {
 
452
            std::wstring progress_str;
 
453
 
 
454
            if(download_rate > 0)
 
455
              progress_str = (wformat(L" %sB/s %s")
 
456
                              % transcode(SizeToStr(download_rate))
 
457
                              % transcode(TimeToStr(time_remaining))).str();
 
458
            else
 
459
              progress_str = (wformat(L" %s")
 
460
                              % transcode(TimeToStr(time_remaining))).str();
 
461
 
 
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);
 
466
 
 
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);
 
471
 
 
472
            const int screen_width = term_metrics->get_screen_width();
 
473
 
 
474
            // Format the progress string so that it overlaps the
 
475
            // previous message text and is right-justified.
 
476
 
 
477
            int progress_str_start_column = screen_width - progress_str_width;
 
478
 
 
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)
 
482
              {
 
483
                message_text.push_back(L' ');
 
484
                message_text_width += term_locale->wcwidth(L' ');
 
485
              }
 
486
 
 
487
            // Now, find the location in the message text where the
 
488
            // progress string will begin and drop the rest of the
 
489
            // string.
 
490
            int first_overwritten_character_index;
 
491
            int first_overwritten_character_column;
 
492
 
 
493
            find_column_index(message_text,
 
494
                              progress_str_start_column,
 
495
                              term_locale,
 
496
                              first_overwritten_character_index,
 
497
                              first_overwritten_character_column);
 
498
 
 
499
            // Drop the part of the string that's overwritten;
 
500
            // everything from first_overwritten_character_index
 
501
            // onwards.
 
502
            message_text.erase(first_overwritten_character_index);
 
503
 
 
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)
 
507
              {
 
508
                message_text.push_back(L' ');
 
509
                first_overwritten_character_column += term_locale->wcwidth(L' ');
 
510
              }
 
511
 
 
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
 
517
            // that case.
 
518
            message_text += progress_str;
 
519
          }
 
520
 
 
521
        message->set_text(message_text);
 
522
      }
 
523
    }
 
524
 
 
525
    download_status_display::~download_status_display()
 
526
    {
 
527
    }
 
528
 
 
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)
 
534
    {
 
535
      return make_shared<download_progress>(display_messages,
 
536
                                            message,
 
537
                                            status_display,
 
538
                                            term_input);
 
539
    }
 
540
 
 
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,
 
545
                                           bool hide_status)
 
546
    {
 
547
      if(hide_status)
 
548
        return make_shared<dummy_status_display>();
 
549
      else
 
550
        return make_shared<download_status_display_impl>(message,
 
551
                                                         term_locale,
 
552
                                                         term_metrics);
 
553
    }
 
554
  }
 
555
}