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

Side by Side Diff: components/ntp_snippets/remote/remote_suggestions_provider.h

Issue 2557363002: [NTP Snippets] Refactor background scheduling for remote suggestions (Closed)
Patch Set: Tim's comments + fix ios compilation Created 4 years 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
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_ 5 #ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_
6 #define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_ 6 #define COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_
7 7
8 #include <cstddef>
9 #include <deque>
10 #include <map>
11 #include <memory> 8 #include <memory>
12 #include <set>
13 #include <string>
14 #include <utility>
15 #include <vector>
16 9
17 #include "base/callback_forward.h" 10 #include "base/callback_forward.h"
18 #include "base/gtest_prod_util.h"
19 #include "base/macros.h" 11 #include "base/macros.h"
20 #include "base/time/clock.h"
21 #include "base/time/time.h"
22 #include "components/image_fetcher/image_fetcher_delegate.h"
23 #include "components/ntp_snippets/category.h"
24 #include "components/ntp_snippets/category_status.h"
25 #include "components/ntp_snippets/content_suggestion.h"
26 #include "components/ntp_snippets/content_suggestions_provider.h" 12 #include "components/ntp_snippets/content_suggestions_provider.h"
27 #include "components/ntp_snippets/remote/ntp_snippet.h"
28 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h"
29 #include "components/ntp_snippets/remote/ntp_snippets_request_params.h"
30 #include "components/ntp_snippets/remote/ntp_snippets_scheduler.h"
31 #include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
32 #include "components/ntp_snippets/remote/request_throttler.h"
33
34 class PrefRegistrySimple;
35 class PrefService;
36
37 namespace gfx {
38 class Image;
39 } // namespace gfx
40
41 namespace image_fetcher {
42 class ImageDecoder;
43 class ImageFetcher;
44 } // namespace image_fetcher
45 13
46 namespace ntp_snippets { 14 namespace ntp_snippets {
47 15
48 class RemoteSuggestionsDatabase; 16 class NTPSnippetsFetcher;
49 class CategoryRanker;
50 class UserClassifier;
51
52 // CachedImageFetcher takes care of fetching images from the network and caching
53 // them in the database.
54 // TODO(tschumann): Move into a separate library and inject the
55 // CachedImageFetcher into the RemoteSuggestionsProvider. This allows us to get
56 // rid of exposing this member for testing and lets us test the caching logic
57 // separately.
58 class CachedImageFetcher : public image_fetcher::ImageFetcherDelegate {
59 public:
60 // |pref_service| and |database| need to outlive the created image fetcher
61 // instance.
62 CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
63 std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
64 PrefService* pref_service,
65 RemoteSuggestionsDatabase* database);
66 ~CachedImageFetcher() override;
67
68 // Fetches the image for a suggestion. The fetcher will first issue a lookup
69 // to the underlying cache with a fallback to the network.
70 void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
71 const GURL& image_url,
72 const ImageFetchedCallback& callback);
73
74 private:
75 // image_fetcher::ImageFetcherDelegate implementation.
76 void OnImageDataFetched(const std::string& id_within_category,
77 const std::string& image_data) override;
78 void OnImageDecodingDone(const ImageFetchedCallback& callback,
79 const std::string& id_within_category,
80 const gfx::Image& image);
81 void OnSnippetImageFetchedFromDatabase(
82 const ImageFetchedCallback& callback,
83 const ContentSuggestion::ID& suggestion_id,
84 const GURL& image_url,
85 // SnippetImageCallback requires nonconst reference
86 std::string data);
87 void OnSnippetImageDecodedFromDatabase(
88 const ImageFetchedCallback& callback,
89 const ContentSuggestion::ID& suggestion_id,
90 const GURL& url,
91 const gfx::Image& image);
92 void FetchSnippetImageFromNetwork(const ContentSuggestion::ID& suggestion_id,
93 const GURL& url,
94 const ImageFetchedCallback& callback);
95
96 std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
97 std::unique_ptr<image_fetcher::ImageDecoder> image_decoder_;
98 RemoteSuggestionsDatabase* database_;
99 // Request throttler for limiting requests to thumbnail images.
100 RequestThrottler thumbnail_requests_throttler_;
101
102 DISALLOW_COPY_AND_ASSIGN(CachedImageFetcher);
103 };
104 17
105 // Retrieves fresh content data (articles) from the server, stores them and 18 // Retrieves fresh content data (articles) from the server, stores them and
106 // provides them as content suggestions. 19 // provides them as content suggestions.
107 // This class is final because it does things in its constructor which make it 20 class RemoteSuggestionsProvider : public ContentSuggestionsProvider {
108 // unsafe to derive from it.
109 // TODO(treib): Introduce two-phase initialization and make the class not final?
110 class RemoteSuggestionsProvider final : public ContentSuggestionsProvider {
111 public: 21 public:
112 // |application_language_code| should be a ISO 639-1 compliant string, e.g. 22 // TODO(jkrcal): Would be nice to get rid of this another level of statuses.
113 // 'en' or 'en-US'. Note that this code should only specify the language, not 23 // Maybe possible while refactoring the RemoteSuggestionsStatusService? (and
114 // the locale, so 'en_US' (English language with US locale) and 'en-GB_US' 24 // letting it notify both the SchedulingRemoteSuggestionsProvider and
115 // (British English person in the US) are not language codes. 25 // RemoteSuggestionsProviderImpl or just the scheduling one).
116 RemoteSuggestionsProvider( 26 enum ProviderStatus { ACTIVE, INACTIVE };
Bernhard Bauer 2016/12/21 10:38:42 Nit: Can you make this an enum class?
jkrcal 2016/12/21 11:54:40 Done. Why? Is it a general rule in chromium / Zin
Bernhard Bauer 2016/12/21 12:24:36 No, but there are some advantages, like the additi
jkrcal 2016/12/21 12:41:20 Thanks for the explanation!
117 Observer* observer, 27 using ProviderStatusCallback =
118 PrefService* pref_service, 28 base::RepeatingCallback<void(ProviderStatus status)>;
119 const std::string& application_language_code, 29
120 CategoryRanker* category_ranker, 30 // Callback to notify with the result of a fetch.
121 const UserClassifier* user_classifier, 31 // TODO(jkrcal): Change to OnceCallback? A OnceCallback does only have a
122 NTPSnippetsScheduler* scheduler, 32 // move-constructor which seems problematic for google mock.
123 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher, 33 using FetchStatusCallback = base::Callback<void(Status status_code)>;
124 std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
125 std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
126 std::unique_ptr<RemoteSuggestionsDatabase> database,
127 std::unique_ptr<RemoteSuggestionsStatusService> status_service);
128 34
129 ~RemoteSuggestionsProvider() override; 35 ~RemoteSuggestionsProvider() override;
130 36
131 static void RegisterProfilePrefs(PrefRegistrySimple* registry); 37 // Set a callback to be notified whenever the status of the provider changes.
38 // The initial change is also notified (switching from an initial undecided
39 // status). If the callback is set after the first change, it is called back
40 // immediately.
41 virtual void SetProviderStatusCallback(
42 std::unique_ptr<ProviderStatusCallback> callback) = 0;
132 43
133 // Returns whether the service is ready. While this is false, the list of 44 // Fetches snippets from the server for all remote categories and replaces old
134 // snippets will be empty, and all modifications to it (fetch, dismiss, etc) 45 // snippets by the new ones. The request to the server is performed as an
135 // will be ignored. 46 // background request. Background requests are used for actions not triggered
136 bool ready() const { return state_ == State::READY; } 47 // by the user and have lower priority on the server. After the fetch
48 // finished, the provided |callback| will be triggered with the status of the
49 // fetch (unless not nullptr).
50 virtual void RefetchInTheBackground(
51 std::unique_ptr<FetchStatusCallback> callback) = 0;
137 52
138 // Returns whether the service is initialized. While this is false, some 53 virtual const NTPSnippetsFetcher* snippets_fetcher_for_testing_and_debugging()
139 // calls may trigger DCHECKs. 54 const = 0;
140 bool initialized() const { return ready() || state_ == State::DISABLED; }
141 55
142 // Fetchs content suggestions from the Content Suggestions server for all 56 protected:
143 // remote categories in the background. 57 RemoteSuggestionsProvider(Observer* observer);
144 void FetchSnippetsInTheBackground();
145
146 // Fetchs content suggestions from the Content Suggestions server for all
147 // remote categories. The request to the server is performed as an interactive
148 // request. Interactive requests are used for actions triggered by the user
149 // and request lower latency processing.
150 void FetchSnippetsForAllCategories();
151
152 // Only used in tests and for debugging in snippets-internal/.
153 // TODO(fhorschig): Remove this getter when there is an interface for the
154 // fetcher that allows better mocks.
155 const NTPSnippetsFetcher* snippets_fetcher() const {
156 return snippets_fetcher_.get();
157 }
158
159 // (Re)schedules the periodic fetching of snippets. If |force| is true, the
160 // tasks will be re-scheduled even if they already exist and have the correct
161 // periods.
162 void RescheduleFetching(bool force);
163
164 // ContentSuggestionsProvider implementation
165 CategoryStatus GetCategoryStatus(Category category) override;
166 CategoryInfo GetCategoryInfo(Category category) override;
167 void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
168 void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
169 const ImageFetchedCallback& callback) override;
170 void Fetch(const Category& category,
171 const std::set<std::string>& known_suggestion_ids,
172 const FetchDoneCallback& callback) override;
173 void ClearHistory(
174 base::Time begin,
175 base::Time end,
176 const base::Callback<bool(const GURL& url)>& filter) override;
177 void ClearCachedSuggestions(Category category) override;
178 void OnSignInStateChanged() override;
179 void GetDismissedSuggestionsForDebugging(
180 Category category,
181 const DismissedSuggestionsCallback& callback) override;
182 void ClearDismissedSuggestionsForDebugging(Category category) override;
183
184 // Returns the maximum number of snippets that will be shown at once.
185 static int GetMaxSnippetCountForTesting();
186
187 // Available snippets, only for unit tests.
188 // TODO(treib): Get rid of this. Tests should use a fake observer instead.
189 const NTPSnippet::PtrVector& GetSnippetsForTesting(Category category) const {
190 return category_contents_.find(category)->second.snippets;
191 }
192
193 // Dismissed snippets, only for unit tests.
194 const NTPSnippet::PtrVector& GetDismissedSnippetsForTesting(
195 Category category) const {
196 return category_contents_.find(category)->second.dismissed;
197 }
198
199 // Overrides internal clock for testing purposes.
200 void SetClockForTesting(std::unique_ptr<base::Clock> clock) {
201 clock_ = std::move(clock);
202 }
203
204 // TODO(tschumann): remove this method as soon as we inject the fetcher into
205 // the constructor.
206 CachedImageFetcher& GetImageFetcherForTesting() { return image_fetcher_; }
207
208 private:
209 friend class RemoteSuggestionsProviderTest;
210
211 FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
212 DontNotifyIfNotAvailable);
213 FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
214 RemoveExpiredDismissedContent);
215 FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
216 RescheduleOnStateChange);
217 FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest, StatusChanges);
218 FRIEND_TEST_ALL_PREFIXES(RemoteSuggestionsProviderTest,
219 SuggestionsFetchedOnSignInAndSignOut);
220
221 // Possible state transitions:
222 // NOT_INITED --------+
223 // / \ |
224 // v v |
225 // READY <--> DISABLED |
226 // \ / |
227 // v v |
228 // ERROR_OCCURRED <-----+
229 enum class State {
230 // The service has just been created. Can change to states:
231 // - DISABLED: After the database is done loading,
232 // GetStateForDependenciesStatus can identify the next state to
233 // be DISABLED.
234 // - READY: if GetStateForDependenciesStatus returns it, after the database
235 // is done loading.
236 // - ERROR_OCCURRED: when an unrecoverable error occurred.
237 NOT_INITED,
238
239 // The service registered observers, timers, etc. and is ready to answer to
240 // queries, fetch snippets... Can change to states:
241 // - DISABLED: when the global Chrome state changes, for example after
242 // |OnStateChanged| is called and sync is disabled.
243 // - ERROR_OCCURRED: when an unrecoverable error occurred.
244 READY,
245
246 // The service is disabled and unregistered the related resources.
247 // Can change to states:
248 // - READY: when the global Chrome state changes, for example after
249 // |OnStateChanged| is called and sync is enabled.
250 // - ERROR_OCCURRED: when an unrecoverable error occurred.
251 DISABLED,
252
253 // The service or one of its dependencies encountered an unrecoverable error
254 // and the service can't be used anymore.
255 ERROR_OCCURRED,
256
257 COUNT
258 };
259
260 struct CategoryContent {
261 // The current status of the category.
262 CategoryStatus status = CategoryStatus::INITIALIZING;
263
264 // The additional information about a category.
265 CategoryInfo info;
266
267 // True iff the server returned results in this category in the last fetch.
268 // We never remove categories that the server still provides, but if the
269 // server stops providing a category, we won't yet report it as NOT_PROVIDED
270 // while we still have non-expired snippets in it.
271 bool included_in_last_server_response = true;
272
273 // All currently active suggestions (excl. the dismissed ones).
274 NTPSnippet::PtrVector snippets;
275
276 // All previous suggestions that we keep around in memory because they can
277 // be on some open NTP. We do not persist this list so that on a new start
278 // of Chrome, this is empty.
279 // |archived| is a FIFO buffer with a maximum length.
280 std::deque<std::unique_ptr<NTPSnippet>> archived;
281
282 // Suggestions that the user dismissed. We keep these around until they
283 // expire so we won't re-add them to |snippets| on the next fetch.
284 NTPSnippet::PtrVector dismissed;
285
286 // Returns a non-dismissed snippet with the given |id_within_category|, or
287 // null if none exist.
288 const NTPSnippet* FindSnippet(const std::string& id_within_category) const;
289
290 explicit CategoryContent(const CategoryInfo& info);
291 CategoryContent(CategoryContent&&);
292 ~CategoryContent();
293 CategoryContent& operator=(CategoryContent&&);
294 };
295
296 // Fetches snippets from the server and replaces old snippets by the new ones.
297 // Requests can be marked more important by setting |interactive_request| to
298 // true (such request might circumvent the daily quota for requests, etc.)
299 // Useful for requests triggered by the user.
300 void FetchSnippets(bool interactive_request);
301
302 // Returns the URL of the image of a snippet if it is among the current or
303 // among the archived snippets in the matching category. Returns an empty URL
304 // otherwise.
305 GURL FindSnippetImageUrl(const ContentSuggestion::ID& suggestion_id) const;
306
307 // Callbacks for the RemoteSuggestionsDatabase.
308 void OnDatabaseLoaded(NTPSnippet::PtrVector snippets);
309 void OnDatabaseError();
310
311 // Callback for fetch-more requests with the NTPSnippetsFetcher.
312 void OnFetchMoreFinished(
313 const FetchDoneCallback& fetching_callback,
314 Status status,
315 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
316
317 // Callback for regular fetch requests with the NTPSnippetsFetcher.
318 void OnFetchFinished(
319 bool interactive_request,
320 Status status,
321 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories);
322
323 // Moves all snippets from |to_archive| into the archive of the |content|.
324 // Clears |to_archive|. As the archive is a FIFO buffer of limited size, this
325 // function will also delete images from the database in case the associated
326 // snippet gets evicted from the archive.
327 void ArchiveSnippets(CategoryContent* content,
328 NTPSnippet::PtrVector* to_archive);
329
330 // Sanitizes newly fetched snippets -- e.g. adding missing dates and filtering
331 // out incomplete results or dismissed snippets (indicated by |dismissed|).
332 void SanitizeReceivedSnippets(const NTPSnippet::PtrVector& dismissed,
333 NTPSnippet::PtrVector* snippets);
334
335 // Adds newly available suggestions to |content|.
336 void IntegrateSnippets(CategoryContent* content,
337 NTPSnippet::PtrVector new_snippets);
338
339 // Dismisses a snippet within a given category content.
340 // Note that this modifies the snippet datastructures of |content|
341 // invalidating iterators.
342 void DismissSuggestionFromCategoryContent(
343 CategoryContent* content,
344 const std::string& id_within_category);
345
346 // Removes expired dismissed snippets from the service and the database.
347 void ClearExpiredDismissedSnippets();
348
349 // Removes images from the DB that are not referenced from any known snippet.
350 // Needs to iterate the whole snippet database -- so do it often enough to
351 // keep it small but not too often as it still iterates over the file system.
352 void ClearOrphanedImages();
353
354 // Clears all stored snippets and updates the observer.
355 void NukeAllSnippets();
356
357 // Completes the initialization phase of the service, registering the last
358 // observers. This is done after construction, once the database is loaded.
359 void FinishInitialization();
360
361 // Triggers a state transition depending on the provided status. This method
362 // is called when a change is detected by |status_service_|.
363 void OnStatusChanged(RemoteSuggestionsStatus old_status,
364 RemoteSuggestionsStatus new_status);
365
366 // Verifies state transitions (see |State|'s documentation) and applies them.
367 // Also updates the provider status. Does nothing except updating the provider
368 // status if called with the current state.
369 void EnterState(State state);
370
371 // Enables the service. Do not call directly, use |EnterState| instead.
372 void EnterStateReady();
373
374 // Disables the service. Do not call directly, use |EnterState| instead.
375 void EnterStateDisabled();
376
377 // Disables the service permanently because an unrecoverable error occurred.
378 // Do not call directly, use |EnterState| instead.
379 void EnterStateError();
380
381 // Converts the cached snippets in the given |category| to content suggestions
382 // and notifies the observer.
383 void NotifyNewSuggestions(Category category, const CategoryContent& content);
384
385 // Updates the internal status for |category| to |category_status_| and
386 // notifies the content suggestions observer if it changed.
387 void UpdateCategoryStatus(Category category, CategoryStatus status);
388 // Calls UpdateCategoryStatus() for all provided categories.
389 void UpdateAllCategoryStatus(CategoryStatus status);
390
391 // Updates the category info for |category|. If a corresponding
392 // CategoryContent object does not exist, it will be created.
393 // Returns the existing or newly created object.
394 CategoryContent* UpdateCategoryInfo(Category category,
395 const CategoryInfo& info);
396
397 void RestoreCategoriesFromPrefs();
398 void StoreCategoriesToPrefs();
399
400 NTPSnippetsRequestParams BuildFetchParams() const;
401
402 void MarkEmptyCategoriesAsLoading();
403
404 State state_;
405
406 PrefService* pref_service_;
407
408 const Category articles_category_;
409
410 std::map<Category, CategoryContent, Category::CompareByID> category_contents_;
411
412 // The ISO 639-1 code of the language used by the application.
413 const std::string application_language_code_;
414
415 // Ranker that orders the categories. Not owned.
416 CategoryRanker* category_ranker_;
417
418 // Classifier that tells us how active the user is. Not owned.
419 const UserClassifier* user_classifier_;
420
421 // Scheduler for fetching snippets. Not owned.
422 NTPSnippetsScheduler* scheduler_;
423
424 // The snippets fetcher.
425 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher_;
426
427 // The database for persisting snippets.
428 std::unique_ptr<RemoteSuggestionsDatabase> database_;
429 base::TimeTicks database_load_start_;
430
431 // The image fetcher.
432 CachedImageFetcher image_fetcher_;
433
434 // The service that provides events and data about the signin and sync state.
435 std::unique_ptr<RemoteSuggestionsStatusService> status_service_;
436
437 // Set to true if FetchSnippets is called while the service isn't ready.
438 // The fetch will be executed once the service enters the READY state.
439 bool fetch_when_ready_;
440
441 // Set to true if NukeAllSnippets is called while the service isn't ready.
442 // The nuke will be executed once the service finishes initialization or
443 // enters the READY state.
444 bool nuke_when_initialized_;
445
446 // A clock for getting the time. This allows to inject a clock in tests.
447 std::unique_ptr<base::Clock> clock_;
448
449 DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProvider);
450 }; 58 };
451 59
452 } // namespace ntp_snippets 60 } // namespace ntp_snippets
453 61
454 #endif // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_ 62 #endif // COMPONENTS_NTP_SNIPPETS_REMOTE_REMOTE_SUGGESTIONS_PROVIDER_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698