| Index: chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc
|
| diff --git a/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc b/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b163644474913c5ee97c468b61515d6772843280
|
| --- /dev/null
|
| +++ b/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc
|
| @@ -0,0 +1,394 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h"
|
| +
|
| +#include <iterator>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/i18n/rtl.h"
|
| +#include "base/strings/string16.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/time.h"
|
| +#include "base/value_conversions.h"
|
| +#include "base/values.h"
|
| +#include "chrome/browser/download/all_download_item_notifier.h"
|
| +#include "chrome/browser/download/download_crx_util.h"
|
| +#include "chrome/browser/download/download_item_model.h"
|
| +#include "chrome/browser/download/download_query.h"
|
| +#include "chrome/browser/extensions/api/downloads/downloads_api.h"
|
| +#include "chrome/browser/extensions/extension_service.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "content/public/browser/browser_context.h"
|
| +#include "content/public/browser/download_item.h"
|
| +#include "content/public/browser/download_manager.h"
|
| +#include "content/public/browser/web_ui.h"
|
| +#include "extensions/browser/extension_system.h"
|
| +#include "net/base/filename_util.h"
|
| +#include "third_party/icu/source/i18n/unicode/datefmt.h"
|
| +#include "ui/base/l10n/time_format.h"
|
| +
|
| +using content::BrowserContext;
|
| +using content::DownloadItem;
|
| +using content::DownloadManager;
|
| +
|
| +using DownloadVector = DownloadManager::DownloadVector;
|
| +
|
| +namespace {
|
| +
|
| +// Returns a string constant to be used as the |danger_type| value in
|
| +// CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE,
|
| +// DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the
|
| +// |danger_type| value is only defined if the value of |state| is |DANGEROUS|.
|
| +const char* GetDangerTypeString(content::DownloadDangerType danger_type) {
|
| + switch (danger_type) {
|
| + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
|
| + return "DANGEROUS_FILE";
|
| + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
|
| + return "DANGEROUS_URL";
|
| + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
|
| + return "DANGEROUS_CONTENT";
|
| + case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
|
| + return "UNCOMMON_CONTENT";
|
| + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
|
| + return "DANGEROUS_HOST";
|
| + case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
|
| + return "POTENTIALLY_UNWANTED";
|
| + case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
|
| + case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
|
| + case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
|
| + case content::DOWNLOAD_DANGER_TYPE_MAX:
|
| + break;
|
| + }
|
| + // Don't return a danger type string if it is NOT_DANGEROUS,
|
| + // MAYBE_DANGEROUS_CONTENT, or USER_VALIDATED.
|
| + NOTREACHED();
|
| + return "";
|
| +}
|
| +
|
| +// TODO(dbeam): if useful elsewhere, move to base/i18n/time_formatting.h?
|
| +base::string16 TimeFormatLongDate(const base::Time& time) {
|
| + scoped_ptr<icu::DateFormat> formatter(
|
| + icu::DateFormat::createDateInstance(icu::DateFormat::kLong));
|
| + icu::UnicodeString date_string;
|
| + formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
|
| + return base::string16(date_string.getBuffer(),
|
| + static_cast<size_t>(date_string.length()));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +DownloadsListTracker::DownloadsListTracker(
|
| + DownloadManager* download_manager,
|
| + content::WebUI* web_ui)
|
| + : main_notifier_(download_manager, this),
|
| + web_ui_(web_ui),
|
| + should_show_(base::Bind(&DownloadsListTracker::ShouldShow,
|
| + base::Unretained(this))) {
|
| + Init();
|
| +}
|
| +
|
| +DownloadsListTracker::~DownloadsListTracker() {}
|
| +
|
| +void DownloadsListTracker::CallClearAll() {
|
| + if (sending_updates_)
|
| + web_ui_->CallJavascriptFunction("downloads.Manager.clearAll");
|
| +}
|
| +
|
| +bool DownloadsListTracker::SetSearchTerms(const base::ListValue& search_terms) {
|
| + std::vector<base::string16> new_terms;
|
| + new_terms.resize(search_terms.GetSize());
|
| +
|
| + for (size_t i = 0; i < search_terms.GetSize(); ++i)
|
| + search_terms.GetString(i, &new_terms[i]);
|
| +
|
| + if (new_terms == search_terms_)
|
| + return false;
|
| +
|
| + search_terms_.swap(new_terms);
|
| + RebuildSortedSet();
|
| + return true;
|
| +}
|
| +
|
| +void DownloadsListTracker::Start() {
|
| + sending_updates_ = true;
|
| +
|
| + // TODO(dbeam): paging and limiting logic.
|
| +
|
| + base::ListValue list;
|
| + for (auto* item : sorted_visible_items_)
|
| + list.Append(CreateDownloadItemValue(item).Pass());
|
| +
|
| + web_ui_->CallJavascriptFunction("downloads.Manager.insertItems",
|
| + base::FundamentalValue(0), list);
|
| +}
|
| +
|
| +void DownloadsListTracker::Stop() {
|
| + sending_updates_ = false;
|
| +}
|
| +
|
| +DownloadManager* DownloadsListTracker::GetMainNotifierManager() const {
|
| + return main_notifier_.GetManager();
|
| +}
|
| +
|
| +DownloadManager* DownloadsListTracker::GetOriginalNotifierManager() const {
|
| + return original_notifier_ ? original_notifier_->GetManager() : nullptr;
|
| +}
|
| +
|
| +void DownloadsListTracker::OnDownloadCreated(DownloadManager* manager,
|
| + DownloadItem* download_item) {
|
| + if (should_show_.Run(*download_item))
|
| + CallInsertItem(sorted_visible_items_.insert(download_item).first);
|
| +}
|
| +
|
| +void DownloadsListTracker::OnDownloadUpdated(DownloadManager* manager,
|
| + DownloadItem* download_item) {
|
| + auto current_position = sorted_visible_items_.find(download_item);
|
| + bool is_showing = current_position != sorted_visible_items_.end();
|
| + bool should_show = should_show_.Run(*download_item);
|
| +
|
| + if (!is_showing && should_show)
|
| + CallInsertItem(sorted_visible_items_.insert(download_item).first);
|
| + else if (is_showing && !should_show)
|
| + RemoveItem(current_position);
|
| + else if (is_showing)
|
| + CallUpdateItem(current_position);
|
| +}
|
| +
|
| +void DownloadsListTracker::OnDownloadRemoved(DownloadManager* manager,
|
| + DownloadItem* download_item) {
|
| + auto current_position = sorted_visible_items_.find(download_item);
|
| + if (current_position != sorted_visible_items_.end())
|
| + RemoveItem(current_position);
|
| +}
|
| +
|
| +DownloadsListTracker::DownloadsListTracker(
|
| + DownloadManager* download_manager,
|
| + content::WebUI* web_ui,
|
| + base::Callback<bool(const DownloadItem&)> should_show)
|
| + : main_notifier_(download_manager, this),
|
| + web_ui_(web_ui),
|
| + should_show_(should_show) {
|
| + Init();
|
| +}
|
| +
|
| +scoped_ptr<base::DictionaryValue> DownloadsListTracker::CreateDownloadItemValue(
|
| + content::DownloadItem* download_item) const {
|
| + // TODO(asanka): Move towards using download_model here for getting status and
|
| + // progress. The difference currently only matters to Drive downloads and
|
| + // those don't show up on the downloads page, but should.
|
| + DownloadItemModel download_model(download_item);
|
| +
|
| + // The items which are to be written into file_value are also described in
|
| + // chrome/browser/resources/downloads/downloads.js in @typedef for
|
| + // BackendDownloadObject. Please update it whenever you add or remove
|
| + // any keys in file_value.
|
| + scoped_ptr<base::DictionaryValue> file_value(new base::DictionaryValue);
|
| +
|
| + file_value->SetInteger(
|
| + "started", static_cast<int>(download_item->GetStartTime().ToTimeT()));
|
| + file_value->SetString(
|
| + "since_string", ui::TimeFormat::RelativeDate(
|
| + download_item->GetStartTime(), NULL));
|
| + file_value->SetString(
|
| + "date_string", TimeFormatLongDate(download_item->GetStartTime()));
|
| +
|
| + file_value->SetString("id", base::Uint64ToString(download_item->GetId()));
|
| +
|
| + base::FilePath download_path(download_item->GetTargetFilePath());
|
| + file_value->Set("file_path", base::CreateFilePathValue(download_path));
|
| + file_value->SetString("file_url",
|
| + net::FilePathToFileURL(download_path).spec());
|
| +
|
| + extensions::DownloadedByExtension* by_ext =
|
| + extensions::DownloadedByExtension::Get(download_item);
|
| + std::string by_ext_id;
|
| + std::string by_ext_name;
|
| + if (by_ext) {
|
| + by_ext_id = by_ext->id();
|
| + // TODO(dbeam): why doesn't DownloadsByExtension::name() return a string16?
|
| + by_ext_name = by_ext->name();
|
| +
|
| + // Lookup the extension's current name() in case the user changed their
|
| + // language. This won't work if the extension was uninstalled, so the name
|
| + // might be the wrong language.
|
| + bool include_disabled = true;
|
| + const extensions::Extension* extension = extensions::ExtensionSystem::Get(
|
| + Profile::FromBrowserContext(download_item->GetBrowserContext()))->
|
| + extension_service()->GetExtensionById(by_ext->id(), include_disabled);
|
| + if (extension)
|
| + by_ext_name = extension->name();
|
| + }
|
| + file_value->SetString("by_ext_id", by_ext_id);
|
| + file_value->SetString("by_ext_name", by_ext_name);
|
| +
|
| + // Keep file names as LTR. TODO(dbeam): why?
|
| + base::string16 file_name =
|
| + download_item->GetFileNameToReportUser().LossyDisplayName();
|
| + file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name);
|
| + file_value->SetString("file_name", file_name);
|
| + file_value->SetString("url", download_item->GetURL().spec());
|
| + file_value->SetInteger("total", static_cast<int>(
|
| + download_item->GetTotalBytes()));
|
| + file_value->SetBoolean("file_externally_removed",
|
| + download_item->GetFileExternallyRemoved());
|
| + file_value->SetBoolean("resume", download_item->CanResume());
|
| +
|
| + bool incognito = false;
|
| + auto* original_manager = GetOriginalNotifierManager();
|
| + if (original_manager) {
|
| + incognito =
|
| + original_manager->GetDownload(download_item->GetId()) == download_item;
|
| + }
|
| + file_value->SetBoolean("otr", incognito);
|
| +
|
| + const char* danger_type = "";
|
| + base::string16 last_reason_text;
|
| + // -2 is invalid, -1 means indeterminate, and 0-100 are in-progress.
|
| + int percent = -2;
|
| + base::string16 progress_status_text;
|
| + bool retry = false;
|
| + const char* state = nullptr;
|
| +
|
| + switch (download_item->GetState()) {
|
| + case content::DownloadItem::IN_PROGRESS: {
|
| + if (download_item->IsDangerous()) {
|
| + state = "DANGEROUS";
|
| + danger_type = GetDangerTypeString(download_item->GetDangerType());
|
| + } else if (download_item->IsPaused()) {
|
| + state = "PAUSED";
|
| + } else {
|
| + state = "IN_PROGRESS";
|
| + }
|
| + progress_status_text = download_model.GetTabProgressStatusText();
|
| + percent = download_item->PercentComplete();
|
| + break;
|
| + }
|
| +
|
| + case content::DownloadItem::INTERRUPTED:
|
| + state = "INTERRUPTED";
|
| + progress_status_text = download_model.GetTabProgressStatusText();
|
| +
|
| + if (download_item->CanResume())
|
| + percent = download_item->PercentComplete();
|
| +
|
| + last_reason_text = download_model.GetInterruptReasonText();
|
| + if (content::DOWNLOAD_INTERRUPT_REASON_CRASH ==
|
| + download_item->GetLastReason() && !download_item->CanResume()) {
|
| + retry = true;
|
| + }
|
| + break;
|
| +
|
| + case content::DownloadItem::CANCELLED:
|
| + state = "CANCELLED";
|
| + retry = true;
|
| + break;
|
| +
|
| + case content::DownloadItem::COMPLETE:
|
| + DCHECK(!download_item->IsDangerous());
|
| + state = "COMPLETE";
|
| + break;
|
| +
|
| + case content::DownloadItem::MAX_DOWNLOAD_STATE:
|
| + NOTREACHED();
|
| + }
|
| +
|
| + DCHECK(state);
|
| +
|
| + file_value->SetString("danger_type", danger_type);
|
| + file_value->SetString("last_reason_text", last_reason_text);
|
| + file_value->SetInteger("percent", percent);
|
| + file_value->SetString("progress_status_text", progress_status_text);
|
| + file_value->SetBoolean("retry", retry);
|
| + file_value->SetString("state", state);
|
| +
|
| + return file_value.Pass();
|
| +}
|
| +
|
| +const DownloadItem* DownloadsListTracker::GetItemForTesting(size_t index)
|
| + const {
|
| + if (index >= sorted_visible_items_.size())
|
| + return nullptr;
|
| +
|
| + SortedSet::iterator it = sorted_visible_items_.begin();
|
| + std::advance(it, index);
|
| + return *it;
|
| +}
|
| +
|
| +bool DownloadsListTracker::ShouldShow(const DownloadItem& item) const {
|
| + return !download_crx_util::IsExtensionDownload(item) &&
|
| + !item.IsTemporary() &&
|
| + !item.GetFileNameToReportUser().empty() &&
|
| + !item.GetTargetFilePath().empty() &&
|
| + DownloadItemModel(const_cast<DownloadItem*>(&item)).ShouldShowInShelf() &&
|
| + DownloadQuery::MatchesQuery(search_terms_, item);
|
| +}
|
| +
|
| +bool DownloadsListTracker::StartTimeComparator::operator() (
|
| + const content::DownloadItem* a, const content::DownloadItem* b) const {
|
| + return a->GetStartTime() > b->GetStartTime();
|
| +}
|
| +
|
| +void DownloadsListTracker::Init() {
|
| + Profile* profile = Profile::FromBrowserContext(
|
| + GetMainNotifierManager()->GetBrowserContext());
|
| + if (profile->IsOffTheRecord()) {
|
| + original_notifier_.reset(new AllDownloadItemNotifier(
|
| + BrowserContext::GetDownloadManager(profile->GetOriginalProfile()),
|
| + this));
|
| + }
|
| +
|
| + RebuildSortedSet();
|
| +}
|
| +
|
| +void DownloadsListTracker::RebuildSortedSet() {
|
| + DownloadVector all_items, visible_items;
|
| +
|
| + GetMainNotifierManager()->GetAllDownloads(&all_items);
|
| +
|
| + if (GetOriginalNotifierManager())
|
| + GetOriginalNotifierManager()->GetAllDownloads(&all_items);
|
| +
|
| + DownloadQuery query;
|
| + query.AddFilter(should_show_);
|
| + query.Search(all_items.begin(), all_items.end(), &visible_items);
|
| +
|
| + SortedSet sorted_visible_items(visible_items.begin(), visible_items.end());
|
| + sorted_visible_items_.swap(sorted_visible_items);
|
| +}
|
| +
|
| +void DownloadsListTracker::CallInsertItem(const SortedSet::iterator& insert) {
|
| + if (!sending_updates_)
|
| + return;
|
| +
|
| + base::ListValue list;
|
| + list.Append(CreateDownloadItemValue(*insert).Pass());
|
| +
|
| + web_ui_->CallJavascriptFunction("downloads.Manager.insertItems",
|
| + base::FundamentalValue(GetIndex(insert)),
|
| + list);
|
| +}
|
| +
|
| +void DownloadsListTracker::CallUpdateItem(const SortedSet::iterator& update) {
|
| + if (!sending_updates_)
|
| + return;
|
| +
|
| + web_ui_->CallJavascriptFunction("downloads.Manager.updateItem",
|
| + base::FundamentalValue(GetIndex(update)),
|
| + *CreateDownloadItemValue(*update));
|
| +}
|
| +
|
| +int DownloadsListTracker::GetIndex(const SortedSet::iterator& position) const {
|
| + // TODO(dbeam): this could be log(N) if |position| was random access.
|
| + return std::distance(sorted_visible_items_.begin(), position);
|
| +}
|
| +
|
| +void DownloadsListTracker::RemoveItem(const SortedSet::iterator& remove) {
|
| + if (sending_updates_) {
|
| + web_ui_->CallJavascriptFunction("downloads.Manager.removeItem",
|
| + base::FundamentalValue(GetIndex(remove)));
|
| + }
|
| + sorted_visible_items_.erase(remove);
|
| +}
|
|
|