blob: 0541165705d0c0e0468ab509b51e128bb0df81eb [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/download/download_status_updater.h"
#include <stdint.h>
#include <vector>
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
namespace {
// DownloadStatusUpdater::UpdateAppIconDownloadProgress() expects to only be
// called once when a DownloadItem completes, then not again (except perhaps
// until it is resumed). The existence of WasInProgressData is effectively a
// boolean that indicates whether that final UpdateAppIconDownloadProgress()
// call has been made for a given DownloadItem. It is expected that there will
// be many more non-in-progress downloads than in-progress downloads, so
// WasInProgressData is set for in-progress downloads and cleared from
// non-in-progress downloads instead of the other way around in order to save
// memory.
class WasInProgressData : public base::SupportsUserData::Data {
public:
static bool Get(download::DownloadItem* item) {
return item->GetUserData(kKey) != nullptr;
}
static void Clear(download::DownloadItem* item) {
item->RemoveUserData(kKey);
}
explicit WasInProgressData(download::DownloadItem* item) {
item->SetUserData(kKey, base::WrapUnique(this));
}
WasInProgressData(const WasInProgressData&) = delete;
WasInProgressData& operator=(const WasInProgressData&) = delete;
private:
static const char kKey[];
};
const char WasInProgressData::kKey[] =
"DownloadItem DownloadStatusUpdater WasInProgressData";
} // anonymous namespace
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
DownloadStatusUpdater::DownloadStatusUpdater() = default;
DownloadStatusUpdater::~DownloadStatusUpdater() = default;
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
bool DownloadStatusUpdater::GetProgress(float* progress,
int* download_count) const {
*progress = 0;
*download_count = 0;
bool progress_certain = true;
int64_t received_bytes = 0;
int64_t total_bytes = 0;
for (const auto& notifier : notifiers_) {
if (notifier->GetManager()) {
content::DownloadManager::DownloadVector items;
notifier->GetManager()->GetAllDownloads(&items);
for (download::DownloadItem* item : items) {
if (item->GetState() == download::DownloadItem::IN_PROGRESS) {
++*download_count;
if (item->GetTotalBytes() <= 0) {
// There may or may not be more data coming down this pipe.
progress_certain = false;
} else {
received_bytes += item->GetReceivedBytes();
total_bytes += item->GetTotalBytes();
}
}
}
}
}
if (total_bytes > 0)
*progress = static_cast<float>(received_bytes) / total_bytes;
return progress_certain;
}
void DownloadStatusUpdater::AddManager(content::DownloadManager* manager) {
notifiers_.push_back(
std::make_unique<download::AllDownloadItemNotifier>(manager, this));
content::DownloadManager::DownloadVector items;
manager->GetAllDownloads(&items);
for (download::DownloadItem* item : items) {
OnDownloadCreated(manager, item);
}
}
void DownloadStatusUpdater::OnDownloadCreated(content::DownloadManager* manager,
download::DownloadItem* item) {
// Ignore downloads loaded from history, which are in a terminal state.
// TODO(benjhayden): Use the Observer interface to distinguish between
// historical and started downloads.
if (item->GetState() == download::DownloadItem::IN_PROGRESS &&
!item->IsTransient()) {
UpdateAppIconDownloadProgress(item);
new WasInProgressData(item);
}
// else, the lack of WasInProgressData indicates to OnDownloadUpdated that it
// should not call UpdateAppIconDownloadProgress().
}
void DownloadStatusUpdater::OnDownloadUpdated(content::DownloadManager* manager,
download::DownloadItem* item) {
if (item->GetState() == download::DownloadItem::IN_PROGRESS &&
!item->IsTransient()) {
// If the item was interrupted/cancelled and then resumed/restarted, then
// set WasInProgress so that UpdateAppIconDownloadProgress() will be called
// when it completes.
if (!WasInProgressData::Get(item))
new WasInProgressData(item);
} else {
// The item is now in a terminal state. If it was already in a terminal
// state, then do not call UpdateAppIconDownloadProgress() again. If it is
// now transitioning to a terminal state, then clear its WasInProgressData
// so that UpdateAppIconDownloadProgress() won't be called after this final
// call.
if (!WasInProgressData::Get(item))
return;
WasInProgressData::Clear(item);
}
UpdateAppIconDownloadProgress(item);
UpdateProfileKeepAlive(manager);
}
void DownloadStatusUpdater::OnManagerGoingDown(
content::DownloadManager* manager) {
Profile* profile = Profile::FromBrowserContext(manager->GetBrowserContext());
profile_keep_alives_.erase(profile);
}
void DownloadStatusUpdater::UpdateProfileKeepAlive(
content::DownloadManager* manager) {
if (!manager) {
// Can be null in tests.
return;
}
Profile* profile = Profile::FromBrowserContext(manager->GetBrowserContext());
DCHECK(profile);
if (profile->IsOffTheRecord())
return;
// Are we already holding a keepalive?
bool already_has_keep_alive =
(profile_keep_alives_.find(profile) != profile_keep_alives_.end());
// Do we still need to hold a keepalive?
content::DownloadManager::DownloadVector items;
manager->GetAllDownloads(&items);
auto items_it = base::ranges::find(items, download::DownloadItem::IN_PROGRESS,
&download::DownloadItem::GetState);
bool should_keep_alive = (items_it != items.end());
if (should_keep_alive == already_has_keep_alive) {
// The current state is already correct for this Profile. No changes needed.
return;
}
// The current state is incorrect. Acquire/release a keepalive.
if (should_keep_alive) {
profile_keep_alives_[profile] = std::make_unique<ScopedProfileKeepAlive>(
profile, ProfileKeepAliveOrigin::kDownloadInProgress);
} else {
profile_keep_alives_.erase(profile);
}
}
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
void DownloadStatusUpdater::UpdateAppIconDownloadProgress(
download::DownloadItem* download) {
// TODO(avi): Implement for Android?
}
#endif