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

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

Powered by Google App Engine
This is Rietveld 408576698