OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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/ui/webui/md_downloads/downloads_list_tracker.h" |
| 6 |
| 7 #include <iterator> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/bind_helpers.h" |
| 11 #include "base/i18n/rtl.h" |
| 12 #include "base/strings/string16.h" |
| 13 #include "base/strings/string_number_conversions.h" |
| 14 #include "base/time/time.h" |
| 15 #include "base/value_conversions.h" |
| 16 #include "base/values.h" |
| 17 #include "chrome/browser/download/all_download_item_notifier.h" |
| 18 #include "chrome/browser/download/download_crx_util.h" |
| 19 #include "chrome/browser/download/download_item_model.h" |
| 20 #include "chrome/browser/download/download_query.h" |
| 21 #include "chrome/browser/extensions/api/downloads/downloads_api.h" |
| 22 #include "chrome/browser/extensions/extension_service.h" |
| 23 #include "chrome/browser/profiles/profile.h" |
| 24 #include "content/public/browser/browser_context.h" |
| 25 #include "content/public/browser/download_item.h" |
| 26 #include "content/public/browser/download_manager.h" |
| 27 #include "content/public/browser/web_ui.h" |
| 28 #include "extensions/browser/extension_system.h" |
| 29 #include "net/base/filename_util.h" |
| 30 #include "third_party/icu/source/i18n/unicode/datefmt.h" |
| 31 #include "ui/base/l10n/time_format.h" |
| 32 |
| 33 using content::BrowserContext; |
| 34 using content::DownloadItem; |
| 35 using content::DownloadManager; |
| 36 |
| 37 using DownloadVector = DownloadManager::DownloadVector; |
| 38 |
| 39 namespace { |
| 40 |
| 41 // Returns a string constant to be used as the |danger_type| value in |
| 42 // CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, |
| 43 // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the |
| 44 // |danger_type| value is only defined if the value of |state| is |DANGEROUS|. |
| 45 const char* GetDangerTypeString(content::DownloadDangerType danger_type) { |
| 46 switch (danger_type) { |
| 47 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: |
| 48 return "DANGEROUS_FILE"; |
| 49 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
| 50 return "DANGEROUS_URL"; |
| 51 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| 52 return "DANGEROUS_CONTENT"; |
| 53 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
| 54 return "UNCOMMON_CONTENT"; |
| 55 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
| 56 return "DANGEROUS_HOST"; |
| 57 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
| 58 return "POTENTIALLY_UNWANTED"; |
| 59 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: |
| 60 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: |
| 61 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: |
| 62 case content::DOWNLOAD_DANGER_TYPE_MAX: |
| 63 break; |
| 64 } |
| 65 // Don't return a danger type string if it is NOT_DANGEROUS, |
| 66 // MAYBE_DANGEROUS_CONTENT, or USER_VALIDATED. |
| 67 NOTREACHED(); |
| 68 return ""; |
| 69 } |
| 70 |
| 71 // TODO(dbeam): if useful elsewhere, move to base/i18n/time_formatting.h? |
| 72 base::string16 TimeFormatLongDate(const base::Time& time) { |
| 73 scoped_ptr<icu::DateFormat> formatter( |
| 74 icu::DateFormat::createDateInstance(icu::DateFormat::kLong)); |
| 75 icu::UnicodeString date_string; |
| 76 formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string); |
| 77 return base::string16(date_string.getBuffer(), |
| 78 static_cast<size_t>(date_string.length())); |
| 79 } |
| 80 |
| 81 } // namespace |
| 82 |
| 83 DownloadsListTracker::DownloadsListTracker( |
| 84 DownloadManager* download_manager, |
| 85 content::WebUI* web_ui) |
| 86 : main_notifier_(download_manager, this), |
| 87 web_ui_(web_ui), |
| 88 should_show_(base::Bind(&DownloadsListTracker::ShouldShow, |
| 89 base::Unretained(this))) { |
| 90 Init(); |
| 91 } |
| 92 |
| 93 DownloadsListTracker::~DownloadsListTracker() {} |
| 94 |
| 95 void DownloadsListTracker::CallClearAll() { |
| 96 if (sending_updates_) |
| 97 web_ui_->CallJavascriptFunction("downloads.Manager.clearAll"); |
| 98 } |
| 99 |
| 100 bool DownloadsListTracker::SetSearchTerms(const base::ListValue& search_terms) { |
| 101 std::vector<base::string16> new_terms; |
| 102 new_terms.resize(search_terms.GetSize()); |
| 103 |
| 104 for (size_t i = 0; i < search_terms.GetSize(); ++i) |
| 105 search_terms.GetString(i, &new_terms[i]); |
| 106 |
| 107 if (new_terms == search_terms_) |
| 108 return false; |
| 109 |
| 110 search_terms_.swap(new_terms); |
| 111 RebuildSortedSet(); |
| 112 return true; |
| 113 } |
| 114 |
| 115 void DownloadsListTracker::Start() { |
| 116 sending_updates_ = true; |
| 117 |
| 118 // TODO(dbeam): paging and limiting logic. |
| 119 |
| 120 base::ListValue list; |
| 121 for (auto* item : sorted_visible_items_) |
| 122 list.Append(CreateDownloadItemValue(item).Pass()); |
| 123 |
| 124 web_ui_->CallJavascriptFunction("downloads.Manager.insertItems", |
| 125 base::FundamentalValue(0), list); |
| 126 } |
| 127 |
| 128 void DownloadsListTracker::Stop() { |
| 129 sending_updates_ = false; |
| 130 } |
| 131 |
| 132 DownloadManager* DownloadsListTracker::GetMainNotifierManager() const { |
| 133 return main_notifier_.GetManager(); |
| 134 } |
| 135 |
| 136 DownloadManager* DownloadsListTracker::GetOriginalNotifierManager() const { |
| 137 return original_notifier_ ? original_notifier_->GetManager() : nullptr; |
| 138 } |
| 139 |
| 140 void DownloadsListTracker::OnDownloadCreated(DownloadManager* manager, |
| 141 DownloadItem* download_item) { |
| 142 if (should_show_.Run(*download_item)) |
| 143 CallInsertItem(sorted_visible_items_.insert(download_item).first); |
| 144 } |
| 145 |
| 146 void DownloadsListTracker::OnDownloadUpdated(DownloadManager* manager, |
| 147 DownloadItem* download_item) { |
| 148 auto current_position = sorted_visible_items_.find(download_item); |
| 149 bool is_showing = current_position != sorted_visible_items_.end(); |
| 150 bool should_show = should_show_.Run(*download_item); |
| 151 |
| 152 if (!is_showing && should_show) |
| 153 CallInsertItem(sorted_visible_items_.insert(download_item).first); |
| 154 else if (is_showing && !should_show) |
| 155 RemoveItem(current_position); |
| 156 else if (is_showing) |
| 157 CallUpdateItem(current_position); |
| 158 } |
| 159 |
| 160 void DownloadsListTracker::OnDownloadRemoved(DownloadManager* manager, |
| 161 DownloadItem* download_item) { |
| 162 auto current_position = sorted_visible_items_.find(download_item); |
| 163 if (current_position != sorted_visible_items_.end()) |
| 164 RemoveItem(current_position); |
| 165 } |
| 166 |
| 167 DownloadsListTracker::DownloadsListTracker( |
| 168 DownloadManager* download_manager, |
| 169 content::WebUI* web_ui, |
| 170 base::Callback<bool(const DownloadItem&)> should_show) |
| 171 : main_notifier_(download_manager, this), |
| 172 web_ui_(web_ui), |
| 173 should_show_(should_show) { |
| 174 Init(); |
| 175 } |
| 176 |
| 177 scoped_ptr<base::DictionaryValue> DownloadsListTracker::CreateDownloadItemValue( |
| 178 content::DownloadItem* download_item) const { |
| 179 // TODO(asanka): Move towards using download_model here for getting status and |
| 180 // progress. The difference currently only matters to Drive downloads and |
| 181 // those don't show up on the downloads page, but should. |
| 182 DownloadItemModel download_model(download_item); |
| 183 |
| 184 // The items which are to be written into file_value are also described in |
| 185 // chrome/browser/resources/downloads/downloads.js in @typedef for |
| 186 // BackendDownloadObject. Please update it whenever you add or remove |
| 187 // any keys in file_value. |
| 188 scoped_ptr<base::DictionaryValue> file_value(new base::DictionaryValue); |
| 189 |
| 190 file_value->SetInteger( |
| 191 "started", static_cast<int>(download_item->GetStartTime().ToTimeT())); |
| 192 file_value->SetString( |
| 193 "since_string", ui::TimeFormat::RelativeDate( |
| 194 download_item->GetStartTime(), NULL)); |
| 195 file_value->SetString( |
| 196 "date_string", TimeFormatLongDate(download_item->GetStartTime())); |
| 197 |
| 198 file_value->SetString("id", base::Uint64ToString(download_item->GetId())); |
| 199 |
| 200 base::FilePath download_path(download_item->GetTargetFilePath()); |
| 201 file_value->Set("file_path", base::CreateFilePathValue(download_path)); |
| 202 file_value->SetString("file_url", |
| 203 net::FilePathToFileURL(download_path).spec()); |
| 204 |
| 205 extensions::DownloadedByExtension* by_ext = |
| 206 extensions::DownloadedByExtension::Get(download_item); |
| 207 std::string by_ext_id; |
| 208 std::string by_ext_name; |
| 209 if (by_ext) { |
| 210 by_ext_id = by_ext->id(); |
| 211 // TODO(dbeam): why doesn't DownloadsByExtension::name() return a string16? |
| 212 by_ext_name = by_ext->name(); |
| 213 |
| 214 // Lookup the extension's current name() in case the user changed their |
| 215 // language. This won't work if the extension was uninstalled, so the name |
| 216 // might be the wrong language. |
| 217 bool include_disabled = true; |
| 218 const extensions::Extension* extension = extensions::ExtensionSystem::Get( |
| 219 Profile::FromBrowserContext(download_item->GetBrowserContext()))-> |
| 220 extension_service()->GetExtensionById(by_ext->id(), include_disabled); |
| 221 if (extension) |
| 222 by_ext_name = extension->name(); |
| 223 } |
| 224 file_value->SetString("by_ext_id", by_ext_id); |
| 225 file_value->SetString("by_ext_name", by_ext_name); |
| 226 |
| 227 // Keep file names as LTR. TODO(dbeam): why? |
| 228 base::string16 file_name = |
| 229 download_item->GetFileNameToReportUser().LossyDisplayName(); |
| 230 file_name = base::i18n::GetDisplayStringInLTRDirectionality(file_name); |
| 231 file_value->SetString("file_name", file_name); |
| 232 file_value->SetString("url", download_item->GetURL().spec()); |
| 233 file_value->SetInteger("total", static_cast<int>( |
| 234 download_item->GetTotalBytes())); |
| 235 file_value->SetBoolean("file_externally_removed", |
| 236 download_item->GetFileExternallyRemoved()); |
| 237 file_value->SetBoolean("resume", download_item->CanResume()); |
| 238 |
| 239 bool incognito = false; |
| 240 auto* original_manager = GetOriginalNotifierManager(); |
| 241 if (original_manager) { |
| 242 incognito = |
| 243 original_manager->GetDownload(download_item->GetId()) == download_item; |
| 244 } |
| 245 file_value->SetBoolean("otr", incognito); |
| 246 |
| 247 const char* danger_type = ""; |
| 248 base::string16 last_reason_text; |
| 249 // -2 is invalid, -1 means indeterminate, and 0-100 are in-progress. |
| 250 int percent = -2; |
| 251 base::string16 progress_status_text; |
| 252 bool retry = false; |
| 253 const char* state = nullptr; |
| 254 |
| 255 switch (download_item->GetState()) { |
| 256 case content::DownloadItem::IN_PROGRESS: { |
| 257 if (download_item->IsDangerous()) { |
| 258 state = "DANGEROUS"; |
| 259 danger_type = GetDangerTypeString(download_item->GetDangerType()); |
| 260 } else if (download_item->IsPaused()) { |
| 261 state = "PAUSED"; |
| 262 } else { |
| 263 state = "IN_PROGRESS"; |
| 264 } |
| 265 progress_status_text = download_model.GetTabProgressStatusText(); |
| 266 percent = download_item->PercentComplete(); |
| 267 break; |
| 268 } |
| 269 |
| 270 case content::DownloadItem::INTERRUPTED: |
| 271 state = "INTERRUPTED"; |
| 272 progress_status_text = download_model.GetTabProgressStatusText(); |
| 273 |
| 274 if (download_item->CanResume()) |
| 275 percent = download_item->PercentComplete(); |
| 276 |
| 277 last_reason_text = download_model.GetInterruptReasonText(); |
| 278 if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == |
| 279 download_item->GetLastReason() && !download_item->CanResume()) { |
| 280 retry = true; |
| 281 } |
| 282 break; |
| 283 |
| 284 case content::DownloadItem::CANCELLED: |
| 285 state = "CANCELLED"; |
| 286 retry = true; |
| 287 break; |
| 288 |
| 289 case content::DownloadItem::COMPLETE: |
| 290 DCHECK(!download_item->IsDangerous()); |
| 291 state = "COMPLETE"; |
| 292 break; |
| 293 |
| 294 case content::DownloadItem::MAX_DOWNLOAD_STATE: |
| 295 NOTREACHED(); |
| 296 } |
| 297 |
| 298 DCHECK(state); |
| 299 |
| 300 file_value->SetString("danger_type", danger_type); |
| 301 file_value->SetString("last_reason_text", last_reason_text); |
| 302 file_value->SetInteger("percent", percent); |
| 303 file_value->SetString("progress_status_text", progress_status_text); |
| 304 file_value->SetBoolean("retry", retry); |
| 305 file_value->SetString("state", state); |
| 306 |
| 307 return file_value.Pass(); |
| 308 } |
| 309 |
| 310 const DownloadItem* DownloadsListTracker::GetItemForTesting(size_t index) |
| 311 const { |
| 312 if (index >= sorted_visible_items_.size()) |
| 313 return nullptr; |
| 314 |
| 315 SortedSet::iterator it = sorted_visible_items_.begin(); |
| 316 std::advance(it, index); |
| 317 return *it; |
| 318 } |
| 319 |
| 320 bool DownloadsListTracker::ShouldShow(const DownloadItem& item) const { |
| 321 return !download_crx_util::IsExtensionDownload(item) && |
| 322 !item.IsTemporary() && |
| 323 !item.GetFileNameToReportUser().empty() && |
| 324 !item.GetTargetFilePath().empty() && |
| 325 DownloadItemModel(const_cast<DownloadItem*>(&item)).ShouldShowInShelf() && |
| 326 DownloadQuery::MatchesQuery(search_terms_, item); |
| 327 } |
| 328 |
| 329 bool DownloadsListTracker::StartTimeComparator::operator() ( |
| 330 const content::DownloadItem* a, const content::DownloadItem* b) const { |
| 331 return a->GetStartTime() > b->GetStartTime(); |
| 332 } |
| 333 |
| 334 void DownloadsListTracker::Init() { |
| 335 Profile* profile = Profile::FromBrowserContext( |
| 336 GetMainNotifierManager()->GetBrowserContext()); |
| 337 if (profile->IsOffTheRecord()) { |
| 338 original_notifier_.reset(new AllDownloadItemNotifier( |
| 339 BrowserContext::GetDownloadManager(profile->GetOriginalProfile()), |
| 340 this)); |
| 341 } |
| 342 |
| 343 RebuildSortedSet(); |
| 344 } |
| 345 |
| 346 void DownloadsListTracker::RebuildSortedSet() { |
| 347 DownloadVector all_items, visible_items; |
| 348 |
| 349 GetMainNotifierManager()->GetAllDownloads(&all_items); |
| 350 |
| 351 if (GetOriginalNotifierManager()) |
| 352 GetOriginalNotifierManager()->GetAllDownloads(&all_items); |
| 353 |
| 354 DownloadQuery query; |
| 355 query.AddFilter(should_show_); |
| 356 query.Search(all_items.begin(), all_items.end(), &visible_items); |
| 357 |
| 358 SortedSet sorted_visible_items(visible_items.begin(), visible_items.end()); |
| 359 sorted_visible_items_.swap(sorted_visible_items); |
| 360 } |
| 361 |
| 362 void DownloadsListTracker::CallInsertItem(const SortedSet::iterator& insert) { |
| 363 if (!sending_updates_) |
| 364 return; |
| 365 |
| 366 base::ListValue list; |
| 367 list.Append(CreateDownloadItemValue(*insert).Pass()); |
| 368 |
| 369 web_ui_->CallJavascriptFunction("downloads.Manager.insertItems", |
| 370 base::FundamentalValue(GetIndex(insert)), |
| 371 list); |
| 372 } |
| 373 |
| 374 void DownloadsListTracker::CallUpdateItem(const SortedSet::iterator& update) { |
| 375 if (!sending_updates_) |
| 376 return; |
| 377 |
| 378 web_ui_->CallJavascriptFunction("downloads.Manager.updateItem", |
| 379 base::FundamentalValue(GetIndex(update)), |
| 380 *CreateDownloadItemValue(*update)); |
| 381 } |
| 382 |
| 383 int DownloadsListTracker::GetIndex(const SortedSet::iterator& position) const { |
| 384 // TODO(dbeam): this could be log(N) if |position| was random access. |
| 385 return std::distance(sorted_visible_items_.begin(), position); |
| 386 } |
| 387 |
| 388 void DownloadsListTracker::RemoveItem(const SortedSet::iterator& remove) { |
| 389 if (sending_updates_) { |
| 390 web_ui_->CallJavascriptFunction("downloads.Manager.removeItem", |
| 391 base::FundamentalValue(GetIndex(remove))); |
| 392 } |
| 393 sorted_visible_items_.erase(remove); |
| 394 } |
OLD | NEW |