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

Side by Side Diff: chrome/browser/ntp_snippets/download_suggestions_provider.cc

Issue 2360263002: [NTPSnippets] Show all downloads on the NTP (3/3): Downloads provider. (Closed)
Patch Set: another win fix. Created 4 years, 2 months 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 2016 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/ntp_snippets/download_suggestions_provider.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/feature_list.h"
11 #include "base/guid.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/time/time.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "components/ntp_snippets/pref_names.h"
20 #include "components/ntp_snippets/pref_util.h"
21 #include "components/offline_pages/client_namespace_constants.h"
22 #include "components/prefs/pref_registry_simple.h"
23 #include "components/prefs/pref_service.h"
24 #include "net/base/filename_util.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/gfx/image/image.h"
27
28 using content::DownloadItem;
29 using content::DownloadManager;
30 using ntp_snippets::Category;
31 using ntp_snippets::CategoryInfo;
32 using ntp_snippets::CategoryStatus;
33 using ntp_snippets::ContentSuggestion;
34 using ntp_snippets::prefs::kDismissedAssetDownloadSuggestions;
35 using ntp_snippets::prefs::kDismissedOfflinePageDownloadSuggestions;
36 using offline_pages::OfflinePageItem;
37
38 namespace {
39
40 // TODO(vitaliii): Make this configurable via a variation param. See
41 // crbug.com/654800.
42 const int kMaxSuggestionsCount = 5;
43 const char kAssetDownloadsPrefix = 'D';
44 const char kOfflinePageDownloadsPrefix = 'O';
45
46 std::string GetOfflinePagePerCategoryID(int64_t raw_offline_page_id) {
47 // Raw ID is prefixed in order to avoid conflicts with asset downloads.
48 return std::string(1, kOfflinePageDownloadsPrefix) +
49 base::IntToString(raw_offline_page_id);
50 }
51
52 std::string GetAssetDownloadPerCategoryID(uint32_t raw_download_id) {
53 // Raw ID is prefixed in order to avoid conflicts with offline page downloads.
54 return std::string(1, kAssetDownloadsPrefix) +
55 base::UintToString(raw_download_id);
56 }
57
58 // Determines whether |suggestion_id| corresponds to offline page suggestion or
59 // asset download based on |id_within_category| prefix.
60 bool CorrespondsToOfflinePage(const ContentSuggestion::ID& suggestion_id) {
61 const std::string& id_within_category = suggestion_id.id_within_category();
62 if (!id_within_category.empty()) {
63 if (id_within_category[0] == kOfflinePageDownloadsPrefix)
64 return true;
65 if (id_within_category[0] == kAssetDownloadsPrefix)
66 return false;
67 }
68 NOTREACHED() << "Unknown id_within_category " << id_within_category;
69 return false;
70 }
71
72 bool IsOfflinePageDownload(const offline_pages::ClientId& client_id) {
73 return (client_id.name_space == offline_pages::kAsyncNamespace ||
74 client_id.name_space == offline_pages::kDownloadNamespace ||
75 client_id.name_space == offline_pages::kNTPSuggestionsNamespace);
76 }
77
78 bool IsDownloadCompleted(const DownloadItem& item) {
79 return item.GetState() == DownloadItem::DownloadState::COMPLETE &&
80 !item.GetFileExternallyRemoved();
81 }
82
83 struct OrderDownloadsMostRecentlyDownloadedFirst {
84 bool operator()(const DownloadItem* left, const DownloadItem* right) const {
85 return left->GetEndTime() > right->GetEndTime();
86 }
87 };
88
89 } // namespace
90
91 DownloadSuggestionsProvider::DownloadSuggestionsProvider(
92 ContentSuggestionsProvider::Observer* observer,
93 ntp_snippets::CategoryFactory* category_factory,
94 const scoped_refptr<ntp_snippets::OfflinePageProxy>& offline_page_proxy,
95 content::DownloadManager* download_manager,
96 PrefService* pref_service,
97 bool download_manager_ui_enabled)
98 : ContentSuggestionsProvider(observer, category_factory),
99 category_status_(CategoryStatus::AVAILABLE_LOADING),
100 provided_category_(category_factory->FromKnownCategory(
101 ntp_snippets::KnownCategories::DOWNLOADS)),
102 offline_page_proxy_(offline_page_proxy),
103 download_manager_(download_manager),
104 pref_service_(pref_service),
105 download_manager_ui_enabled_(download_manager_ui_enabled),
106 weak_ptr_factory_(this) {
107 observer->OnCategoryStatusChanged(this, provided_category_, category_status_);
108 offline_page_proxy_->AddObserver(this);
109 download_manager_->AddObserver(this);
Marc Treib 2016/10/17 10:18:40 Is this always non-null?
vitaliii 2016/10/26 00:07:55 Done.
110 // The asset downloads are not fetched, since for each of them
Marc Treib 2016/10/17 10:18:41 nit: I'd formulate this as something like No need
vitaliii 2016/10/26 00:07:55 Done.
111 // |OnDownloadCreated| is fired.
112 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/true);
113 }
114
115 DownloadSuggestionsProvider::~DownloadSuggestionsProvider() {
116 offline_page_proxy_->RemoveObserver(this);
117 if (download_manager_) {
118 download_manager_->RemoveObserver(this);
119 std::vector<DownloadItem*> all_downloads;
120 download_manager_->GetAllDownloads(&all_downloads);
121
122 for (DownloadItem* item : all_downloads) {
123 item->RemoveObserver(this);
124 }
125 }
126 }
127
128 CategoryStatus DownloadSuggestionsProvider::GetCategoryStatus(
129 Category category) {
130 DCHECK_EQ(provided_category_, category);
131 return category_status_;
132 }
133
134 CategoryInfo DownloadSuggestionsProvider::GetCategoryInfo(Category category) {
135 DCHECK_EQ(provided_category_, category);
136 return CategoryInfo(
137 l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER),
138 ntp_snippets::ContentSuggestionsCardLayout::MINIMAL_CARD,
139 /*has_more_button=*/download_manager_ui_enabled_,
140 /*show_if_empty=*/false);
141 }
142
143 void DownloadSuggestionsProvider::DismissSuggestion(
144 const ContentSuggestion::ID& suggestion_id) {
145 DCHECK_EQ(provided_category_, suggestion_id.category());
146 std::set<std::string> dismissed_ids =
147 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
148 dismissed_ids.insert(suggestion_id.id_within_category());
149 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
150 dismissed_ids);
151
152 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
153 }
154
155 void DownloadSuggestionsProvider::FetchSuggestionImage(
156 const ContentSuggestion::ID& suggestion_id,
157 const ImageFetchedCallback& callback) {
158 // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it is
159 // available there.
160 // TODO(vitaliii): Provide site's favicon for assets downloads. See
161 // crbug.com/631447.
162 base::ThreadTaskRunnerHandle::Get()->PostTask(
163 FROM_HERE, base::Bind(callback, gfx::Image()));
164 }
165
166 void DownloadSuggestionsProvider::ClearHistory(
167 base::Time begin,
168 base::Time end,
169 const base::Callback<bool(const GURL& url)>& filter) {
170 cached_offline_page_downloads_.clear();
171 cached_asset_downloads_.clear();
172 // This will trigger an asynchronous re-fetch.
173 ClearDismissedSuggestionsForDebugging(provided_category_);
174 }
175
176 void DownloadSuggestionsProvider::ClearCachedSuggestions(Category category) {
177 DCHECK_EQ(provided_category_, category);
178 // Ignored. The internal caches are not stored on disk and they are just
179 // partial copies of the data stored at OfflinePage model and DownloadManager.
180 // If it is cleared there, it will be cleared in these caches as well.
181 }
182
183 void DownloadSuggestionsProvider::GetDismissedSuggestionsForDebugging(
184 Category category,
185 const DismissedSuggestionsCallback& callback) {
186 DCHECK_EQ(provided_category_, category);
187
188 offline_page_proxy_->GetAllPages(
189 base::Bind(&DownloadSuggestionsProvider::
190 GetAllPagesCallbackForGetDismissedSuggestions,
191 weak_ptr_factory_.GetWeakPtr(), callback));
192 }
193
194 void DownloadSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
195 Category category) {
196 DCHECK_EQ(provided_category_, category);
197 StoreAssetDismissedIDsToPrefs(std::set<std::string>());
198 StoreOfflinePageDismissedIDsToPrefs(std::set<std::string>());
199 AsynchronouslyFetchAllDownloadsAndSubmitSuggestions();
200 }
201
202 // static
203 void DownloadSuggestionsProvider::RegisterProfilePrefs(
204 PrefRegistrySimple* registry) {
205 registry->RegisterListPref(kDismissedAssetDownloadSuggestions);
206 registry->RegisterListPref(kDismissedOfflinePageDownloadSuggestions);
207 }
208
209 ////////////////////////////////////////////////////////////////////////////////
210 // Private methods
211
212 void DownloadSuggestionsProvider::GetAllPagesCallbackForGetDismissedSuggestions(
213 const DismissedSuggestionsCallback& callback,
214 const std::vector<OfflinePageItem>& offline_pages) const {
215 std::set<std::string> dismissed_ids = ReadOfflinePageDismissedIDsFromPrefs();
216 std::vector<ContentSuggestion> suggestions;
217 for (const OfflinePageItem& item : offline_pages) {
218 if (dismissed_ids.count(GetOfflinePagePerCategoryID(item.offline_id)))
219 suggestions.push_back(ConvertOfflinePage(item));
220 }
221
222 if (download_manager_) {
223 std::vector<DownloadItem*> all_downloads;
224 download_manager_->GetAllDownloads(&all_downloads);
225
226 dismissed_ids = ReadAssetDismissedIDsFromPrefs();
227
228 for (const DownloadItem* item : all_downloads) {
229 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
230 suggestions.push_back(ConvertDownloadItem(*item));
231 }
232 }
233
234 callback.Run(std::move(suggestions));
235 }
236
237 void DownloadSuggestionsProvider::OfflinePageModelChanged(
238 const std::vector<offline_pages::OfflinePageItem>& offline_pages) {
239 UpdateOfflinePagesCache(offline_pages);
240 SubmitContentSuggestions();
241 }
242
243 void DownloadSuggestionsProvider::OfflinePageDeleted(
244 int64_t offline_id,
245 const offline_pages::ClientId& client_id) {
246 if (IsOfflinePageDownload(client_id)) {
247 InvalidateSuggestion(GetOfflinePagePerCategoryID(offline_id));
248 }
249 }
250
251 void DownloadSuggestionsProvider::OnDownloadCreated(DownloadManager* manager,
252 DownloadItem* item) {
253 DCHECK_EQ(download_manager_, manager);
254 // This is called when new downloads are started and on startup for existing
255 // ones.
256 // We listen to each item to know when it is destroyed.
257 item->AddObserver(this);
258 if (CacheAssetDownloadIfNeeded(item))
259 SubmitContentSuggestions();
260 }
261
262 void DownloadSuggestionsProvider::ManagerGoingDown(DownloadManager* manager) {
263 DCHECK_EQ(download_manager_, manager);
264 download_manager_ = nullptr;
Marc Treib 2016/10/17 10:18:41 Should you also unregister all the item observers
vitaliii 2016/10/26 00:07:55 Done.
265 }
266
267 void DownloadSuggestionsProvider::OnDownloadUpdated(DownloadItem* item) {
268 if (base::ContainsValue(cached_asset_downloads_, item)) {
269 if (item->GetFileExternallyRemoved()) {
270 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
271 } else {
272 // The download may have changed.
273 SubmitContentSuggestions();
274 }
275 } else {
276 // Unfinished downloads may become completed.
277 if (CacheAssetDownloadIfNeeded(item))
278 SubmitContentSuggestions();
279 }
280 }
281
282 void DownloadSuggestionsProvider::OnDownloadOpened(DownloadItem* item) {
283 // Ignored.
284 }
285
286 void DownloadSuggestionsProvider::OnDownloadRemoved(DownloadItem* item) {
287 // Ignored. We listen to |OnDownloadDestroyed| instead. The reason is that
288 // we may need to retrieve all downloads, but |OnDownloadRemoved| is called
289 // before the download is removed from the list.
290 }
291
292 void DownloadSuggestionsProvider::OnDownloadDestroyed(
293 content::DownloadItem* item) {
294 item->RemoveObserver(this);
295
296 if (!IsDownloadCompleted(*item))
297 return;
298 // TODO(vitaliii): Implement a better way to clean up dismissed IDs (in case
299 // some calls are missed).
300 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
301 }
302
303 void DownloadSuggestionsProvider::NotifyStatusChanged(
304 CategoryStatus new_status) {
305 DCHECK_NE(CategoryStatus::NOT_PROVIDED, category_status_);
306 DCHECK_NE(CategoryStatus::NOT_PROVIDED, new_status);
307 if (category_status_ == new_status)
308 return;
309 category_status_ = new_status;
310 observer()->OnCategoryStatusChanged(this, provided_category_,
311 category_status_);
312 }
313
314 void DownloadSuggestionsProvider::AsynchronouslyFetchOfflinePagesDownloads(
315 bool notify) {
316 offline_page_proxy_->GetAllPages(
317 base::Bind(notify ? &DownloadSuggestionsProvider::OfflinePageModelChanged
318 : &DownloadSuggestionsProvider::UpdateOfflinePagesCache,
Marc Treib 2016/10/17 10:18:41 Add the "bool notify" parameter also to UpdateOffl
vitaliii 2016/10/26 00:07:55 Done.
319 weak_ptr_factory_.GetWeakPtr()));
320 }
321
322 void DownloadSuggestionsProvider::FetchAssetsDownloads() {
323 if (!download_manager_) {
324 // The manager has gone down.
325 return;
326 }
327
328 std::vector<DownloadItem*> all_downloads;
329 download_manager_->GetAllDownloads(&all_downloads);
330 std::set<std::string> old_dismissed_ids = ReadAssetDismissedIDsFromPrefs();
331 std::set<std::string> retained_dismissed_ids;
332 cached_asset_downloads_.clear();
333 for (const DownloadItem* item : all_downloads) {
334 std::string within_category_id =
335 GetAssetDownloadPerCategoryID(item->GetId());
336 if (!old_dismissed_ids.count(within_category_id)) {
337 if (IsDownloadCompleted(*item))
338 cached_asset_downloads_.push_back(item);
339 } else {
340 retained_dismissed_ids.insert(within_category_id);
341 }
342 }
343
344 if (old_dismissed_ids.size() != retained_dismissed_ids.size()) {
345 StoreAssetDismissedIDsToPrefs(retained_dismissed_ids);
346 }
347
348 if (static_cast<int>(cached_asset_downloads_.size()) > kMaxSuggestionsCount) {
349 // Partially sorts |downloads| such that:
350 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
351 // element which would occur on this position if |downloads| was sorted;
352 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
353 // or equal to the elements after it.
354 std::nth_element(cached_asset_downloads_.begin(),
355 cached_asset_downloads_.begin() + kMaxSuggestionsCount,
356 cached_asset_downloads_.end(),
357 OrderDownloadsMostRecentlyDownloadedFirst());
358 cached_asset_downloads_.resize(kMaxSuggestionsCount);
359 }
360 }
361
362 void DownloadSuggestionsProvider::
363 AsynchronouslyFetchAllDownloadsAndSubmitSuggestions() {
364 FetchAssetsDownloads();
365 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/true);
366 }
367
368 void DownloadSuggestionsProvider::SubmitContentSuggestions() {
369 NotifyStatusChanged(CategoryStatus::AVAILABLE);
370
371 std::vector<ContentSuggestion> suggestions;
372 for (const OfflinePageItem& item : cached_offline_page_downloads_)
373 suggestions.push_back(ConvertOfflinePage(item));
374
375 for (const DownloadItem* item : cached_asset_downloads_)
376 suggestions.push_back(ConvertDownloadItem(*item));
377
378 std::sort(suggestions.begin(), suggestions.end(),
379 [](const ContentSuggestion& left, const ContentSuggestion& right) {
380 return left.publish_date() > right.publish_date();
381 });
382
383 // |resize()| cannot be used here (does not compile), because
384 // |ContentSuggestion|'s move constructor is not marked |noexcept|. |resize()|
385 // provides a strong guarantee that it either succeeds or the state of the
386 // vector is left unchanged, therefore, the only option left is to copy, but
387 // |ContentSuggestion| is not copyable either.
388 // On the contrary, |pop_back()| does not reallocate any memory.
389 while (suggestions.size() > kMaxSuggestionsCount)
390 suggestions.pop_back();
391
392 observer()->OnNewSuggestions(this, provided_category_,
393 std::move(suggestions));
394 }
395
396 ContentSuggestion DownloadSuggestionsProvider::ConvertOfflinePage(
397 const OfflinePageItem& offline_page) const {
398 // TODO(vitaliii): Make sure the URL is actually opened as an offline URL even
399 // when the user is online. See crbug.com/641568.
400 ContentSuggestion suggestion(
401 ContentSuggestion::ID(provided_category_, GetOfflinePagePerCategoryID(
402 offline_page.offline_id)),
403 offline_page.url);
404
405 if (offline_page.title.empty()) {
406 // TODO(vitaliii): Remove this fallback once the OfflinePageModel provides
407 // titles for all (relevant) OfflinePageItems.
408 suggestion.set_title(base::UTF8ToUTF16(offline_page.url.spec()));
409 } else {
410 suggestion.set_title(offline_page.title);
411 }
412 suggestion.set_publish_date(offline_page.creation_time);
413 suggestion.set_publisher_name(base::UTF8ToUTF16(offline_page.url.host()));
414 return suggestion;
415 }
416
417 ContentSuggestion DownloadSuggestionsProvider::ConvertDownloadItem(
418 const DownloadItem& download_item) const {
419 // TODO(vitaliii): Ensure that files are opened in browser, but not downloaded
420 // again. See crbug.com/641568.
421 ContentSuggestion suggestion(
422 ContentSuggestion::ID(provided_category_, GetAssetDownloadPerCategoryID(
423 download_item.GetId())),
424 net::FilePathToFileURL(download_item.GetTargetFilePath()));
425 #if defined(OS_POSIX)
426 // TODO(vitaliii): Make a function in FilePath to get UTF16 (i.e. move this
427 // #if block there).
Marc Treib 2016/10/17 10:18:40 ...we don't have some helper for this?! Ugh... I'
vitaliii 2016/10/26 00:07:55 Done.
428 // TODO(vitaliii): Set proper title.
429 DCHECK(
430 base::IsStringUTF8(download_item.GetTargetFilePath().BaseName().value()));
431 suggestion.set_title(
432 base::UTF8ToUTF16(download_item.GetTargetFilePath().BaseName().value()));
433 #elif defined(OS_WIN)
434 suggestion.set_title(
435 base::WideToUTF16(download_item.GetTargetFilePath().BaseName().value()));
436 #endif // OS_WIN
437 suggestion.set_publish_date(download_item.GetEndTime());
438 suggestion.set_publisher_name(
439 base::UTF8ToUTF16(download_item.GetURL().host()));
440 // TODO(vitaliii): Set suggestion icon.
441 return suggestion;
442 }
443
444 bool DownloadSuggestionsProvider::CacheAssetDownloadIfNeeded(
445 const content::DownloadItem* item) {
446 if (!IsDownloadCompleted(*item))
447 return false;
448
449 if (base::ContainsValue(cached_asset_downloads_, item))
450 return false;
451
452 std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs();
453 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
454 return false;
455
456 DCHECK_LE(static_cast<int>(cached_asset_downloads_.size()),
457 kMaxSuggestionsCount);
458 if (cached_asset_downloads_.size() == kMaxSuggestionsCount) {
459 auto oldest = std::max_element(cached_asset_downloads_.begin(),
460 cached_asset_downloads_.end(),
461 OrderDownloadsMostRecentlyDownloadedFirst());
462 if (item->GetEndTime() <= (*oldest)->GetEndTime())
463 return false;
464
465 *oldest = item;
466 } else {
467 cached_asset_downloads_.push_back(item);
468 }
469
470 return true;
471 }
472
473 bool DownloadSuggestionsProvider::RemoveSuggestionFromCacheIfPresent(
474 const ContentSuggestion::ID& suggestion_id) {
475 DCHECK_EQ(provided_category_, suggestion_id.category());
476 if (CorrespondsToOfflinePage(suggestion_id)) {
477 auto matching =
478 std::find_if(cached_offline_page_downloads_.begin(),
479 cached_offline_page_downloads_.end(),
480 [&suggestion_id](const OfflinePageItem& item) {
481 return GetOfflinePagePerCategoryID(item.offline_id) ==
482 suggestion_id.id_within_category();
483 });
484 if (matching != cached_offline_page_downloads_.end()) {
485 cached_offline_page_downloads_.erase(matching);
486 return true;
487 }
488 return false;
489 }
490
491 auto matching = std::find_if(
492 cached_asset_downloads_.begin(), cached_asset_downloads_.end(),
493 [&suggestion_id](const DownloadItem* item) {
494 return GetAssetDownloadPerCategoryID(item->GetId()) ==
495 suggestion_id.id_within_category();
496 });
497 if (matching != cached_asset_downloads_.end()) {
498 cached_asset_downloads_.erase(matching);
499 return true;
500 }
501 return false;
502 }
503
504 void DownloadSuggestionsProvider::
505 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(
506 const ContentSuggestion::ID& suggestion_id) {
507 DCHECK_EQ(provided_category_, suggestion_id.category());
508 if (!RemoveSuggestionFromCacheIfPresent(suggestion_id))
509 return;
510
511 if (CorrespondsToOfflinePage(suggestion_id)) {
512 if (cached_offline_page_downloads_.size() == kMaxSuggestionsCount - 1) {
513 // Previously there were |kMaxSuggestionsCount| cached suggestion,
514 // therefore, overall there may be more than |kMaxSuggestionsCount|
515 // suggestions in the model and now one of them may be cached instead of
516 // the removed one. Even though, the suggestions are not immediately
517 // used the cache has to be kept up to date, because it may be used when
518 // other data source is updated.
519 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/false);
520 }
521 } else {
522 if (cached_asset_downloads_.size() == kMaxSuggestionsCount - 1) {
523 // The same as the case above.
524 FetchAssetsDownloads();
525 }
526 }
527 }
528
529 void DownloadSuggestionsProvider::UpdateOfflinePagesCache(
530 const std::vector<offline_pages::OfflinePageItem>& all_offline_pages) {
531 std::set<std::string> old_dismissed_ids =
532 ReadOfflinePageDismissedIDsFromPrefs();
533 std::set<std::string> retained_dismissed_ids;
534 std::vector<const OfflinePageItem*> items;
535 // Filtering out dismissed items and pruning dismissed IDs.
536 for (const OfflinePageItem& item : all_offline_pages) {
537 if (!IsOfflinePageDownload(item.client_id))
538 continue;
539
540 std::string id_within_category =
541 GetOfflinePagePerCategoryID(item.offline_id);
542 if (!old_dismissed_ids.count(id_within_category))
543 items.push_back(&item);
544 else
545 retained_dismissed_ids.insert(id_within_category);
546 }
547
548 if (static_cast<int>(items.size()) > kMaxSuggestionsCount) {
549 // Partially sorts |items| such that:
550 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
551 // element which would occur on this position if |items| was sorted;
552 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
553 // or equal to the elements after it.
554 std::nth_element(
555 items.begin(), items.begin() + kMaxSuggestionsCount, items.end(),
556 [](const OfflinePageItem* left, const OfflinePageItem* right) {
557 return left->creation_time > right->creation_time;
558 });
559 items.resize(kMaxSuggestionsCount);
560 }
561
562 cached_offline_page_downloads_.clear();
563 for (const OfflinePageItem* item : items) {
564 cached_offline_page_downloads_.push_back(*item);
565 }
566
567 if (old_dismissed_ids.size() != retained_dismissed_ids.size()) {
568 StoreOfflinePageDismissedIDsToPrefs(retained_dismissed_ids);
569 }
570 }
571
572 void DownloadSuggestionsProvider::InvalidateSuggestion(
573 const std::string& id_within_category) {
574 ContentSuggestion::ID suggestion_id(provided_category_, id_within_category);
575 observer()->OnSuggestionInvalidated(this, suggestion_id);
576
577 std::set<std::string> dismissed_ids =
578 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
579 auto it = dismissed_ids.find(id_within_category);
580 if (it != dismissed_ids.end()) {
581 dismissed_ids.erase(it);
582 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
583 dismissed_ids);
584 }
585
586 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
587 }
588
589 std::set<std::string>
590 DownloadSuggestionsProvider::ReadAssetDismissedIDsFromPrefs() const {
591 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
592 *pref_service_, kDismissedAssetDownloadSuggestions);
593 }
594
595 void DownloadSuggestionsProvider::StoreAssetDismissedIDsToPrefs(
596 const std::set<std::string>& dismissed_ids) {
597 DCHECK(std::all_of(
598 dismissed_ids.begin(), dismissed_ids.end(),
599 [](const std::string& id) { return id[0] == kAssetDownloadsPrefix; }));
600 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
601 pref_service_, kDismissedAssetDownloadSuggestions, dismissed_ids);
602 }
603
604 std::set<std::string>
605 DownloadSuggestionsProvider::ReadOfflinePageDismissedIDsFromPrefs() const {
606 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
607 *pref_service_, kDismissedOfflinePageDownloadSuggestions);
608 }
609
610 void DownloadSuggestionsProvider::StoreOfflinePageDismissedIDsToPrefs(
611 const std::set<std::string>& dismissed_ids) {
612 DCHECK(std::all_of(dismissed_ids.begin(), dismissed_ids.end(),
613 [](const std::string& id) {
614 return id[0] == kOfflinePageDownloadsPrefix;
615 }));
616 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
617 pref_service_, kDismissedOfflinePageDownloadSuggestions, dismissed_ids);
618 }
619
620 std::set<std::string> DownloadSuggestionsProvider::ReadDismissedIDsFromPrefs(
621 bool for_offline_page_downloads) const {
622 if (for_offline_page_downloads) {
623 return ReadOfflinePageDismissedIDsFromPrefs();
624 }
625 return ReadAssetDismissedIDsFromPrefs();
626 }
627
628 // TODO(vitaliii): Store one set instead of two. See crbug.com/656024.
Marc Treib 2016/10/17 10:18:41 For this kind of thing (internal cleanup that has
vitaliii 2016/10/26 00:07:55 Acknowledged.
629 void DownloadSuggestionsProvider::StoreDismissedIDsToPrefs(
630 bool for_offline_page_downloads,
631 const std::set<std::string>& dismissed_ids) {
632 if (for_offline_page_downloads)
633 StoreOfflinePageDismissedIDsToPrefs(dismissed_ids);
634 else
635 StoreAssetDismissedIDsToPrefs(dismissed_ids);
636 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698