Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(520)

Side by Side Diff: chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc

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

Powered by Google App Engine
This is Rietveld 408576698