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

Unified Diff: chrome/browser/ntp_snippets/downloads/download_suggestions_provider.cc

Issue 2360263002: [NTPSnippets] Show all downloads on the NTP (3/3): Downloads provider. (Closed)
Patch Set: Marc's comments. 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ntp_snippets/downloads/download_suggestions_provider.cc
diff --git a/chrome/browser/ntp_snippets/downloads/download_suggestions_provider.cc b/chrome/browser/ntp_snippets/downloads/download_suggestions_provider.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1a4a94cb2aa7cd44c277a0b6182f5c9a4066bc1f
--- /dev/null
+++ b/chrome/browser/ntp_snippets/downloads/download_suggestions_provider.cc
@@ -0,0 +1,589 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ntp_snippets/downloads/download_suggestions_provider.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/pref_util.h"
+#include "components/offline_pages/client_namespace_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "grit/components_strings.h"
+#include "net/base/filename_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+
+using content::DownloadItem;
+using content::DownloadManager;
+using offline_pages::OfflinePageItem;
+
+namespace ntp_snippets {
+
+namespace {
+
+const int kMaxSuggestionsCount = 5;
Marc Treib 2016/10/11 12:07:40 This should probably be configurable via a variati
vitaliii 2016/10/13 08:43:06 Done.
+const char kAssetDownloadsPrefix[] = "D";
+const char kOfflinePageDownloadsPrefix[] = "O";
Marc Treib 2016/10/11 12:07:40 Just make these two chars (not array)
vitaliii 2016/10/13 08:43:06 Done.
+
+std::string GetOfflinePagePerCategoryID(int64_t raw_offline_page_id) {
+ // Raw ID is prefixed in order to avoid conflicts with asset downloads.
+ return base::JoinString(
+ {kOfflinePageDownloadsPrefix, base::IntToString(raw_offline_page_id)},
+ "");
Marc Treib 2016/10/11 12:07:41 s/""/std::string()/ But if you're just appending
vitaliii 2016/10/13 08:43:05 Done.
+}
+
+std::string GetAssetDownloadPerCategoryID(uint32_t raw_download_id) {
+ // Raw ID is prefixed in order to avoid conflicts with offline page downloads.
+ return base::JoinString(
+ {kAssetDownloadsPrefix, base::UintToString(raw_download_id)}, "");
Marc Treib 2016/10/11 12:07:41 Same here
vitaliii 2016/10/13 08:43:05 Done.
+}
+
+// Determines whether |suggestion_id| corresponds to offline page suggestion or
+// asset download based on |id_within_category| prefix.
+bool IsOfflinePageID(const ContentSuggestion::ID& suggestion_id) {
Marc Treib 2016/10/11 12:07:40 nit: "OfflinePageID" is somewhat overloaded. Maybe
vitaliii 2016/10/13 08:43:05 Done.
+ const std::string& id_within_category = suggestion_id.id_within_category();
+ if (!id_within_category.empty()) {
+ if (id_within_category[0] == kOfflinePageDownloadsPrefix[0])
+ return true;
+ if (id_within_category[0] == kAssetDownloadsPrefix[0])
+ return false;
+ }
+ NOTREACHED() << "Unknown id_within_category " << id_within_category;
+ return false;
+}
+
+struct OrderOfflinePagesByMostRecentlyVisitedFirst {
+ bool operator()(const OfflinePageItem* left,
+ const OfflinePageItem* right) const {
+ return left->last_access_time > right->last_access_time;
+ }
+};
+
+bool IsOfflinePageDownload(const offline_pages::ClientId& client_id) {
+ return (client_id.name_space == offline_pages::kAsyncNamespace ||
+ client_id.name_space == offline_pages::kDownloadNamespace ||
+ client_id.name_space == offline_pages::kNTPSuggestionsNamespace) &&
Marc Treib 2016/10/11 12:07:41 What's kNTPSuggestionsNamespace?
vitaliii 2016/10/13 08:43:05 This is for offlined NTP items, e.g. right click o
+ base::IsValidGUID(client_id.id);
Marc Treib 2016/10/11 12:07:41 Is this needed? Would we ever expect to get an inv
vitaliii 2016/10/13 08:43:06 It was done like that previously (and the logic wa
+}
+
+bool IsDownloadCompleted(const DownloadItem* item) {
Marc Treib 2016/10/11 12:07:40 nit: "const DownloadItem&" if it can't be null
vitaliii 2016/10/13 08:43:06 Wow, I did not know that this is possible with poi
+ return item->GetState() == DownloadItem::DownloadState::COMPLETE &&
+ !item->GetFileExternallyRemoved();
+}
+
+struct OrderDownloadsMostRecentlyDownloadedCompletedFirst {
+ bool operator()(const DownloadItem* left, const DownloadItem* right) const {
+ if (IsDownloadCompleted(left) != IsDownloadCompleted(right))
+ return IsDownloadCompleted(left);
+ return left->GetEndTime() > right->GetEndTime();
+ }
+};
+
+struct SuggestionItemWrapper {
+ base::Time time;
+ bool is_offline_page;
+ int index;
+ bool operator<(const SuggestionItemWrapper& other) const {
+ return time > other.time;
+ }
+};
+
+} // namespace
+
+DownloadSuggestionsProvider::DownloadSuggestionsProvider(
+ ContentSuggestionsProvider::Observer* observer,
+ CategoryFactory* category_factory,
+ const scoped_refptr<OfflinePageProxy>& offline_page_proxy,
+ content::DownloadManager* download_manager,
+ PrefService* pref_service,
+ bool download_manager_ui_enabled)
+ : ContentSuggestionsProvider(observer, category_factory),
+ category_status_(CategoryStatus::AVAILABLE_LOADING),
+ provided_category_(
+ category_factory->FromKnownCategory(KnownCategories::DOWNLOADS)),
+ offline_page_proxy_(offline_page_proxy),
+ download_manager_notifier_(download_manager, this),
+ pref_service_(pref_service),
+ download_manager_ui_enabled_(download_manager_ui_enabled),
+ weak_ptr_factory_(this) {
+ observer->OnCategoryStatusChanged(this, provided_category_, category_status_);
+ offline_page_proxy_->AddObserver(this);
+ FetchAllDownloadsAndSubmitSuggestions();
+}
+
+DownloadSuggestionsProvider::~DownloadSuggestionsProvider() {
+ offline_page_proxy_->RemoveObserver(this);
+}
+
+CategoryStatus DownloadSuggestionsProvider::GetCategoryStatus(
+ Category category) {
+ if (category == provided_category_)
+ return category_status_;
+ NOTREACHED() << "Unknown category " << category.id();
+ return CategoryStatus::NOT_PROVIDED;
Marc Treib 2016/10/11 12:07:40 Just do DCHECK_EQ(provided_category_, category); r
vitaliii 2016/10/13 08:43:05 Done.
+}
+
+CategoryInfo DownloadSuggestionsProvider::GetCategoryInfo(Category category) {
+ if (category == provided_category_) {
+ return CategoryInfo(
+ l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER),
+ ContentSuggestionsCardLayout::MINIMAL_CARD,
+ /*has_more_button=*/download_manager_ui_enabled_,
+ /*show_if_empty=*/false);
+ }
+ NOTREACHED() << "Unknown category " << category.id();
Marc Treib 2016/10/11 12:07:40 Same here
vitaliii 2016/10/13 08:43:06 Done.
+ return CategoryInfo(base::string16(),
+ ContentSuggestionsCardLayout::MINIMAL_CARD,
+ /*has_more_button=*/false,
+ /*show_if_empty=*/false);
+}
+
+void DownloadSuggestionsProvider::DismissSuggestion(
+ const ContentSuggestion::ID& suggestion_id) {
+ DCHECK_EQ(provided_category_, suggestion_id.category());
+ std::set<std::string> dismissed_ids =
+ ReadDismissedIDsFromPrefs(suggestion_id);
+ dismissed_ids.insert(suggestion_id.id_within_category());
+ StoreDismissedIDsToPrefs(suggestion_id, dismissed_ids);
Marc Treib 2016/10/11 12:07:41 nit: I find it a bit confusing to pass in the sugg
vitaliii 2016/10/13 08:43:05 I did the former. The latter is less attractive,
+
+ RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
+}
+
+void DownloadSuggestionsProvider::FetchSuggestionImage(
+ const ContentSuggestion::ID& suggestion_id,
+ const ImageFetchedCallback& callback) {
+ // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it is
+ // available there.
+ // TODO(vitaliii): Provide site's favicon for assets downloads.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, gfx::Image()));
+}
+
+void DownloadSuggestionsProvider::ClearHistory(
+ base::Time begin,
+ base::Time end,
+ const base::Callback<bool(const GURL& url)>& filter) {
+ ClearDismissedSuggestionsForDebugging(provided_category_);
Marc Treib 2016/10/11 12:07:40 This will call FetchAllDownloadsAndSubmitSuggestio
vitaliii 2016/10/13 08:43:05 Good point! Actually current cache is completely i
Marc Treib 2016/10/13 12:11:25 Hm, but that is async, right? So maybe it's best t
vitaliii 2016/10/15 18:36:30 Done.
+ cached_offline_page_downloads_.clear();
+ cached_asset_downloads_.clear();
+ FetchAllDownloadsAndSubmitSuggestions();
+}
+
+void DownloadSuggestionsProvider::ClearCachedSuggestions(Category category) {
+ DCHECK_EQ(provided_category_, category);
+ cached_offline_page_downloads_.clear();
+ cached_asset_downloads_.clear();
+ FetchAllDownloadsAndSubmitSuggestions();
Marc Treib 2016/10/11 12:07:41 Would we expect this to change anything? The cache
vitaliii 2016/10/13 08:43:05 Good point.
+}
+
+void DownloadSuggestionsProvider::GetDismissedSuggestionsForDebugging(
+ Category category,
+ const DismissedSuggestionsCallback& callback) {
+ DCHECK_EQ(provided_category_, category);
+ // TODO(vitaliii): Implement.
+ callback.Run(std::vector<ContentSuggestion>());
+}
+
+void DownloadSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
+ Category category) {
+ DCHECK_EQ(provided_category_, category);
+ StoreAssetDismissedIDsToPrefs(std::set<std::string>());
+ StoreOfflinePageDismissedIDsToPrefs(std::set<std::string>());
+ FetchAllDownloadsAndSubmitSuggestions();
+}
+
+// static
+void DownloadSuggestionsProvider::RegisterProfilePrefs(
+ PrefRegistrySimple* registry) {
+ registry->RegisterListPref(prefs::kDismissedAssetDownloadSuggestions);
+ registry->RegisterListPref(prefs::kDismissedOfflinePageDownloadSuggestions);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private methods
+
+void DownloadSuggestionsProvider::OfflinePageModelChanged(
+ const std::vector<offline_pages::OfflinePageItem>& offline_pages) {
+ NotifyStatusChanged(CategoryStatus::AVAILABLE);
Marc Treib 2016/10/11 12:07:41 SubmitContentSuggestions will change the status, s
vitaliii 2016/10/13 08:43:07 Yes, but it will happen just before the new sugges
Marc Treib 2016/10/13 12:11:25 But all this happens synchronously, so the UI does
vitaliii 2016/10/15 18:36:30 Done.
+ ProcessAllOfflinePages(offline_pages);
+ SubmitContentSuggestions();
+}
+
+void DownloadSuggestionsProvider::OfflinePageDeleted(
+ int64_t offline_id,
+ const offline_pages::ClientId& client_id) {
+ // Because we never switch to NOT_PROVIDED dynamically, there can be no open
+ // UI containing an invalidated suggestion unless the status is something
+ // other than NOT_PROVIDED, so only notify invalidation in that case.
Marc Treib 2016/10/11 12:07:41 Actually, can the status *ever* be NOT_PROVIDED no
vitaliii 2016/10/13 08:43:05 Looks like not, but I am not sure. However, I do
+ if (category_status_ != CategoryStatus::NOT_PROVIDED &&
+ IsOfflinePageDownload(client_id)) {
+ InvalidateSuggestion(GetOfflinePagePerCategoryID(offline_id));
+ }
+}
+
+void DownloadSuggestionsProvider::OnDownloadCreated(DownloadManager* manager,
+ DownloadItem* item) {
+ // This is called when new downloads are started and on startup for existing
+ // ones.
+ if (CacheAssetDownloadIfNeeded(item))
+ SubmitContentSuggestions();
+}
+
+void DownloadSuggestionsProvider::OnDownloadUpdated(DownloadManager* manager,
+ DownloadItem* item) {
+ // Unfinished downloads may become completed, therefore, this call cannot be
+ // ignored.
+ if (CacheAssetDownloadIfNeeded(item))
+ SubmitContentSuggestions();
+}
+void DownloadSuggestionsProvider::OnDownloadOpened(DownloadManager* manager,
Marc Treib 2016/10/11 12:07:40 nit: empty line before
vitaliii 2016/10/13 08:43:06 Done.
+ DownloadItem* item) {
+ // Ignored.
+}
+void DownloadSuggestionsProvider::OnDownloadRemoved(DownloadManager* manager,
Marc Treib 2016/10/11 12:07:40 Also here
vitaliii 2016/10/13 08:43:06 Done.
+ DownloadItem* item) {
+ if (!IsDownloadCompleted(item))
+ return;
+ // TODO(vitaliii): Implement a better way to clean up dismissed IDs (in case
+ // some calls are missed).
+ InvalidateSuggestion(GetAssetDownloadPerCategoryID(item->GetId()));
+}
+
+void DownloadSuggestionsProvider::NotifyStatusChanged(
+ CategoryStatus new_status) {
+ DCHECK_NE(CategoryStatus::NOT_PROVIDED, category_status_);
Marc Treib 2016/10/11 12:07:40 Should this check new_status instead of category_s
vitaliii 2016/10/13 08:43:07 Done.
+ if (category_status_ == new_status)
+ return;
+ category_status_ = new_status;
+ observer()->OnCategoryStatusChanged(this, provided_category_, new_status);
Marc Treib 2016/10/11 12:07:40 nit: s/new_status/category_status_/
vitaliii 2016/10/13 08:43:06 Done.
+}
+
+void DownloadSuggestionsProvider::FetchOfflinePagesDownloads() {
+ offline_page_proxy_->GetAllPages(
+ base::Bind(&DownloadSuggestionsProvider::ProcessAllOfflinePages,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DownloadSuggestionsProvider::
+ FetchOfflinePagesDownloadsAndSubmitSuggestions() {
+ offline_page_proxy_->GetAllPages(
+ base::Bind(&DownloadSuggestionsProvider::OfflinePageModelChanged,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DownloadSuggestionsProvider::FetchAssetsDownloads() {
+ DownloadManager* manager = download_manager_notifier_.GetManager();
+ if (!manager) {
+ // The manager has gone down.
+ return;
+ }
+
+ NotifyStatusChanged(CategoryStatus::AVAILABLE);
Marc Treib 2016/10/11 12:07:41 Why is this here?
vitaliii 2016/10/13 08:43:07 Done.
+
+ std::vector<DownloadItem*> downloads;
+ manager->GetAllDownloads(&downloads);
+
+ std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs();
+ std::set<std::string> new_dismissed_ids;
Marc Treib 2016/10/11 12:07:40 Maybe old_dismissed_ids and retained_dismissed_ids
vitaliii 2016/10/13 08:43:06 Done.
+ std::vector<DownloadItem*> not_dismissed_downloads;
+ for (DownloadItem* item : downloads) {
Marc Treib 2016/10/11 12:07:41 const?
vitaliii 2016/10/13 08:43:05 Done.
+ std::string within_category_id =
+ GetAssetDownloadPerCategoryID(item->GetId());
+ if (!dismissed_ids.count(within_category_id)) {
+ not_dismissed_downloads.push_back(item);
+ } else {
+ new_dismissed_ids.insert(within_category_id);
+ }
+ }
+
+ if (dismissed_ids.size() != new_dismissed_ids.size())
+ StoreAssetDismissedIDsToPrefs(new_dismissed_ids);
+
+ downloads = not_dismissed_downloads;
Marc Treib 2016/10/11 12:07:40 Maybe rename "downloads" to "all_downloads", "not_
vitaliii 2016/10/13 08:43:06 Done.
+ if (static_cast<int>(downloads.size()) > kMaxSuggestionsCount) {
+ std::nth_element(downloads.begin(),
Marc Treib 2016/10/11 12:07:41 Nice! But since nth_element isn't very common, and
vitaliii 2016/10/13 08:43:06 Done.
+ downloads.begin() + kMaxSuggestionsCount, downloads.end(),
+ OrderDownloadsMostRecentlyDownloadedCompletedFirst());
+ downloads.resize(kMaxSuggestionsCount);
+ }
+
+ cached_asset_downloads_.clear();
+ for (DownloadItem* item : downloads) {
+ if (IsDownloadCompleted(item))
Marc Treib 2016/10/11 12:07:41 Wouldn't it be easier if the filtering happened be
vitaliii 2016/10/13 08:43:06 Done.
+ cached_asset_downloads_.push_back(item);
+ }
+}
+
+void DownloadSuggestionsProvider::FetchAllDownloadsAndSubmitSuggestions() {
+ FetchAssetsDownloads();
+ FetchOfflinePagesDownloadsAndSubmitSuggestions();
Marc Treib 2016/10/11 12:07:41 I was gonna say that this isn't safe since the off
vitaliii 2016/10/13 08:43:05 Done.
+}
+
+void DownloadSuggestionsProvider::SubmitContentSuggestions() {
+ NotifyStatusChanged(CategoryStatus::AVAILABLE);
+
+ std::vector<SuggestionItemWrapper> suggestion_items;
+ for (int i = 0; i < static_cast<int>(cached_offline_page_downloads_.size());
+ ++i) {
+ SuggestionItemWrapper wrapped_item;
+ wrapped_item.is_offline_page = true;
+ wrapped_item.index = i;
Marc Treib 2016/10/11 12:07:41 Mh, this is a bit yucky. Maybe instead of bool+ind
vitaliii 2016/10/13 08:43:05 Acknowledged.
Marc Treib 2016/10/13 12:11:25 ...this reply confused me ;P "Obsolete, Suggestion
vitaliii 2016/10/15 18:36:29 I've got it. Sorry about that.
+ wrapped_item.time = cached_offline_page_downloads_[i].last_access_time;
+ suggestion_items.push_back(wrapped_item);
+ }
+
+ for (int i = 0; i < static_cast<int>(cached_asset_downloads_.size()); ++i) {
+ SuggestionItemWrapper wrapped_item;
+ wrapped_item.is_offline_page = false;
+ wrapped_item.index = i;
+ wrapped_item.time = cached_asset_downloads_[i]->GetEndTime();
+ suggestion_items.push_back(wrapped_item);
+ }
+
+ std::sort(suggestion_items.begin(), suggestion_items.end());
Marc Treib 2016/10/11 12:07:40 Or in fact: Can the sorting just happen after conv
vitaliii 2016/10/13 08:43:06 Done.
+
+ std::vector<ContentSuggestion> suggestions;
+ for (const SuggestionItemWrapper& wrapped_item : suggestion_items) {
+ if (suggestions.size() >= kMaxSuggestionsCount)
+ break;
+
+ if (wrapped_item.is_offline_page) {
+ suggestions.push_back(ConvertOfflinePage(
+ cached_offline_page_downloads_[wrapped_item.index]));
+ } else {
+ suggestions.push_back(
+ ConvertDownloadItem(cached_asset_downloads_[wrapped_item.index]));
+ }
+ }
+
+ observer()->OnNewSuggestions(this, provided_category_,
+ std::move(suggestions));
+}
+
+ContentSuggestion DownloadSuggestionsProvider::ConvertOfflinePage(
+ const OfflinePageItem& offline_page) const {
+ // TODO(vitaliii): Make sure the URL is actually opened as an offline URL even
+ // when the user is online.
Marc Treib 2016/10/11 12:07:40 Do we have a bug for this? If not, could you file
vitaliii 2016/10/13 08:43:06 Done. Why do not you like https://cs.chromium.org
Marc Treib 2016/10/13 12:11:25 TODOs in the code aren't a good issue tracking sys
vitaliii 2016/10/15 18:36:29 I see, good point.
+ ContentSuggestion suggestion(
+ ContentSuggestion::ID(provided_category_, GetOfflinePagePerCategoryID(
+ offline_page.offline_id)),
+ offline_page.url);
+
+ if (offline_page.title.empty()) {
+ // TODO(vitaliii): Remove this fallback once the OfflinePageModel provides
+ // titles for all (relevant) OfflinePageItems.
+ suggestion.set_title(base::UTF8ToUTF16(offline_page.url.spec()));
+ } else {
+ suggestion.set_title(offline_page.title);
+ }
+ suggestion.set_publish_date(offline_page.creation_time);
+ suggestion.set_publisher_name(base::UTF8ToUTF16(offline_page.url.host()));
+ return suggestion;
+}
+
+ContentSuggestion DownloadSuggestionsProvider::ConvertDownloadItem(
+ const DownloadItem* download_item) const {
+ ContentSuggestion::ID per_category_id(
Marc Treib 2016/10/11 12:07:40 This is not a per-category-ID. But I'd just inline
vitaliii 2016/10/13 08:43:05 Done.
+ provided_category_,
+ GetAssetDownloadPerCategoryID(download_item->GetId()));
+ // TODO(vitaliii): Ensure that files are opened in browser, but not downloaded
+ // again.
+ ContentSuggestion suggestion(
+ per_category_id,
+ net::FilePathToFileURL(download_item->GetTargetFilePath()));
+ // TODO(vitaliii): Set proper title.
+ suggestion.set_title(
+ base::UTF8ToUTF16(download_item->GetTargetFilePath().BaseName().value()));
Marc Treib 2016/10/11 12:07:40 I think this won't work on all platforms, since pa
vitaliii 2016/10/13 08:43:06 This is Anroid only for now, added DCHECK for futu
+ suggestion.set_publish_date(download_item->GetEndTime());
+ suggestion.set_publisher_name(
+ base::UTF8ToUTF16(download_item->GetURL().host()));
+ // TODO(vitaliii): Set suggestion icon.
+ return suggestion;
+}
+
+bool DownloadSuggestionsProvider::CacheAssetDownloadIfNeeded(
+ const content::DownloadItem* item) {
+ if (!IsDownloadCompleted(item))
+ return false;
+
+ std::vector<const content::DownloadItem*>& cache = cached_asset_downloads_;
Marc Treib 2016/10/11 12:07:41 Please avoid non-const references in general. Here
vitaliii 2016/10/13 08:43:06 Done.
+ if (std::find(cache.begin(), cache.end(), item) != cache.end())
Marc Treib 2016/10/11 12:07:41 optional: base::ContainsValue might be a bit easie
vitaliii 2016/10/13 08:43:07 Done.
+ return false;
+
+ std::set<std::string> dismissed_ids = ReadAssetDismissedIDsFromPrefs();
+ if (dismissed_ids.count(GetAssetDownloadPerCategoryID(item->GetId())))
+ return false;
+
+ DCHECK(cache.size() <= kMaxSuggestionsCount);
Marc Treib 2016/10/11 12:07:40 DCHECK_LE?
vitaliii 2016/10/13 08:43:06 Done.
+ if (cache.size() == kMaxSuggestionsCount) {
+ std::vector<const content::DownloadItem*>::iterator oldest =
Marc Treib 2016/10/11 12:07:40 optional: auto oldest_it = ...?
vitaliii 2016/10/13 08:43:05 Done.
+ std::max_element(cache.begin(), cache.end(),
+ OrderDownloadsMostRecentlyDownloadedCompletedFirst());
+ if (item->GetEndTime() <= (*oldest)->GetEndTime())
vitaliii 2016/10/11 08:15:56 Would using an index here be better?
Marc Treib 2016/10/11 12:07:41 I don't follow - an index for what?
vitaliii 2016/10/13 08:43:06 i.e. int i = oldest - cached_asset_downloads_.beg
Marc Treib 2016/10/13 12:11:25 Ah - no, what you have is fine.
vitaliii 2016/10/15 18:36:29 Acknowledged.
+ return false;
+
+ (*oldest) = item;
Marc Treib 2016/10/11 12:07:41 nit: parens not required
vitaliii 2016/10/13 08:43:05 Done.
+ } else {
+ cache.push_back(item);
+ }
+
+ return true;
+}
+
+bool DownloadSuggestionsProvider::RemoveSuggestionFromCacheIfPresent(
+ const ContentSuggestion::ID& suggestion_id) {
+ DCHECK_EQ(provided_category_, suggestion_id.category());
+ if (IsOfflinePageID(suggestion_id)) {
+ std::vector<offline_pages::OfflinePageItem>& cache =
Marc Treib 2016/10/11 12:07:41 As above: Please spell out cached_offline_page_dow
vitaliii 2016/10/13 08:43:06 Done.
+ cached_offline_page_downloads_;
+ for (int i = 0; i < static_cast<int>(cache.size()); ++i) {
Marc Treib 2016/10/11 12:07:40 optional: This might be slightly nicer as an std::
vitaliii 2016/10/13 08:43:06 Done.
+ if (GetOfflinePagePerCategoryID(cache[i].offline_id) ==
+ suggestion_id.id_within_category()) {
+ cache.erase(cache.begin() + i);
+ return true;
+ }
+ }
+ return false;
+ } else {
Marc Treib 2016/10/11 12:07:40 No "else" after "return"
vitaliii 2016/10/13 08:43:06 Done.
+ std::vector<const content::DownloadItem*>& cache = cached_asset_downloads_;
Marc Treib 2016/10/11 12:07:40 Also here: Please spell out
vitaliii 2016/10/13 08:43:05 Done.
+ for (int i = 0; i < static_cast<int>(cache.size()); ++i) {
+ if (GetAssetDownloadPerCategoryID(cache[i]->GetId()) ==
+ suggestion_id.id_within_category()) {
+ cache.erase(cache.begin() + i);
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+void DownloadSuggestionsProvider::
+ RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(
+ const ContentSuggestion::ID& suggestion_id) {
+ DCHECK_EQ(provided_category_, suggestion_id.category());
+ if (RemoveSuggestionFromCacheIfPresent(suggestion_id)) {
Marc Treib 2016/10/11 12:07:41 if (!Remove...) return; and save a level of inde
vitaliii 2016/10/13 08:43:07 Done.
+ if (IsOfflinePageID(suggestion_id)) {
+ if (cached_offline_page_downloads_.size() == kMaxSuggestionsCount - 1) {
+ // Previously there were |kMaxSuggestionsCount| cached suggestion,
+ // therefore, overall there may be more than |kMaxSuggestionsCount|
+ // suggestions in the model and now one of them may be cached instead of
+ // the removed one. Even though, the suggestions are not immediately
+ // used the cache has to be kept up to date, because it may be used when
+ // other data source is updated.
+ FetchOfflinePagesDownloads();
+ }
+ } else {
+ if (cached_asset_downloads_.size() == kMaxSuggestionsCount - 1) {
+ // The same as the case above.
+ FetchAssetsDownloads();
+ }
+ }
+ }
+}
+
+void DownloadSuggestionsProvider::ProcessAllOfflinePages(
+ const std::vector<offline_pages::OfflinePageItem>& all_offline_pages) {
+ std::set<std::string> old_dismissed_ids =
+ ReadOfflinePageDismissedIDsFromPrefs();
+ std::set<std::string> new_dismissed_ids;
+ std::vector<const OfflinePageItem*> items;
+ // Filtering out dismissed items and pruning dismissed IDs.
+ for (const OfflinePageItem& item : all_offline_pages) {
+ if (!IsOfflinePageDownload(item.client_id))
+ continue;
+
+ std::string per_category_id = GetOfflinePagePerCategoryID(item.offline_id);
+ if (!old_dismissed_ids.count(per_category_id))
Marc Treib 2016/10/11 12:07:41 I think the dismissed IDs sometimes have the "O"/"
vitaliii 2016/10/13 08:43:05 For the former - I do not think so, I added DCHECK
Marc Treib 2016/10/13 12:11:25 I looked over all the code, and it looks like you
vitaliii 2016/10/15 18:36:29 Let's stick with this approach for now and then I
+ items.push_back(&item);
+ else
+ new_dismissed_ids.insert(per_category_id);
+ }
+
+ if (static_cast<int>(items.size()) > kMaxSuggestionsCount) {
+ std::nth_element(items.begin(), items.begin() + kMaxSuggestionsCount,
+ items.end(),
+ OrderOfflinePagesByMostRecentlyVisitedFirst());
Marc Treib 2016/10/11 12:07:41 You could use a lambda here
vitaliii 2016/10/13 08:43:04 Done.
+ items.resize(kMaxSuggestionsCount);
+ }
+
+ cached_offline_page_downloads_.clear();
+ for (const OfflinePageItem* item : items) {
+ cached_offline_page_downloads_.push_back(*item);
+ }
+
+ if (old_dismissed_ids.size() != new_dismissed_ids.size()) {
+ StoreOfflinePageDismissedIDsToPrefs(new_dismissed_ids);
+ }
+}
+
+void DownloadSuggestionsProvider::InvalidateSuggestion(
+ const std::string& per_category_id) {
+ ContentSuggestion::ID suggestion_id(provided_category_, per_category_id);
+ observer()->OnSuggestionInvalidated(this, suggestion_id);
+
+ std::set<std::string> dismissed_ids =
+ ReadDismissedIDsFromPrefs(suggestion_id);
+ auto it = dismissed_ids.find(per_category_id);
+ if (it != dismissed_ids.end()) {
+ dismissed_ids.erase(it);
+ StoreDismissedIDsToPrefs(suggestion_id, dismissed_ids);
+ }
+
+ RemoveSuggestionFromCacheAndRetrieveMoreIfNeeded(suggestion_id);
+}
+
+std::set<std::string>
+DownloadSuggestionsProvider::ReadAssetDismissedIDsFromPrefs() const {
+ return prefs::ReadDismissedIDsFromPrefs(
+ *pref_service_, prefs::kDismissedAssetDownloadSuggestions);
+}
+
+void DownloadSuggestionsProvider::StoreAssetDismissedIDsToPrefs(
+ const std::set<std::string>& dismissed_ids) {
+ prefs::StoreDismissedIDsToPrefs(
+ pref_service_, prefs::kDismissedAssetDownloadSuggestions, dismissed_ids);
+}
+
+std::set<std::string>
+DownloadSuggestionsProvider::ReadOfflinePageDismissedIDsFromPrefs() const {
+ return prefs::ReadDismissedIDsFromPrefs(
+ *pref_service_, prefs::kDismissedOfflinePageDownloadSuggestions);
+}
+
+void DownloadSuggestionsProvider::StoreOfflinePageDismissedIDsToPrefs(
+ const std::set<std::string>& dismissed_ids) {
+ prefs::StoreDismissedIDsToPrefs(
+ pref_service_, prefs::kDismissedOfflinePageDownloadSuggestions,
+ dismissed_ids);
+}
+
+std::set<std::string> DownloadSuggestionsProvider::ReadDismissedIDsFromPrefs(
+ const ContentSuggestion::ID& suggestion_id) const {
+ if (IsOfflinePageID(suggestion_id)) {
+ return ReadOfflinePageDismissedIDsFromPrefs();
+ } else {
Marc Treib 2016/10/11 12:07:40 nit: No "else" after "return" Or maybe just get r
vitaliii 2016/10/13 08:43:06 Done.
+ return ReadAssetDismissedIDsFromPrefs();
+ }
+}
+
+void DownloadSuggestionsProvider::StoreDismissedIDsToPrefs(
+ const ContentSuggestion::ID& suggestion_id,
+ const std::set<std::string>& dismissed_ids) {
+ if (IsOfflinePageID(suggestion_id)) {
+ StoreOfflinePageDismissedIDsToPrefs(dismissed_ids);
+ } else {
Marc Treib 2016/10/11 12:07:39 Also here
vitaliii 2016/10/13 08:43:05 Done.
+ StoreAssetDismissedIDsToPrefs(dismissed_ids);
+ }
+}
+
+} // namespace ntp_snippets

Powered by Google App Engine
This is Rietveld 408576698