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

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 + Bernhard's comments + rebase. Created 4 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 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 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_(std::move(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 if (download_manager_)
110 download_manager_->AddObserver(this);
111 // No need to explicitly fetch the asset downloads, since for each of them
112 // |OnDownloadCreated| is fired.
113 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/true);
114 }
115
116 DownloadSuggestionsProvider::~DownloadSuggestionsProvider() {
117 offline_page_proxy_->RemoveObserver(this);
118 if (download_manager_) {
119 download_manager_->RemoveObserver(this);
120 UnregisterDownloadItemObservers();
121 }
122 }
123
124 CategoryStatus DownloadSuggestionsProvider::GetCategoryStatus(
125 Category category) {
126 DCHECK_EQ(provided_category_, category);
127 return category_status_;
128 }
129
130 CategoryInfo DownloadSuggestionsProvider::GetCategoryInfo(Category category) {
131 DCHECK_EQ(provided_category_, category);
132 return CategoryInfo(
133 l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER),
134 ntp_snippets::ContentSuggestionsCardLayout::MINIMAL_CARD,
135 /*has_more_button=*/download_manager_ui_enabled_,
dgn 2016/10/27 15:35:33 We now always show the button, and this flag is us
vitaliii 2016/11/01 03:09:54 I did not know that meaning of |has_more_button| h
136 /*show_if_empty=*/false,
137 l10n_util::GetStringUTF16(IDS_NTP_DOWNLOADS_SUGGESTIONS_SECTION_EMPTY));
138 }
139
140 void DownloadSuggestionsProvider::DismissSuggestion(
141 const ContentSuggestion::ID& suggestion_id) {
142 DCHECK_EQ(provided_category_, suggestion_id.category());
143 std::set<std::string> dismissed_ids =
144 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
145 dismissed_ids.insert(suggestion_id.id_within_category());
146 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
147 dismissed_ids);
148
149 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
150 }
151
152 void DownloadSuggestionsProvider::FetchSuggestionImage(
153 const ContentSuggestion::ID& suggestion_id,
154 const ImageFetchedCallback& callback) {
155 // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it is
156 // available there.
157 // TODO(vitaliii): Provide site's favicon for assets downloads. See
dgn 2016/10/27 15:35:33 For download thumbnail we could also do the same t
vitaliii 2016/11/01 03:09:54 We could, but so far it is still an open question:
158 // crbug.com/631447.
159 base::ThreadTaskRunnerHandle::Get()->PostTask(
160 FROM_HERE, base::Bind(callback, gfx::Image()));
161 }
162
163 void DownloadSuggestionsProvider::ClearHistory(
164 base::Time begin,
165 base::Time end,
166 const base::Callback<bool(const GURL& url)>& filter) {
167 cached_offline_page_downloads_.clear();
168 cached_asset_downloads_.clear();
169 // This will trigger an asynchronous re-fetch.
170 ClearDismissedSuggestionsForDebugging(provided_category_);
171 }
172
173 void DownloadSuggestionsProvider::ClearCachedSuggestions(Category category) {
174 DCHECK_EQ(provided_category_, category);
175 // Ignored. The internal caches are not stored on disk and they are just
176 // partial copies of the data stored at OfflinePage model and DownloadManager.
177 // If it is cleared there, it will be cleared in these caches as well.
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 AsynchronouslyFetchAllDownloadsAndSubmitSuggestions();
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 if (download_manager_) {
220 std::vector<DownloadItem*> all_downloads;
221 download_manager_->GetAllDownloads(&all_downloads);
222
223 dismissed_ids = ReadAssetDismissedIDsFromPrefs();
224
225 for (const DownloadItem* item : all_downloads) {
226 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
227 suggestions.push_back(ConvertDownloadItem(*item));
228 }
229 }
230
231 callback.Run(std::move(suggestions));
232 }
233
234 void DownloadSuggestionsProvider::OfflinePageModelChanged(
235 const std::vector<offline_pages::OfflinePageItem>& offline_pages) {
236 UpdateOfflinePagesCache(/*notify=*/true, offline_pages);
237 }
238
239 void DownloadSuggestionsProvider::OfflinePageDeleted(
240 int64_t offline_id,
241 const offline_pages::ClientId& client_id) {
242 if (IsOfflinePageDownload(client_id))
243 InvalidateSuggestion(GetOfflinePagePerCategoryID(offline_id));
244 }
245
246 void DownloadSuggestionsProvider::OnDownloadCreated(DownloadManager* manager,
247 DownloadItem* item) {
248 DCHECK_EQ(download_manager_, manager);
249 // This is called when new downloads are started and on startup for existing
250 // ones. We listen to each item to know when it is destroyed.
251 item->AddObserver(this);
252 if (CacheAssetDownloadIfNeeded(item))
253 SubmitContentSuggestions();
254 }
255
256 void DownloadSuggestionsProvider::ManagerGoingDown(DownloadManager* manager) {
257 DCHECK_EQ(download_manager_, manager);
258 UnregisterDownloadItemObservers();
259 download_manager_ = nullptr;
260 }
261
262 void DownloadSuggestionsProvider::OnDownloadUpdated(DownloadItem* item) {
263 if (base::ContainsValue(cached_asset_downloads_, item)) {
264 if (item->GetFileExternallyRemoved()) {
265 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
266 } else {
267 // The download may have changed.
268 SubmitContentSuggestions();
269 }
270 } else {
271 // Unfinished downloads may become completed.
272 if (CacheAssetDownloadIfNeeded(item))
273 SubmitContentSuggestions();
274 }
275 }
276
277 void DownloadSuggestionsProvider::OnDownloadOpened(DownloadItem* item) {
278 // Ignored.
279 }
280
281 void DownloadSuggestionsProvider::OnDownloadRemoved(DownloadItem* item) {
282 // Ignored. We listen to |OnDownloadDestroyed| instead. The reason is that
283 // we may need to retrieve all downloads, but |OnDownloadRemoved| is called
284 // before the download is removed from the list.
285 }
286
287 void DownloadSuggestionsProvider::OnDownloadDestroyed(
288 content::DownloadItem* item) {
289 item->RemoveObserver(this);
290
291 if (!IsDownloadCompleted(*item))
292 return;
293 // TODO(vitaliii): Implement a better way to clean up dismissed IDs (in case
294 // some calls are missed).
295 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
296 }
297
298 void DownloadSuggestionsProvider::NotifyStatusChanged(
299 CategoryStatus new_status) {
300 DCHECK_NE(CategoryStatus::NOT_PROVIDED, category_status_);
301 DCHECK_NE(CategoryStatus::NOT_PROVIDED, new_status);
302 if (category_status_ == new_status)
303 return;
304 category_status_ = new_status;
305 observer()->OnCategoryStatusChanged(this, provided_category_,
306 category_status_);
307 }
308
309 void DownloadSuggestionsProvider::AsynchronouslyFetchOfflinePagesDownloads(
310 bool notify) {
311 offline_page_proxy_->GetAllPages(
312 base::Bind(&DownloadSuggestionsProvider::UpdateOfflinePagesCache,
313 weak_ptr_factory_.GetWeakPtr(), notify));
314 }
315
316 void DownloadSuggestionsProvider::FetchAssetsDownloads() {
317 if (!download_manager_) {
318 // The manager has gone down.
319 return;
320 }
321
322 std::vector<DownloadItem*> all_downloads;
323 download_manager_->GetAllDownloads(&all_downloads);
324 std::set<std::string> old_dismissed_ids = ReadAssetDismissedIDsFromPrefs();
325 std::set<std::string> retained_dismissed_ids;
326 cached_asset_downloads_.clear();
327 for (const DownloadItem* item : all_downloads) {
328 std::string within_category_id =
329 GetAssetDownloadPerCategoryID(item->GetId());
330 if (!old_dismissed_ids.count(within_category_id)) {
331 if (IsDownloadCompleted(*item))
332 cached_asset_downloads_.push_back(item);
333 } else {
334 retained_dismissed_ids.insert(within_category_id);
335 }
336 }
337
338 if (old_dismissed_ids.size() != retained_dismissed_ids.size())
339 StoreAssetDismissedIDsToPrefs(retained_dismissed_ids);
340
341 if (static_cast<int>(cached_asset_downloads_.size()) > kMaxSuggestionsCount) {
342 // Partially sorts |downloads| such that:
343 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
344 // element which would occur on this position if |downloads| was sorted;
345 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
346 // or equal to the elements after it.
347 std::nth_element(cached_asset_downloads_.begin(),
348 cached_asset_downloads_.begin() + kMaxSuggestionsCount,
349 cached_asset_downloads_.end(),
350 OrderDownloadsMostRecentlyDownloadedFirst());
351 cached_asset_downloads_.resize(kMaxSuggestionsCount);
352 }
353 }
354
355 void DownloadSuggestionsProvider::
356 AsynchronouslyFetchAllDownloadsAndSubmitSuggestions() {
357 FetchAssetsDownloads();
358 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/true);
359 }
360
361 void DownloadSuggestionsProvider::SubmitContentSuggestions() {
362 NotifyStatusChanged(CategoryStatus::AVAILABLE);
363
364 std::vector<ContentSuggestion> suggestions;
365 for (const OfflinePageItem& item : cached_offline_page_downloads_)
366 suggestions.push_back(ConvertOfflinePage(item));
367
368 for (const DownloadItem* item : cached_asset_downloads_)
369 suggestions.push_back(ConvertDownloadItem(*item));
370
371 std::sort(suggestions.begin(), suggestions.end(),
372 [](const ContentSuggestion& left, const ContentSuggestion& right) {
373 return left.publish_date() > right.publish_date();
374 });
375
376 // |resize()| cannot be used here (does not compile), because
377 // |ContentSuggestion|'s move constructor is not marked |noexcept|. |resize()|
378 // provides a strong guarantee that it either succeeds or the state of the
379 // vector is left unchanged, therefore, the only option left is to copy, but
380 // |ContentSuggestion| is not copyable either.
381 // On the contrary, |pop_back()| does not reallocate any memory.
382 while (suggestions.size() > kMaxSuggestionsCount)
383 suggestions.pop_back();
384
385 observer()->OnNewSuggestions(this, provided_category_,
386 std::move(suggestions));
387 }
388
389 ContentSuggestion DownloadSuggestionsProvider::ConvertOfflinePage(
390 const OfflinePageItem& offline_page) const {
391 // TODO(vitaliii): Make sure the URL is actually opened as an offline URL even
392 // when the user is online. See crbug.com/641568.
393 ContentSuggestion suggestion(
394 ContentSuggestion::ID(provided_category_, GetOfflinePagePerCategoryID(
395 offline_page.offline_id)),
396 offline_page.url);
397
398 if (offline_page.title.empty()) {
399 // TODO(vitaliii): Remove this fallback once the OfflinePageModel provides
400 // titles for all (relevant) OfflinePageItems.
401 suggestion.set_title(base::UTF8ToUTF16(offline_page.url.spec()));
402 } else {
403 suggestion.set_title(offline_page.title);
404 }
405 suggestion.set_publish_date(offline_page.creation_time);
406 suggestion.set_publisher_name(base::UTF8ToUTF16(offline_page.url.host()));
407 return suggestion;
408 }
409
410 ContentSuggestion DownloadSuggestionsProvider::ConvertDownloadItem(
411 const DownloadItem& download_item) const {
412 // TODO(vitaliii): Ensure that files are opened in browser, but not downloaded
413 // again. See crbug.com/641568.
414 ContentSuggestion suggestion(
415 ContentSuggestion::ID(provided_category_, GetAssetDownloadPerCategoryID(
416 download_item.GetId())),
417 net::FilePathToFileURL(download_item.GetTargetFilePath()));
418 // TODO(vitaliii): Set proper title.
419 suggestion.set_title(
420 download_item.GetTargetFilePath().BaseName().LossyDisplayName());
421 suggestion.set_publish_date(download_item.GetEndTime());
422 suggestion.set_publisher_name(
423 base::UTF8ToUTF16(download_item.GetURL().host()));
424 // TODO(vitaliii): Set suggestion icon.
425 return suggestion;
426 }
427
428 bool DownloadSuggestionsProvider::CacheAssetDownloadIfNeeded(
429 const content::DownloadItem* item) {
430 if (!IsDownloadCompleted(*item))
431 return false;
432
433 if (base::ContainsValue(cached_asset_downloads_, item))
434 return false;
435
436 std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs();
437 if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
438 return false;
439
440 DCHECK_LE(static_cast<int>(cached_asset_downloads_.size()),
441 kMaxSuggestionsCount);
442 if (cached_asset_downloads_.size() == kMaxSuggestionsCount) {
443 auto oldest = std::max_element(cached_asset_downloads_.begin(),
444 cached_asset_downloads_.end(),
445 OrderDownloadsMostRecentlyDownloadedFirst());
446 if (item->GetEndTime() <= (*oldest)->GetEndTime())
447 return false;
448
449 *oldest = item;
450 } else {
451 cached_asset_downloads_.push_back(item);
452 }
453
454 return true;
455 }
456
457 bool DownloadSuggestionsProvider::RemoveSuggestionFromCacheIfPresent(
458 const ContentSuggestion::ID& suggestion_id) {
459 DCHECK_EQ(provided_category_, suggestion_id.category());
460 if (CorrespondsToOfflinePage(suggestion_id)) {
461 auto matching =
462 std::find_if(cached_offline_page_downloads_.begin(),
463 cached_offline_page_downloads_.end(),
464 [&suggestion_id](const OfflinePageItem& item) {
465 return GetOfflinePagePerCategoryID(item.offline_id) ==
466 suggestion_id.id_within_category();
467 });
468 if (matching != cached_offline_page_downloads_.end()) {
469 cached_offline_page_downloads_.erase(matching);
470 return true;
471 }
472 return false;
473 }
474
475 auto matching = std::find_if(
476 cached_asset_downloads_.begin(), cached_asset_downloads_.end(),
477 [&suggestion_id](const DownloadItem* item) {
478 return GetAssetDownloadPerCategoryID(item->GetId()) ==
479 suggestion_id.id_within_category();
480 });
481 if (matching != cached_asset_downloads_.end()) {
482 cached_asset_downloads_.erase(matching);
483 return true;
484 }
485 return false;
486 }
487
488 void DownloadSuggestionsProvider::
489 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(
490 const ContentSuggestion::ID& suggestion_id) {
491 DCHECK_EQ(provided_category_, suggestion_id.category());
492 if (!RemoveSuggestionFromCacheIfPresent(suggestion_id))
493 return;
494
495 if (CorrespondsToOfflinePage(suggestion_id)) {
496 if (cached_offline_page_downloads_.size() == kMaxSuggestionsCount - 1) {
497 // Previously there were |kMaxSuggestionsCount| cached suggestion,
498 // therefore, overall there may be more than |kMaxSuggestionsCount|
499 // suggestions in the model and now one of them may be cached instead of
500 // the removed one. Even though, the suggestions are not immediately
501 // used the cache has to be kept up to date, because it may be used when
502 // other data source is updated.
503 AsynchronouslyFetchOfflinePagesDownloads(/*notify=*/false);
504 }
505 } else {
506 if (cached_asset_downloads_.size() == kMaxSuggestionsCount - 1) {
507 // The same as the case above.
508 FetchAssetsDownloads();
509 }
510 }
511 }
512
513 void DownloadSuggestionsProvider::UpdateOfflinePagesCache(
514 bool notify,
515 const std::vector<offline_pages::OfflinePageItem>& all_offline_pages) {
516 std::set<std::string> old_dismissed_ids =
517 ReadOfflinePageDismissedIDsFromPrefs();
518 std::set<std::string> retained_dismissed_ids;
519 std::vector<const OfflinePageItem*> items;
520 // Filtering out dismissed items and pruning dismissed IDs.
521 for (const OfflinePageItem& item : all_offline_pages) {
522 if (!IsOfflinePageDownload(item.client_id))
523 continue;
524
525 std::string id_within_category =
526 GetOfflinePagePerCategoryID(item.offline_id);
527 if (!old_dismissed_ids.count(id_within_category))
528 items.push_back(&item);
529 else
530 retained_dismissed_ids.insert(id_within_category);
531 }
532
533 if (static_cast<int>(items.size()) > kMaxSuggestionsCount) {
534 // Partially sorts |items| such that:
535 // 1) The element at the index |kMaxSuggestionsCount| is changed to the
536 // element which would occur on this position if |items| was sorted;
537 // 2) All of the elements before index |kMaxSuggestionsCount| are less than
538 // or equal to the elements after it.
539 std::nth_element(
540 items.begin(), items.begin() + kMaxSuggestionsCount, items.end(),
541 [](const OfflinePageItem* left, const OfflinePageItem* right) {
542 return left->creation_time > right->creation_time;
543 });
544 items.resize(kMaxSuggestionsCount);
545 }
546
547 cached_offline_page_downloads_.clear();
548 for (const OfflinePageItem* item : items)
549 cached_offline_page_downloads_.push_back(*item);
550
551 if (old_dismissed_ids.size() != retained_dismissed_ids.size())
552 StoreOfflinePageDismissedIDsToPrefs(retained_dismissed_ids);
553
554 if (notify)
555 SubmitContentSuggestions();
556 }
557
558 void DownloadSuggestionsProvider::InvalidateSuggestion(
559 const std::string& id_within_category) {
560 ContentSuggestion::ID suggestion_id(provided_category_, id_within_category);
561 observer()->OnSuggestionInvalidated(this, suggestion_id);
562
563 std::set<std::string> dismissed_ids =
564 ReadDismissedIDsFromPrefs(CorrespondsToOfflinePage(suggestion_id));
565 auto it = dismissed_ids.find(id_within_category);
566 if (it != dismissed_ids.end()) {
567 dismissed_ids.erase(it);
568 StoreDismissedIDsToPrefs(CorrespondsToOfflinePage(suggestion_id),
569 dismissed_ids);
570 }
571
572 RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
573 }
574
575 std::set<std::string>
576 DownloadSuggestionsProvider::ReadAssetDismissedIDsFromPrefs() const {
577 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
578 *pref_service_, kDismissedAssetDownloadSuggestions);
579 }
580
581 void DownloadSuggestionsProvider::StoreAssetDismissedIDsToPrefs(
582 const std::set<std::string>& dismissed_ids) {
583 DCHECK(std::all_of(
584 dismissed_ids.begin(), dismissed_ids.end(),
585 [](const std::string& id) { return id[0] == kAssetDownloadsPrefix; }));
586 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
587 pref_service_, kDismissedAssetDownloadSuggestions, dismissed_ids);
588 }
589
590 std::set<std::string>
591 DownloadSuggestionsProvider::ReadOfflinePageDismissedIDsFromPrefs() const {
592 return ntp_snippets::prefs::ReadDismissedIDsFromPrefs(
593 *pref_service_, kDismissedOfflinePageDownloadSuggestions);
594 }
595
596 void DownloadSuggestionsProvider::StoreOfflinePageDismissedIDsToPrefs(
597 const std::set<std::string>& dismissed_ids) {
598 DCHECK(std::all_of(dismissed_ids.begin(), dismissed_ids.end(),
599 [](const std::string& id) {
600 return id[0] == kOfflinePageDownloadsPrefix;
601 }));
602 ntp_snippets::prefs::StoreDismissedIDsToPrefs(
603 pref_service_, kDismissedOfflinePageDownloadSuggestions, dismissed_ids);
604 }
605
606 std::set<std::string> DownloadSuggestionsProvider::ReadDismissedIDsFromPrefs(
607 bool for_offline_page_downloads) const {
608 if (for_offline_page_downloads)
609 return ReadOfflinePageDismissedIDsFromPrefs();
610 return ReadAssetDismissedIDsFromPrefs();
611 }
612
613 // TODO(vitaliii): Store one set instead of two. See crbug.com/656024.
614 void DownloadSuggestionsProvider::StoreDismissedIDsToPrefs(
615 bool for_offline_page_downloads,
616 const std::set<std::string>& dismissed_ids) {
617 if (for_offline_page_downloads)
618 StoreOfflinePageDismissedIDsToPrefs(dismissed_ids);
619 else
620 StoreAssetDismissedIDsToPrefs(dismissed_ids);
621 }
622
623 void DownloadSuggestionsProvider::UnregisterDownloadItemObservers() {
624 if (!download_manager_)
625 return;
626
627 std::vector<DownloadItem*> all_downloads;
628 download_manager_->GetAllDownloads(&all_downloads);
629
630 for (DownloadItem* item : all_downloads)
631 item->RemoveObserver(this);
632 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698