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

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: Marc's comments + tests + some corrections. 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 "components/ntp_snippets/pref_names.h"
19 #include "components/ntp_snippets/pref_util.h"
20 #include "components/offline_pages/client_namespace_constants.h"
21 #include "components/prefs/pref_registry_simple.h"
22 #include "components/prefs/pref_service.h"
23 #include "grit/components_strings.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 DCHECK((client_id.name_space == offline_pages::kAsyncNamespace ||
Marc Treib 2016/10/13 12:11:25 You can DCHECK the IsValidGUID, but not the namesp
vitaliii 2016/10/15 18:36:30 Actually I DCHECK'ed on the device, so no need for
Marc Treib 2016/10/17 10:18:40 "on the device"? (Not DCHECKing here is fine, I ju
vitaliii 2016/10/26 00:07:54 I meant that I run the app on the device while hav
74 client_id.name_space == offline_pages::kDownloadNamespace ||
75 client_id.name_space == offline_pages::kNTPSuggestionsNamespace) &&
76 base::IsValidGUID(client_id.id));
77
78 return (client_id.name_space == offline_pages::kAsyncNamespace ||
79 client_id.name_space == offline_pages::kDownloadNamespace ||
80 client_id.name_space == offline_pages::kNTPSuggestionsNamespace);
81 }
82
83 bool IsDownloadCompleted(const DownloadItem& item) {
84 return item.GetState() == DownloadItem::DownloadState::COMPLETE &&
85 !item.GetFileExternallyRemoved();
86 }
87
88 struct OrderDownloadsMostRecentlyDownloadedFirst {
89 bool operator()(const DownloadItem* left, const DownloadItem* right) const {
90 return left->GetEndTime() > right->GetEndTime();
91 }
92 };
93
94 struct SuggestionItemWrapper {
Marc Treib 2016/10/13 12:11:25 Not used anymore :)
vitaliii 2016/10/15 18:36:30 Done.
95 base::Time time;
96 bool is_offline_page;
97 int index;
98 bool operator<(const SuggestionItemWrapper& other) const {
99 return time > other.time;
100 }
101 };
102
103 } // namespace
104
105 DownloadSuggestionsProvider::DownloadSuggestionsProvider(
106 ContentSuggestionsProvider::Observer* observer,
107 ntp_snippets::CategoryFactory* category_factory,
108 const scoped_refptr<ntp_snippets::OfflinePageProxy>& offline_page_proxy,
109 content::DownloadManager* download_manager,
110 PrefService* pref_service,
111 bool download_manager_ui_enabled)
112 : ContentSuggestionsProvider(observer, category_factory),
113 category_status_(CategoryStatus::AVAILABLE_LOADING),
114 provided_category_(category_factory->FromKnownCategory(
115 ntp_snippets::KnownCategories::DOWNLOADS)),
116 offline_page_proxy_(offline_page_proxy),
117 download_manager_notifier_(download_manager, this),
118 pref_service_(pref_service),
119 download_manager_ui_enabled_(download_manager_ui_enabled),
120 weak_ptr_factory_(this) {
121 observer->OnCategoryStatusChanged(this, provided_category_, category_status_);
122 offline_page_proxy_->AddObserver(this);
123 AsyncronouslyFetchAllDownloadsAndSubmitSuggestions();
124 }
125
126 DownloadSuggestionsProvider::~DownloadSuggestionsProvider() {
127 offline_page_proxy_->RemoveObserver(this);
128 }
129
130 CategoryStatus DownloadSuggestionsProvider::GetCategoryStatus(
131 Category category) {
132 DCHECK_EQ(provided_category_, category);
133 return category_status_;
134 }
135
136 CategoryInfo DownloadSuggestionsProvider::GetCategoryInfo(Category category) {
137 DCHECK_EQ(provided_category_, category);
138 return CategoryInfo(
139 l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER),
140 ntp_snippets::ContentSuggestionsCardLayout::MINIMAL_CARD,
141 /*has_more_button=*/download_manager_ui_enabled_,
142 /*show_if_empty=*/false);
143 }
144
145 void DownloadSuggestionsProvider::DismissSuggestion(
146 const ContentSuggestion::ID& suggestion_id) {
147 DCHECK_EQ(provided_category_, suggestion_id.category());
148 std::set<std::string> dismissed_ids =
149 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
150 dismissed_ids.insert(suggestion_id.id_within_category());
151 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
152 dismissed_ids);
153
154 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
155 }
156
157 void DownloadSuggestionsProvider::FetchSuggestionImage(
158 const ContentSuggestion::ID& suggestion_id,
159 const ImageFetchedCallback& callback) {
160 // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it is
161 // available there.
162 // TODO(vitaliii): Provide site's favicon for assets downloads. See
163 // crbug.com/631447.
164 base::ThreadTaskRunnerHandle::Get()->PostTask(
165 FROM_HERE, base::Bind(callback, gfx::Image()));
166 }
167
168 void DownloadSuggestionsProvider::ClearHistory(
169 base::Time begin,
170 base::Time end,
171 const base::Callback<bool(const GURL& url)>& filter) {
172 ClearDismissedSuggestionsForDebugging(provided_category_);
173 }
174
175 void DownloadSuggestionsProvider::ClearCachedSuggestions(Category category) {
176 DCHECK_EQ(provided_category_, category);
177 // Ignored.
Marc Treib 2016/10/13 12:11:26 Maybe a comment on why it's safe to ignore?
vitaliii 2016/10/15 18:36:30 Done.
178 }
179
180 void DownloadSuggestionsProvider::GetDismissedSuggestionsForDebugging(
181 Category category,
182 const DismissedSuggestionsCallback& callback) {
183 DCHECK_EQ(provided_category_, category);
184
185 offline_page_proxy_->GetAllPages(
186 base::Bind(&DownloadSuggestionsProvider::
187 GetAllPagesCallbackForGetDismissedSuggestions,
188 weak_ptr_factory_.GetWeakPtr(), callback));
189 }
190
191 void DownloadSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
192 Category category) {
193 DCHECK_EQ(provided_category_, category);
194 StoreAssetDismissedIDsToPrefs(std::set<std::string>());
195 StoreOfflinePageDismissedIDsToPrefs(std::set<std::string>());
196 AsyncronouslyFetchAllDownloadsAndSubmitSuggestions();
197 }
198
199 // static
200 void DownloadSuggestionsProvider::RegisterProfilePrefs(
201 PrefRegistrySimple* registry) {
202 registry->RegisterListPref(kDismissedAssetDownloadSuggestions);
203 registry->RegisterListPref(kDismissedOfflinePageDownloadSuggestions);
204 }
205
206 ////////////////////////////////////////////////////////////////////////////////
207 // Private methods
208
209 void DownloadSuggestionsProvider::GetAllPagesCallbackForGetDismissedSuggestions(
210 const DismissedSuggestionsCallback& callback,
211 const std::vector<OfflinePageItem>& offline_pages) const {
212 std::set<std::string> dismissed_ids = ReadOfflinePageDismissedIDsFromPrefs();
213 std::vector<ContentSuggestion> suggestions;
214 for (const OfflinePageItem& item : offline_pages) {
215 if (dismissed_ids.count(GetOfflinePagePerCategoryID(item.offline_id)))
216 suggestions.push_back(ConvertOfflinePage(item));
217 }
218
219 DownloadManager* manager = download_manager_notifier_.GetManager();
220 if (manager) {
221 std::vector<DownloadItem*> all_downloads;
222 manager->GetAllDownloads(&all_downloads);
223
224 dismissed_ids = ReadAssetDismissedIDsFromPrefs();
225
226 for (const DownloadItem* item : all_downloads) {
227 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
228 suggestions.push_back(ConvertDownloadItem(*item));
229 }
230 }
231
232 callback.Run(std::move(suggestions));
233 }
234
235 void DownloadSuggestionsProvider::OfflinePageModelChanged(
236 const std::vector<offline_pages::OfflinePageItem>& offline_pages) {
237 NotifyStatusChanged(CategoryStatus::AVAILABLE);
Marc Treib 2016/10/13 12:11:25 Not required, SubmitContentSuggestions will do tha
vitaliii 2016/10/15 18:36:30 Done.
238 ProcessAllOfflinePages(offline_pages);
239 SubmitContentSuggestions();
240 }
241
242 void DownloadSuggestionsProvider::OfflinePageDeleted(
243 int64_t offline_id,
244 const offline_pages::ClientId& client_id) {
245 if (IsOfflinePageDownload(client_id)) {
246 InvalidateSuggestion(GetOfflinePagePerCategoryID(offline_id));
247 }
248 }
249
250 void DownloadSuggestionsProvider::OnDownloadCreated(DownloadManager* manager,
251 DownloadItem* item) {
252 // This is called when new downloads are started and on startup for existing
253 // ones.
Marc Treib 2016/10/13 12:11:25 Hm, so on startup we'll notify our observer once p
vitaliii 2016/10/15 18:36:30 Yeap unless DownloadManager changes their Observer
254 if (CacheAssetDownloadIfNeeded(item))
255 SubmitContentSuggestions();
256 }
257
258 void DownloadSuggestionsProvider::OnDownloadUpdated(DownloadManager* manager,
259 DownloadItem* item) {
260 // Unfinished downloads may become completed, therefore, this call cannot be
261 // ignored.
262 if (CacheAssetDownloadIfNeeded(item))
263 SubmitContentSuggestions();
264 }
265
266 void DownloadSuggestionsProvider::OnDownloadOpened(DownloadManager* manager,
267 DownloadItem* item) {
268 // Ignored.
269 }
270
271 void DownloadSuggestionsProvider::OnDownloadRemoved(DownloadManager* manager,
272 DownloadItem* item) {
273 if (!IsDownloadCompleted(*item))
274 return;
275 // TODO(vitaliii): Implement a better way to clean up dismissed IDs (in case
276 // some calls are missed).
277 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
278 }
279
280 void DownloadSuggestionsProvider::NotifyStatusChanged(
281 CategoryStatus new_status) {
282 DCHECK_NE(CategoryStatus::NOT_PROVIDED, category_status_);
283 DCHECK_NE(CategoryStatus::NOT_PROVIDED, new_status);
284 if (category_status_ == new_status)
285 return;
286 category_status_ = new_status;
287 observer()->OnCategoryStatusChanged(this, provided_category_,
288 category_status_);
289 }
290
291 void DownloadSuggestionsProvider::AsyncronouslyFetchOfflinePagesDownloads() {
292 offline_page_proxy_->GetAllPages(
293 base::Bind(&DownloadSuggestionsProvider::ProcessAllOfflinePages,
294 weak_ptr_factory_.GetWeakPtr()));
295 }
296
297 void DownloadSuggestionsProvider::
298 AsyncronouslyFetchOfflinePagesDownloadsAndSubmitSuggestions() {
Marc Treib 2016/10/13 12:11:25 Add a "bool notify" parameter and merge with Async
vitaliii 2016/10/15 18:36:30 Done.
299 offline_page_proxy_->GetAllPages(
300 base::Bind(&DownloadSuggestionsProvider::OfflinePageModelChanged,
301 weak_ptr_factory_.GetWeakPtr()));
302 }
303
304 void DownloadSuggestionsProvider::SyncronouslyFetchAssetsDownloads() {
305 DownloadManager* manager = download_manager_notifier_.GetManager();
306 if (!manager) {
307 // The manager has gone down.
308 return;
309 }
310
311 std::vector<DownloadItem*> all_downloads;
312 manager->GetAllDownloads(&all_downloads);
313
314 std::set<std::string> old_dismissed_ids = ReadAssetDismissedIDsFromPrefs();
315 std::set<std::string> retained_dismissed_ids;
316 std::vector<const DownloadItem*> downloads;
317 for (const DownloadItem* item : all_downloads) {
318 std::string within_category_id =
319 GetAssetDownloadPerCategoryID(item->GetId());
320 if (!old_dismissed_ids.count(within_category_id)) {
321 if (IsDownloadCompleted(*item))
322 downloads.push_back(item);
323 } else {
324 retained_dismissed_ids.insert(within_category_id);
325 }
326 }
327
328 if (old_dismissed_ids.size() != retained_dismissed_ids.size()) {
329 StoreAssetDismissedIDsToPrefs(retained_dismissed_ids);
330 }
331
332 if (static_cast<int>(downloads.size()) > kMaxSuggestionsCount) {
333 // Partially sorts |downloads| such that:
334 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
335 // element which would occur on this position if |downloads| was sorted;
336 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
337 // or equal to the elements after it.
338 std::nth_element(downloads.begin(),
339 downloads.begin() + kMaxSuggestionsCount, downloads.end(),
340 OrderDownloadsMostRecentlyDownloadedFirst());
341 downloads.resize(kMaxSuggestionsCount);
342 }
343
344 cached_asset_downloads_ = std::move(downloads);
Marc Treib 2016/10/13 12:11:25 You could put the items directly into cached_asset
vitaliii 2016/10/15 18:36:30 Done.
345 }
346
347 void DownloadSuggestionsProvider::
348 AsyncronouslyFetchAllDownloadsAndSubmitSuggestions() {
349 SyncronouslyFetchAssetsDownloads();
350 AsyncronouslyFetchOfflinePagesDownloadsAndSubmitSuggestions();
351 }
352
353 void DownloadSuggestionsProvider::SubmitContentSuggestions() {
354 NotifyStatusChanged(CategoryStatus::AVAILABLE);
355
356 std::vector<ContentSuggestion> suggestions;
357 for (const OfflinePageItem& item : cached_offline_page_downloads_)
358 suggestions.push_back(ConvertOfflinePage(item));
359
360 for (const DownloadItem* item : cached_asset_downloads_)
361 suggestions.push_back(ConvertDownloadItem(*item));
362
363 std::sort(suggestions.begin(), suggestions.end(),
364 [](const ContentSuggestion& left, const ContentSuggestion& right) {
365 return left.publish_date() > right.publish_date();
366 });
367
368 // |resize()| cannot be used here (does not compile), because
369 // |ContentSuggestion|'s move constructor is not marked |noexcept|. |resize()|
370 // provides a strong guarantee that it either succeeds or the state of the
371 // vector is left unchanged, therefore, the only option left is to copy, but
372 // |ContentSuggestion| is not copyable either.
373 // On the contrary, |pop_back()| does not reallocate any memory.
374 while (suggestions.size() > kMaxSuggestionsCount)
375 suggestions.pop_back();
Marc Treib 2016/10/13 12:11:25 I think .erase would also work, something like sug
vitaliii 2016/10/15 18:36:30 Acknowledged.
376
377 observer()->OnNewSuggestions(this, provided_category_,
378 std::move(suggestions));
379 }
380
381 ContentSuggestion DownloadSuggestionsProvider::ConvertOfflinePage(
382 const OfflinePageItem& offline_page) const {
383 // TODO(vitaliii): Make sure the URL is actually opened as an offline URL even
384 // when the user is online. See crbug.com/641568.
385 ContentSuggestion suggestion(
386 ContentSuggestion::ID(provided_category_, GetOfflinePagePerCategoryID(
387 offline_page.offline_id)),
388 offline_page.url);
389
390 if (offline_page.title.empty()) {
391 // TODO(vitaliii): Remove this fallback once the OfflinePageModel provides
392 // titles for all (relevant) OfflinePageItems.
393 suggestion.set_title(base::UTF8ToUTF16(offline_page.url.spec()));
394 } else {
395 suggestion.set_title(offline_page.title);
396 }
397 suggestion.set_publish_date(offline_page.creation_time);
398 suggestion.set_publisher_name(base::UTF8ToUTF16(offline_page.url.host()));
399 return suggestion;
400 }
401
402 ContentSuggestion DownloadSuggestionsProvider::ConvertDownloadItem(
403 const DownloadItem& download_item) const {
404 // TODO(vitaliii): Ensure that files are opened in browser, but not downloaded
405 // again. See crbug.com/641568.
406 ContentSuggestion suggestion(
407 ContentSuggestion::ID(provided_category_, GetAssetDownloadPerCategoryID(
408 download_item.GetId())),
409 net::FilePathToFileURL(download_item.GetTargetFilePath()));
410 // TODO(vitaliii): Set proper title.
411 DCHECK(
412 base::IsStringUTF8(download_item.GetTargetFilePath().BaseName().value()));
413 suggestion.set_title(
414 base::UTF8ToUTF16(download_item.GetTargetFilePath().BaseName().value()));
415 suggestion.set_publish_date(download_item.GetEndTime());
416 suggestion.set_publisher_name(
417 base::UTF8ToUTF16(download_item.GetURL().host()));
418 // TODO(vitaliii): Set suggestion icon.
419 return suggestion;
420 }
421
422 bool DownloadSuggestionsProvider::CacheAssetDownloadIfNeeded(
423 const content::DownloadItem* item) {
424 if (!IsDownloadCompleted(*item))
425 return false;
426
427 if (base::ContainsValue(cached_asset_downloads_, item))
428 return false;
Marc Treib 2016/10/13 12:11:25 Hm, is this always safe? Could the download have c
vitaliii 2016/10/15 18:36:30 Good point. I leave this check here, because the f
429
430 std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs();
431 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
432 return false;
433
434 DCHECK_LE(static_cast<int>(cached_asset_downloads_.size()),
435 kMaxSuggestionsCount);
436 if (cached_asset_downloads_.size() == kMaxSuggestionsCount) {
437 auto oldest = std::max_element(cached_asset_downloads_.begin(),
438 cached_asset_downloads_.end(),
439 OrderDownloadsMostRecentlyDownloadedFirst());
440 if (item->GetEndTime() <= (*oldest)->GetEndTime())
441 return false;
442
443 *oldest = item;
444 } else {
445 cached_asset_downloads_.push_back(item);
446 }
447
448 return true;
449 }
450
451 bool DownloadSuggestionsProvider::RemoveSuggestionFromCacheIfPresent(
452 const ContentSuggestion::ID& suggestion_id) {
453 DCHECK_EQ(provided_category_, suggestion_id.category());
454 if (CorrespondsToOfflinePage(suggestion_id)) {
455 auto matching =
456 std::find_if(cached_offline_page_downloads_.begin(),
457 cached_offline_page_downloads_.end(),
458 [&suggestion_id](const OfflinePageItem& item) {
459 return GetOfflinePagePerCategoryID(item.offline_id) ==
460 suggestion_id.id_within_category();
461 });
462 if (matching != cached_offline_page_downloads_.end()) {
463 cached_offline_page_downloads_.erase(matching);
464 return true;
465 }
466 return false;
467 }
468
469 auto matching = std::find_if(
470 cached_asset_downloads_.begin(), cached_asset_downloads_.end(),
471 [&suggestion_id](const DownloadItem* item) {
472 return GetAssetDownloadPerCategoryID(item->GetId()) ==
473 suggestion_id.id_within_category();
474 });
475 if (matching != cached_asset_downloads_.end()) {
476 cached_asset_downloads_.erase(matching);
477 return true;
478 }
479 return false;
480 }
481
482 void DownloadSuggestionsProvider::
483 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(
484 const ContentSuggestion::ID& suggestion_id) {
485 DCHECK_EQ(provided_category_, suggestion_id.category());
486 if (!RemoveSuggestionFromCacheIfPresent(suggestion_id))
487 return;
488
489 if (CorrespondsToOfflinePage(suggestion_id)) {
490 if (cached_offline_page_downloads_.size() == kMaxSuggestionsCount - 1) {
491 // Previously there were |kMaxSuggestionsCount| cached suggestion,
492 // therefore, overall there may be more than |kMaxSuggestionsCount|
493 // suggestions in the model and now one of them may be cached instead of
494 // the removed one. Even though, the suggestions are not immediately
495 // used the cache has to be kept up to date, because it may be used when
496 // other data source is updated.
497 AsyncronouslyFetchOfflinePagesDownloads();
498 }
499 } else {
500 if (cached_asset_downloads_.size() == kMaxSuggestionsCount - 1) {
501 // The same as the case above.
502 SyncronouslyFetchAssetsDownloads();
503 }
504 }
505 }
506
507 void DownloadSuggestionsProvider::ProcessAllOfflinePages(
508 const std::vector<offline_pages::OfflinePageItem>& all_offline_pages) {
509 std::set<std::string> old_dismissed_ids =
510 ReadOfflinePageDismissedIDsFromPrefs();
511 std::set<std::string> retained_dismissed_ids;
512 std::vector<const OfflinePageItem*> items;
513 // Filtering out dismissed items and pruning dismissed IDs.
514 for (const OfflinePageItem& item : all_offline_pages) {
515 if (!IsOfflinePageDownload(item.client_id))
516 continue;
517
518 std::string per_category_id = GetOfflinePagePerCategoryID(item.offline_id);
519 if (!old_dismissed_ids.count(per_category_id))
520 items.push_back(&item);
521 else
522 retained_dismissed_ids.insert(per_category_id);
523 }
524
525 if (static_cast<int>(items.size()) > kMaxSuggestionsCount) {
526 // Partially sorts |items| such that:
527 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
528 // element which would occur on this position if |items| was sorted;
529 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
530 // or equal to the elements after it.
531 std::nth_element(
532 items.begin(), items.begin() + kMaxSuggestionsCount, items.end(),
533 [](const OfflinePageItem* left, const OfflinePageItem* right) {
534 return left->creation_time > right->creation_time;
535 });
536 items.resize(kMaxSuggestionsCount);
537 }
538
539 cached_offline_page_downloads_.clear();
540 for (const OfflinePageItem* item : items) {
541 cached_offline_page_downloads_.push_back(*item);
542 }
543
544 if (old_dismissed_ids.size() != retained_dismissed_ids.size()) {
545 StoreOfflinePageDismissedIDsToPrefs(retained_dismissed_ids);
546 }
547 }
548
549 void DownloadSuggestionsProvider::InvalidateSuggestion(
550 const std::string& per_category_id) {
Marc Treib 2016/10/13 12:11:25 s/per_category_id/id_in_category/ to match the hea
vitaliii 2016/10/15 18:36:30 Done.
551 ContentSuggestion::ID suggestion_id(provided_category_, per_category_id);
552 observer()->OnSuggestionInvalidated(this, suggestion_id);
553
554 std::set<std::string> dismissed_ids =
555 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
556 auto it = dismissed_ids.find(per_category_id);
557 if (it != dismissed_ids.end()) {
558 dismissed_ids.erase(it);
559 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
560 dismissed_ids);
561 }
562
563 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
564 }
565
566 std::set<std::string>
567 DownloadSuggestionsProvider::ReadAssetDismissedIDsFromPrefs() const {
568 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
569 *pref_service_, kDismissedAssetDownloadSuggestions);
570 }
571
572 void DownloadSuggestionsProvider::StoreAssetDismissedIDsToPrefs(
573 const std::set<std::string>& dismissed_ids) {
574 DCHECK(std::all_of(
575 dismissed_ids.begin(), dismissed_ids.end(),
576 [](const std::string& id) { return id[0] == kAssetDownloadsPrefix; }));
577 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
578 pref_service_, kDismissedAssetDownloadSuggestions, dismissed_ids);
579 }
580
581 std::set<std::string>
582 DownloadSuggestionsProvider::ReadOfflinePageDismissedIDsFromPrefs() const {
583 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
584 *pref_service_, kDismissedOfflinePageDownloadSuggestions);
585 }
586
587 void DownloadSuggestionsProvider::StoreOfflinePageDismissedIDsToPrefs(
588 const std::set<std::string>& dismissed_ids) {
589 DCHECK(std::all_of(dismissed_ids.begin(), dismissed_ids.end(),
590 [](const std::string& id) {
591 return id[0] == kOfflinePageDownloadsPrefix;
592 }));
593 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
594 pref_service_, kDismissedOfflinePageDownloadSuggestions, dismissed_ids);
595 }
596
597 std::set<std::string> DownloadSuggestionsProvider::ReadDismissedIDsFromPrefs(
598 bool for_offline_page_downloads) const {
599 if (for_offline_page_downloads) {
600 return ReadOfflinePageDismissedIDsFromPrefs();
601 }
602 return ReadAssetDismissedIDsFromPrefs();
603 }
604
605 void DownloadSuggestionsProvider::StoreDismissedIDsToPrefs(
606 bool for_offline_page_downloads,
607 const std::set<std::string>& dismissed_ids) {
608 if (for_offline_page_downloads)
609 StoreOfflinePageDismissedIDsToPrefs(dismissed_ids);
610 else
611 StoreAssetDismissedIDsToPrefs(dismissed_ids);
612 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698