Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 "components/ntp_snippets/downloads/download_suggestions_provider.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <utility> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/guid.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/strings/string_util.h" | |
| 14 #include "base/strings/utf_string_conversions.h" | |
| 15 #include "base/threading/thread_task_runner_handle.h" | |
| 16 #include "components/ntp_snippets/pref_names.h" | |
| 17 #include "components/ntp_snippets/pref_util.h" | |
| 18 #include "components/offline_pages/client_namespace_constants.h" | |
| 19 #include "components/prefs/pref_registry_simple.h" | |
| 20 #include "components/prefs/pref_service.h" | |
| 21 #include "grit/components_strings.h" | |
| 22 #include "net/base/filename_util.h" | |
| 23 #include "ui/base/l10n/l10n_util.h" | |
| 24 #include "ui/gfx/image/image.h" | |
| 25 | |
| 26 using content::DownloadItem; | |
| 27 using content::DownloadManager; | |
| 28 using offline_pages::OfflinePageItem; | |
| 29 | |
| 30 namespace ntp_snippets { | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 const int kMaxSuggestionsCount = 5; | |
| 35 const char kDownloadsPrefix[] = "D"; | |
| 36 const char kOfflinePagesPrefix[] = "O"; | |
| 37 | |
| 38 std::string GetOfflinePagePerCategoryID(int64_t raw_offline_page_id) { | |
| 39 // Raw ID is prefixed in order to avoid conflicts with Downloads. | |
| 40 return base::JoinString( | |
| 41 {kOfflinePagesPrefix, base::IntToString(raw_offline_page_id)}, ""); | |
| 42 } | |
| 43 | |
| 44 std::string GetAssetDownloadPerCategoryID(uint32_t raw_download_id) { | |
| 45 // Raw ID is prefixed in order to avoid conflicts with OfflinePages. | |
| 46 return base::JoinString( | |
| 47 {kDownloadsPrefix, base::UintToString(raw_download_id)}, ""); | |
| 48 } | |
| 49 | |
| 50 bool IsOfflinePageID(const std::string& within_category_id) { | |
| 51 if (!within_category_id.empty()) { | |
| 52 if (within_category_id[0] == kOfflinePagesPrefix[0]) | |
| 53 return true; | |
| 54 if (within_category_id[0] == kDownloadsPrefix[0]) | |
| 55 return false; | |
| 56 } | |
| 57 NOTREACHED() << "Unknown within_category_id " << within_category_id; | |
| 58 return false; | |
| 59 } | |
| 60 | |
| 61 struct OrderOfflinePagesByMostRecentlyVisitedFirst { | |
| 62 bool operator()(const OfflinePageItem* left, | |
| 63 const OfflinePageItem* right) const { | |
| 64 return left->last_access_time > right->last_access_time; | |
| 65 } | |
| 66 }; | |
| 67 | |
| 68 bool IsOfflinePageDownload(const offline_pages::ClientId& client_id) { | |
| 69 return (client_id.name_space == offline_pages::kAsyncNamespace || | |
| 70 client_id.name_space == offline_pages::kDownloadNamespace || | |
| 71 client_id.name_space == offline_pages::kNTPSuggestionsNamespace) && | |
| 72 base::IsValidGUID(client_id.id); | |
| 73 } | |
| 74 | |
| 75 bool IsDownloadCompleted(const DownloadItem* item) { | |
| 76 return item->GetState() == content::DownloadItem::DownloadState::COMPLETE && | |
| 77 !item->GetFileExternallyRemoved(); | |
| 78 } | |
| 79 | |
| 80 struct OrderDownloadsMostRecentlyDownloadedCompletedFirst { | |
| 81 bool operator()(const DownloadItem* left, const DownloadItem* right) const { | |
| 82 if (IsDownloadCompleted(left) != IsDownloadCompleted(right)) | |
| 83 return IsDownloadCompleted(left); | |
| 84 return left->GetEndTime() > right->GetEndTime(); | |
| 85 } | |
| 86 }; | |
| 87 | |
| 88 struct SuggestionItemWrapper { | |
| 89 base::Time time; | |
| 90 bool is_offline_page; | |
| 91 int index; | |
| 92 bool operator<(const SuggestionItemWrapper& other) const { | |
| 93 return time > other.time; | |
| 94 } | |
| 95 }; | |
| 96 | |
| 97 } // namespace | |
| 98 | |
| 99 DownloadSuggestionsProvider::DownloadSuggestionsProvider( | |
| 100 ContentSuggestionsProvider::Observer* observer, | |
| 101 CategoryFactory* category_factory, | |
| 102 const scoped_refptr<OfflinePageProxy>& offline_page_proxy, | |
| 103 content::DownloadManager* download_manager, | |
| 104 PrefService* pref_service, | |
| 105 bool download_manager_ui_enabled) | |
| 106 : ContentSuggestionsProvider(observer, category_factory), | |
| 107 category_status_(CategoryStatus::AVAILABLE_LOADING), | |
| 108 provided_category_( | |
| 109 category_factory->FromKnownCategory(KnownCategories::DOWNLOADS)), | |
| 110 offline_page_proxy_(offline_page_proxy), | |
| 111 download_manager_notifier_(download_manager, this), | |
| 112 pref_service_(pref_service), | |
| 113 download_manager_ui_enabled_(download_manager_ui_enabled), | |
| 114 weak_ptr_factory_(this) { | |
| 115 observer->OnCategoryStatusChanged(this, provided_category_, category_status_); | |
| 116 offline_page_proxy_->AddObserver(this); | |
| 117 FetchAllDownloadsAndSubmitSuggestions(); | |
| 118 } | |
| 119 | |
| 120 DownloadSuggestionsProvider::~DownloadSuggestionsProvider() { | |
| 121 offline_page_proxy_->RemoveObserver(this); | |
| 122 } | |
| 123 | |
| 124 CategoryStatus DownloadSuggestionsProvider::GetCategoryStatus( | |
| 125 Category category) { | |
| 126 if (category == provided_category_) | |
| 127 return category_status_; | |
| 128 NOTREACHED() << "Unknown category " << category.id(); | |
| 129 return CategoryStatus::NOT_PROVIDED; | |
| 130 } | |
| 131 | |
| 132 CategoryInfo DownloadSuggestionsProvider::GetCategoryInfo(Category category) { | |
| 133 if (category == provided_category_) { | |
| 134 return CategoryInfo( | |
| 135 l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER), | |
| 136 ContentSuggestionsCardLayout::MINIMAL_CARD, | |
| 137 /*has_more_button=*/download_manager_ui_enabled_, | |
| 138 /*show_if_empty=*/false); | |
| 139 } | |
| 140 NOTREACHED() << "Unknown category " << category.id(); | |
| 141 return CategoryInfo(base::string16(), | |
| 142 ContentSuggestionsCardLayout::MINIMAL_CARD, | |
| 143 /*has_more_button=*/false, | |
| 144 /*show_if_empty=*/false); | |
| 145 } | |
| 146 | |
| 147 void DownloadSuggestionsProvider::DismissSuggestion( | |
| 148 const std::string& suggestion_id) { | |
| 149 DCHECK_EQ(provided_category_, GetCategoryFromUniqueID(suggestion_id)); | |
| 150 std::string within_category_id = | |
| 151 GetWithinCategoryIDFromUniqueID(suggestion_id); | |
| 152 std::set<std::string> dismissed_ids = | |
| 153 (IsOfflinePageID(within_category_id) | |
| 154 ? ReadOfflinePageDismissedIDsFromPrefs() | |
| 155 : ReadAssetDismissedIDsFromPrefs()); | |
| 156 | |
| 157 dismissed_ids.insert(within_category_id); | |
| 158 if (IsOfflinePageID(within_category_id)) | |
| 159 StoreOfflinePageDismissedIDsToPrefs(dismissed_ids); | |
| 160 else | |
| 161 StoreAssetDismissedIDsToPrefs(dismissed_ids); | |
| 162 } | |
| 163 | |
| 164 void DownloadSuggestionsProvider::FetchSuggestionImage( | |
| 165 const std::string& suggestion_id, | |
| 166 const ImageFetchedCallback& callback) { | |
| 167 // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it's | |
| 168 // available there. | |
| 169 // TODO(vitaliii): Provide site's favicon for assets downloads. | |
| 170 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 171 FROM_HERE, base::Bind(callback, gfx::Image())); | |
| 172 } | |
| 173 | |
| 174 void DownloadSuggestionsProvider::ClearHistory( | |
| 175 base::Time begin, | |
| 176 base::Time end, | |
| 177 const base::Callback<bool(const GURL& url)>& filter) { | |
| 178 ClearDismissedSuggestionsForDebugging(provided_category_); | |
| 179 cached_offline_page_downloads_.clear(); | |
| 180 cached_asset_downloads_.clear(); | |
| 181 FetchAllDownloadsAndSubmitSuggestions(); | |
| 182 } | |
| 183 | |
| 184 void DownloadSuggestionsProvider::ClearCachedSuggestions(Category category) { | |
| 185 // Ignored. | |
| 186 } | |
| 187 | |
| 188 void DownloadSuggestionsProvider::GetDismissedSuggestionsForDebugging( | |
| 189 Category category, | |
| 190 const DismissedSuggestionsCallback& callback) { | |
| 191 DCHECK_EQ(provided_category_, category); | |
| 192 // TODO(vitaliii): Implement. | |
| 193 callback.Run(std::vector<ContentSuggestion>()); | |
| 194 } | |
| 195 | |
| 196 void DownloadSuggestionsProvider::ClearDismissedSuggestionsForDebugging( | |
| 197 Category category) { | |
| 198 DCHECK_EQ(provided_category_, category); | |
| 199 StoreAssetDismissedIDsToPrefs(std::set<std::string>()); | |
| 200 StoreOfflinePageDismissedIDsToPrefs(std::set<std::string>()); | |
| 201 FetchAllDownloadsAndSubmitSuggestions(); | |
| 202 } | |
| 203 | |
| 204 // static | |
| 205 void DownloadSuggestionsProvider::RegisterProfilePrefs( | |
| 206 PrefRegistrySimple* registry) { | |
| 207 registry->RegisterListPref(prefs::kDismissedAssetDownloadSuggestions); | |
| 208 registry->RegisterListPref(prefs::kDismissedOfflinePageDownloadSuggestions); | |
| 209 } | |
| 210 | |
| 211 //////////////////////////////////////////////////////////////////////////////// | |
| 212 // Private methods | |
| 213 | |
| 214 void DownloadSuggestionsProvider::OfflinePageModelChanged( | |
| 215 const std::vector<offline_pages::OfflinePageItem>& offline_pages) { | |
| 216 NotifyStatusChanged(CategoryStatus::AVAILABLE); | |
| 217 | |
| 218 std::set<std::string> old_dismissed_ids = | |
| 219 ReadOfflinePageDismissedIDsFromPrefs(); | |
| 220 std::set<std::string> new_dismissed_ids; | |
| 221 std::vector<const OfflinePageItem*> items; | |
| 222 for (const OfflinePageItem& item : offline_pages) { | |
| 223 std::string per_category_id = GetOfflinePagePerCategoryID(item.offline_id); | |
| 224 if (!IsOfflinePageDownload(item.client_id)) | |
| 225 return; | |
| 226 | |
| 227 if (!old_dismissed_ids.count(per_category_id)) | |
| 228 items.push_back(&item); | |
| 229 else | |
| 230 new_dismissed_ids.insert(per_category_id); | |
| 231 } | |
| 232 | |
| 233 if (static_cast<int>(items.size()) > kMaxSuggestionsCount) { | |
| 234 std::nth_element(items.begin(), items.begin() + kMaxSuggestionsCount, | |
| 235 items.end(), | |
| 236 OrderOfflinePagesByMostRecentlyVisitedFirst()); | |
| 237 items.resize(kMaxSuggestionsCount); | |
| 238 } | |
| 239 | |
| 240 cached_offline_page_downloads_.clear(); | |
| 241 for (const OfflinePageItem* item : items) { | |
| 242 cached_offline_page_downloads_.push_back(*item); | |
| 243 } | |
| 244 | |
| 245 if (old_dismissed_ids.size() != new_dismissed_ids.size()) { | |
| 246 StoreOfflinePageDismissedIDsToPrefs(new_dismissed_ids); | |
| 247 } | |
| 248 | |
| 249 SubmitContentSuggestions(); | |
| 250 } | |
| 251 | |
| 252 void DownloadSuggestionsProvider::OfflinePageDeleted( | |
| 253 int64_t offline_id, | |
| 254 const offline_pages::ClientId& client_id) { | |
| 255 // Because we never switch to NOT_PROVIDED dynamically, there can be no open | |
| 256 // UI containing an invalidated suggestion unless the status is something | |
| 257 // other than NOT_PROVIDED, so only notify invalidation in that case. | |
| 258 if (category_status_ != CategoryStatus::NOT_PROVIDED && | |
| 259 IsOfflinePageDownload(client_id)) { | |
| 260 InvalidateSuggestion(GetOfflinePagePerCategoryID(offline_id)); | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 void DownloadSuggestionsProvider::OnDownloadCreated( | |
| 265 DownloadManager* manager, | |
| 266 content::DownloadItem* item) { | |
| 267 if (!IsDownloadCompleted(item)) | |
| 268 return; | |
| 269 // TODO(vitaliii): Rewrite to not retrieve all downloads. | |
| 270 FetchAssetsDownloads(); | |
|
vitaliii
2016/09/22 13:43:17
Since this is called when each item is read, the o
vitaliii
2016/10/11 08:15:56
I rewrote this to avoid fetching all items.
| |
| 271 SubmitContentSuggestions(); | |
| 272 } | |
| 273 | |
| 274 void DownloadSuggestionsProvider::OnDownloadUpdated( | |
| 275 DownloadManager* manager, | |
| 276 content::DownloadItem* item) { | |
| 277 if (!IsDownloadCompleted(item)) | |
| 278 return; | |
| 279 // TODO(vitaliii): Rewrite to not retrieve all downloads. | |
| 280 FetchAssetsDownloads(); | |
| 281 SubmitContentSuggestions(); | |
| 282 } | |
| 283 void DownloadSuggestionsProvider::OnDownloadOpened( | |
| 284 DownloadManager* manager, | |
| 285 content::DownloadItem* item) { | |
| 286 // Ignored. | |
| 287 } | |
| 288 void DownloadSuggestionsProvider::OnDownloadRemoved( | |
| 289 DownloadManager* manager, | |
| 290 content::DownloadItem* item) { | |
| 291 if (!IsDownloadCompleted(item)) | |
| 292 return; | |
| 293 // TODO(vitaliii): Implement a better way to clean up dismissed IDs. | |
| 294 InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId())); | |
| 295 } | |
| 296 | |
| 297 void DownloadSuggestionsProvider::NotifyStatusChanged( | |
| 298 CategoryStatus new_status) { | |
| 299 DCHECK_NE(CategoryStatus::NOT_PROVIDED, category_status_); | |
| 300 if (category_status_ == new_status) | |
| 301 return; | |
| 302 category_status_ = new_status; | |
| 303 observer()->OnCategoryStatusChanged(this, provided_category_, new_status); | |
| 304 } | |
| 305 | |
| 306 void DownloadSuggestionsProvider:: | |
| 307 FetchOfflinePagesDownloadsAndSubmitSuggestions() { | |
| 308 // TODO(vitaliii): When something other than |GetAllPages| is used here, the | |
| 309 // dismissed IDs cleanup in |OfflinePageModelChanged| needs to be changed to | |
| 310 // avoid accidentally undismissing suggestions. | |
| 311 offline_page_proxy_->GetAllPages( | |
| 312 base::Bind(&DownloadSuggestionsProvider::OfflinePageModelChanged, | |
| 313 weak_ptr_factory_.GetWeakPtr())); | |
| 314 } | |
| 315 | |
| 316 void DownloadSuggestionsProvider::FetchAssetsDownloads() { | |
| 317 DownloadManager* manager = download_manager_notifier_.GetManager(); | |
| 318 if (!manager) { | |
| 319 // The manager has gone down. | |
| 320 return; | |
| 321 } | |
| 322 | |
| 323 NotifyStatusChanged(CategoryStatus::AVAILABLE); | |
| 324 | |
| 325 std::vector<DownloadItem*> downloads; | |
| 326 manager->GetAllDownloads(&downloads); | |
| 327 | |
| 328 std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs(); | |
| 329 std::vector<DownloadItem*> not_dismissed_downloads; | |
| 330 for (DownloadItem* item : downloads) { | |
| 331 std::string within_category_id = | |
| 332 GetAssetDownloadPerCategoryID(item->GetId()); | |
| 333 if (!dismissed_ids.count(within_category_id)) { | |
| 334 not_dismissed_downloads.push_back(item); | |
| 335 } | |
| 336 | |
| 337 downloads = not_dismissed_downloads; | |
| 338 | |
| 339 if (static_cast<int>(downloads.size()) > kMaxSuggestionsCount) { | |
| 340 std::nth_element(downloads.begin(), | |
| 341 downloads.begin() + kMaxSuggestionsCount, | |
| 342 downloads.end(), | |
| 343 OrderDownloadsMostRecentlyDownloadedCompletedFirst()); | |
| 344 downloads.resize(kMaxSuggestionsCount); | |
| 345 } | |
| 346 | |
| 347 cached_asset_downloads_.clear(); | |
| 348 for (DownloadItem* item : downloads) { | |
| 349 if (IsDownloadCompleted(item)) | |
| 350 cached_asset_downloads_.push_back(item); | |
| 351 } | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 void DownloadSuggestionsProvider::FetchAllDownloadsAndSubmitSuggestions() { | |
| 356 FetchAssetsDownloads(); | |
| 357 FetchOfflinePagesDownloadsAndSubmitSuggestions(); | |
| 358 } | |
| 359 | |
| 360 void DownloadSuggestionsProvider::SubmitContentSuggestions() { | |
| 361 NotifyStatusChanged(CategoryStatus::AVAILABLE); | |
| 362 | |
| 363 std::vector<SuggestionItemWrapper> suggestion_items; | |
| 364 for (int i = 0; i < static_cast<int>(cached_offline_page_downloads_.size()); | |
| 365 ++i) { | |
| 366 SuggestionItemWrapper wrapped_item; | |
| 367 wrapped_item.is_offline_page = true; | |
| 368 wrapped_item.index = i; | |
| 369 wrapped_item.time = cached_offline_page_downloads_[i].last_access_time; | |
| 370 suggestion_items.push_back(wrapped_item); | |
| 371 } | |
| 372 | |
| 373 for (int i = 0; i < static_cast<int>(cached_asset_downloads_.size()); ++i) { | |
| 374 SuggestionItemWrapper wrapped_item; | |
| 375 wrapped_item.is_offline_page = false; | |
| 376 wrapped_item.index = i; | |
| 377 wrapped_item.time = cached_asset_downloads_[i]->GetEndTime(); | |
| 378 suggestion_items.push_back(wrapped_item); | |
| 379 } | |
| 380 | |
| 381 std::sort(suggestion_items.begin(), suggestion_items.end()); | |
| 382 | |
| 383 std::vector<ContentSuggestion> suggestions; | |
| 384 for (const SuggestionItemWrapper& wrapped_item : suggestion_items) { | |
| 385 if (suggestions.size() >= kMaxSuggestionsCount) | |
| 386 break; | |
| 387 | |
| 388 if (wrapped_item.is_offline_page) { | |
| 389 suggestions.push_back(ConvertOfflinePage( | |
| 390 cached_offline_page_downloads_[wrapped_item.index])); | |
| 391 } else { | |
| 392 suggestions.push_back( | |
| 393 ConvertDownloadItem(cached_asset_downloads_[wrapped_item.index])); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 observer()->OnNewSuggestions(this, provided_category_, | |
| 398 std::move(suggestions)); | |
| 399 } | |
| 400 | |
| 401 ContentSuggestion DownloadSuggestionsProvider::ConvertOfflinePage( | |
| 402 const OfflinePageItem& offline_page) const { | |
| 403 // TODO(vitaliii): Make sure the URL is actually opened as an offline URL | |
| 404 // and not just as a downloaded file. | |
| 405 ContentSuggestion suggestion( | |
| 406 MakeUniqueID(provided_category_, | |
| 407 GetOfflinePagePerCategoryID(offline_page.offline_id)), | |
| 408 offline_page.GetOfflineURL()); | |
| 409 | |
| 410 if (offline_page.title.empty()) { | |
| 411 // TODO(vitaliii): Remove this fallback once the OfflinePageModel provides | |
| 412 // titles for all (relevant) OfflinePageItems. | |
| 413 suggestion.set_title(base::UTF8ToUTF16(offline_page.url.spec())); | |
| 414 } else { | |
| 415 suggestion.set_title(offline_page.title); | |
| 416 } | |
| 417 suggestion.set_publish_date(offline_page.creation_time); | |
| 418 suggestion.set_publisher_name(base::UTF8ToUTF16(offline_page.url.host())); | |
| 419 return suggestion; | |
| 420 } | |
| 421 | |
| 422 ContentSuggestion DownloadSuggestionsProvider::ConvertDownloadItem( | |
| 423 const content::DownloadItem* download_item) const { | |
| 424 std::string per_category_id = | |
| 425 MakeUniqueID(provided_category_, | |
| 426 GetAssetDownloadPerCategoryID(download_item->GetId())); | |
| 427 // TODO(vitaliii): Ensure that files are opened in browser, but not | |
| 428 // downloaded | |
| 429 // again. | |
| 430 ContentSuggestion suggestion( | |
| 431 per_category_id, | |
| 432 net::FilePathToFileURL(download_item->GetTargetFilePath())); | |
| 433 // TODO(vitaliii): Set proper title. | |
| 434 suggestion.set_title( | |
| 435 base::UTF8ToUTF16(download_item->GetTargetFilePath().BaseName().value())); | |
| 436 suggestion.set_publish_date(download_item->GetEndTime()); | |
| 437 suggestion.set_publisher_name( | |
| 438 base::UTF8ToUTF16(download_item->GetURL().host())); | |
| 439 // TODO(vitaliii): Set suggestion icon. | |
| 440 return suggestion; | |
| 441 } | |
| 442 | |
| 443 void DownloadSuggestionsProvider::InvalidateSuggestion( | |
| 444 const std::string& per_category_id) { | |
| 445 observer()->OnSuggestionInvalidated( | |
| 446 this, provided_category_, | |
| 447 MakeUniqueID(provided_category_, per_category_id)); | |
| 448 | |
| 449 std::set<std::string> dismissed_ids = | |
| 450 (IsOfflinePageID(per_category_id) ? ReadOfflinePageDismissedIDsFromPrefs() | |
| 451 : ReadAssetDismissedIDsFromPrefs()); | |
| 452 auto it = dismissed_ids.find(per_category_id); | |
| 453 if (it != dismissed_ids.end()) { | |
| 454 dismissed_ids.erase(it); | |
| 455 if (IsOfflinePageID(per_category_id)) | |
| 456 StoreOfflinePageDismissedIDsToPrefs(dismissed_ids); | |
| 457 else | |
| 458 StoreAssetDismissedIDsToPrefs(dismissed_ids); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 std::set<std::string> | |
| 463 DownloadSuggestionsProvider::ReadAssetDismissedIDsFromPrefs() const { | |
| 464 return prefs::ReadDismissedIDsFromPrefs( | |
| 465 *pref_service_, prefs::kDismissedAssetDownloadSuggestions); | |
| 466 } | |
| 467 | |
| 468 void DownloadSuggestionsProvider::StoreAssetDismissedIDsToPrefs( | |
| 469 const std::set<std::string>& dismissed_ids) { | |
| 470 prefs::StoreDismissedIDsToPrefs( | |
| 471 pref_service_, prefs::kDismissedAssetDownloadSuggestions, dismissed_ids); | |
| 472 } | |
| 473 | |
| 474 std::set<std::string> | |
| 475 DownloadSuggestionsProvider::ReadOfflinePageDismissedIDsFromPrefs() const { | |
| 476 return prefs::ReadDismissedIDsFromPrefs( | |
| 477 *pref_service_, prefs::kDismissedOfflinePageDownloadSuggestions); | |
| 478 } | |
| 479 | |
| 480 void DownloadSuggestionsProvider::StoreOfflinePageDismissedIDsToPrefs( | |
| 481 const std::set<std::string>& dismissed_ids) { | |
| 482 prefs::StoreDismissedIDsToPrefs( | |
| 483 pref_service_, prefs::kDismissedOfflinePageDownloadSuggestions, | |
| 484 dismissed_ids); | |
| 485 } | |
| 486 | |
| 487 } // namespace ntp_snippets | |
| OLD | NEW |