1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/download/download_status_updater.h"
6
7#include <vector>
8
9#include "base/logging.h"
10#include "base/stl_util.h"
11
12#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
13#include "ui/views/linux_ui/linux_ui.h"
14#endif
15
16namespace {
17
18// DownloadStatusUpdater::UpdateAppIconDownloadProgress() expects to only be
19// called once when a DownloadItem completes, then not again (except perhaps
20// until it is resumed). The existence of WasInProgressData is effectively a
21// boolean that indicates whether that final UpdateAppIconDownloadProgress()
22// call has been made for a given DownloadItem. It is expected that there will
23// be many more non-in-progress downloads than in-progress downloads, so
24// WasInProgressData is set for in-progress downloads and cleared from
25// non-in-progress downloads instead of the other way around in order to save
26// memory.
27class WasInProgressData : public base::SupportsUserData::Data {
28 public:
29  static bool Get(content::DownloadItem* item) {
30    return item->GetUserData(kKey) != NULL;
31  }
32
33  static void Clear(content::DownloadItem* item) {
34    item->RemoveUserData(kKey);
35  }
36
37  explicit WasInProgressData(content::DownloadItem* item) {
38    item->SetUserData(kKey, this);
39  }
40
41 private:
42  static const char kKey[];
43  DISALLOW_COPY_AND_ASSIGN(WasInProgressData);
44};
45
46const char WasInProgressData::kKey[] =
47  "DownloadItem DownloadStatusUpdater WasInProgressData";
48
49}  // anonymous namespace
50
51DownloadStatusUpdater::DownloadStatusUpdater() {
52}
53
54DownloadStatusUpdater::~DownloadStatusUpdater() {
55  STLDeleteElements(&notifiers_);
56}
57
58bool DownloadStatusUpdater::GetProgress(float* progress,
59                                        int* download_count) const {
60  *progress = 0;
61  *download_count = 0;
62  bool progress_certain = true;
63  int64 received_bytes = 0;
64  int64 total_bytes = 0;
65
66  for (std::vector<AllDownloadItemNotifier*>::const_iterator it =
67       notifiers_.begin(); it != notifiers_.end(); ++it) {
68    if ((*it)->GetManager()) {
69      content::DownloadManager::DownloadVector items;
70      (*it)->GetManager()->GetAllDownloads(&items);
71      for (content::DownloadManager::DownloadVector::const_iterator it =
72          items.begin(); it != items.end(); ++it) {
73        if ((*it)->GetState() == content::DownloadItem::IN_PROGRESS) {
74          ++*download_count;
75          if ((*it)->GetTotalBytes() <= 0) {
76            // There may or may not be more data coming down this pipe.
77            progress_certain = false;
78          } else {
79            received_bytes += (*it)->GetReceivedBytes();
80            total_bytes += (*it)->GetTotalBytes();
81          }
82        }
83      }
84    }
85  }
86
87  if (total_bytes > 0)
88    *progress = static_cast<float>(received_bytes) / total_bytes;
89  return progress_certain;
90}
91
92void DownloadStatusUpdater::AddManager(content::DownloadManager* manager) {
93  notifiers_.push_back(new AllDownloadItemNotifier(manager, this));
94  content::DownloadManager::DownloadVector items;
95  manager->GetAllDownloads(&items);
96  for (content::DownloadManager::DownloadVector::const_iterator
97       it = items.begin(); it != items.end(); ++it) {
98    OnDownloadCreated(manager, *it);
99  }
100}
101
102void DownloadStatusUpdater::OnDownloadCreated(
103    content::DownloadManager* manager, content::DownloadItem* item) {
104  // Ignore downloads loaded from history, which are in a terminal state.
105  // TODO(benjhayden): Use the Observer interface to distinguish between
106  // historical and started downloads.
107  if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
108    UpdateAppIconDownloadProgress(item);
109    new WasInProgressData(item);
110  }
111  // else, the lack of WasInProgressData indicates to OnDownloadUpdated that it
112  // should not call UpdateAppIconDownloadProgress().
113}
114
115void DownloadStatusUpdater::OnDownloadUpdated(
116    content::DownloadManager* manager, content::DownloadItem* item) {
117  if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
118    // If the item was interrupted/cancelled and then resumed/restarted, then
119    // set WasInProgress so that UpdateAppIconDownloadProgress() will be called
120    // when it completes.
121    if (!WasInProgressData::Get(item))
122      new WasInProgressData(item);
123  } else {
124    // The item is now in a terminal state. If it was already in a terminal
125    // state, then do not call UpdateAppIconDownloadProgress() again. If it is
126    // now transitioning to a terminal state, then clear its WasInProgressData
127    // so that UpdateAppIconDownloadProgress() won't be called after this final
128    // call.
129    if (!WasInProgressData::Get(item))
130      return;
131    WasInProgressData::Clear(item);
132  }
133  UpdateAppIconDownloadProgress(item);
134}
135
136#if defined(OS_ANDROID) || (defined(USE_AURA) && !defined(OS_WIN))
137void DownloadStatusUpdater::UpdateAppIconDownloadProgress(
138    content::DownloadItem* download) {
139#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
140  const views::LinuxUI* linux_ui = views::LinuxUI::instance();
141  if (linux_ui) {
142    float progress = 0;
143    int download_count = 0;
144    GetProgress(&progress, &download_count);
145    linux_ui->SetDownloadCount(download_count);
146    linux_ui->SetProgressFraction(progress);
147  }
148#endif
149  // TODO(avi): Implement for Android?
150}
151#endif
152