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 "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 } | |
OLD | NEW |