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

Side by Side Diff: components/ntp_snippets/ntp_snippets_service.cc

Issue 2255783002: Support server categories in NTPSnippetsService. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: sync Created 4 years, 4 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 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 #include "components/ntp_snippets/ntp_snippets_service.h" 5 #include "components/ntp_snippets/ntp_snippets_service.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <iterator> 8 #include <iterator>
9 #include <utility> 9 #include <utility>
10 10
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 SuggestionsService* suggestions_service, 193 SuggestionsService* suggestions_service,
194 const std::string& application_language_code, 194 const std::string& application_language_code,
195 NTPSnippetsScheduler* scheduler, 195 NTPSnippetsScheduler* scheduler,
196 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher, 196 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
197 std::unique_ptr<ImageFetcher> image_fetcher, 197 std::unique_ptr<ImageFetcher> image_fetcher,
198 std::unique_ptr<ImageDecoder> image_decoder, 198 std::unique_ptr<ImageDecoder> image_decoder,
199 std::unique_ptr<NTPSnippetsDatabase> database, 199 std::unique_ptr<NTPSnippetsDatabase> database,
200 std::unique_ptr<NTPSnippetsStatusService> status_service) 200 std::unique_ptr<NTPSnippetsStatusService> status_service)
201 : ContentSuggestionsProvider(observer, category_factory), 201 : ContentSuggestionsProvider(observer, category_factory),
202 state_(State::NOT_INITED), 202 state_(State::NOT_INITED),
203 category_status_(CategoryStatus::INITIALIZING),
204 pref_service_(pref_service), 203 pref_service_(pref_service),
205 suggestions_service_(suggestions_service), 204 suggestions_service_(suggestions_service),
205 articles_category_(
206 category_factory->FromKnownCategory(KnownCategories::ARTICLES)),
206 application_language_code_(application_language_code), 207 application_language_code_(application_language_code),
207 scheduler_(scheduler), 208 scheduler_(scheduler),
208 snippets_fetcher_(std::move(snippets_fetcher)), 209 snippets_fetcher_(std::move(snippets_fetcher)),
209 image_fetcher_(std::move(image_fetcher)), 210 image_fetcher_(std::move(image_fetcher)),
210 image_decoder_(std::move(image_decoder)), 211 image_decoder_(std::move(image_decoder)),
211 database_(std::move(database)), 212 database_(std::move(database)),
212 snippets_status_service_(std::move(status_service)), 213 snippets_status_service_(std::move(status_service)),
213 fetch_after_load_(false), 214 fetch_after_load_(false),
214 provided_category_(
215 category_factory->FromKnownCategory(KnownCategories::ARTICLES)),
216 thumbnail_requests_throttler_( 215 thumbnail_requests_throttler_(
217 pref_service, 216 pref_service,
218 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) { 217 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
219 observer->OnCategoryStatusChanged(this, provided_category_, category_status_); 218 // Articles category always exists; others will be added as needed.
219 categories_.emplace(articles_category_, CategoryContent());
Marc Treib 2016/08/22 15:06:46 map::emplace isn't available everywhere yet, see h
sfiera 2016/08/24 14:35:56 Done.
220 observer->OnCategoryStatusChanged(this, articles_category_,
221 CategoryStatus::INITIALIZING);
Marc Treib 2016/08/22 15:06:47 Use the actual value from categories_, so the defa
sfiera 2016/08/24 14:35:56 Done.
220 if (database_->IsErrorState()) { 222 if (database_->IsErrorState()) {
221 EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); 223 EnterState(State::ERROR_OCCURRED);
224 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
222 return; 225 return;
223 } 226 }
224 227
225 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError, 228 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError,
226 base::Unretained(this))); 229 base::Unretained(this)));
227 230
228 // We transition to other states while finalizing the initialization, when the 231 // We transition to other states while finalizing the initialization, when the
229 // database is done loading. 232 // database is done loading.
230 database_->LoadSnippets(base::Bind(&NTPSnippetsService::OnDatabaseLoaded, 233 database_->LoadSnippets(base::Bind(&NTPSnippetsService::OnDatabaseLoaded,
231 base::Unretained(this))); 234 base::Unretained(this)));
(...skipping 15 matching lines...) Expand all
247 else 250 else
248 fetch_after_load_ = true; 251 fetch_after_load_ = true;
249 } 252 }
250 253
251 void NTPSnippetsService::FetchSnippetsFromHosts( 254 void NTPSnippetsService::FetchSnippetsFromHosts(
252 const std::set<std::string>& hosts, 255 const std::set<std::string>& hosts,
253 bool interactive_request) { 256 bool interactive_request) {
254 if (!ready()) 257 if (!ready())
255 return; 258 return;
256 259
257 if (snippets_.empty()) 260 // Empty categories are marked as loading; others are unchanged.
258 UpdateCategoryStatus(CategoryStatus::AVAILABLE_LOADING); 261 for (const auto& category : categories_) {
Marc Treib 2016/08/22 15:06:47 Use the actual type? auto isn't clear here, since
sfiera 2016/08/24 14:35:56 I've switched this to what I do in most places, wh
262 if (category.second.snippets.empty())
263 UpdateCategoryStatus(category.first, CategoryStatus::AVAILABLE_LOADING);
264 }
259 265
260 snippets_fetcher_->FetchSnippetsFromHosts( 266 snippets_fetcher_->FetchSnippetsFromHosts(
261 hosts, application_language_code_, kMaxSnippetCount, interactive_request); 267 hosts, application_language_code_, kMaxSnippetCount, interactive_request);
262 } 268 }
263 269
264 void NTPSnippetsService::RescheduleFetching() { 270 void NTPSnippetsService::RescheduleFetching() {
265 // The scheduler only exists on Android so far, it's null on other platforms. 271 // The scheduler only exists on Android so far, it's null on other platforms.
266 if (!scheduler_) 272 if (!scheduler_)
267 return; 273 return;
268 274
269 if (ready()) { 275 if (ready()) {
270 base::Time now = base::Time::Now(); 276 base::Time now = base::Time::Now();
271 scheduler_->Schedule( 277 scheduler_->Schedule(
272 GetFetchingIntervalWifiCharging(), GetFetchingIntervalWifi(now), 278 GetFetchingIntervalWifiCharging(), GetFetchingIntervalWifi(now),
273 GetFetchingIntervalFallback(), GetRescheduleTime(now)); 279 GetFetchingIntervalFallback(), GetRescheduleTime(now));
274 } else { 280 } else {
275 scheduler_->Unschedule(); 281 scheduler_->Unschedule();
276 } 282 }
277 } 283 }
278 284
279 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) { 285 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) {
280 DCHECK(category.IsKnownCategory(KnownCategories::ARTICLES)); 286 DCHECK(categories_.find(category) != categories_.end());
281 return category_status_; 287 return categories_[category].status;
282 } 288 }
283 289
284 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) { 290 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) {
291 DCHECK(categories_.find(category) != categories_.end());
292 // TODO(sfiera): pass back localized category titles.
Marc Treib 2016/08/22 15:06:47 nit: mention that this refers to non-articles cate
sfiera 2016/08/24 14:35:56 Changed.
285 return CategoryInfo( 293 return CategoryInfo(
286 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER), 294 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER),
287 ContentSuggestionsCardLayout::FULL_CARD, 295 ContentSuggestionsCardLayout::FULL_CARD,
288 /* has_more_button */ false, 296 /* has_more_button */ false,
289 /* show_if_empty */ true); 297 /* show_if_empty */ true);
290 } 298 }
291 299
292 void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) { 300 void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) {
293 if (!ready()) 301 if (!ready())
294 return; 302 return;
295 303
304 Category category_id = GetCategoryFromUniqueID(suggestion_id);
Marc Treib 2016/08/22 15:06:47 Just "category" please (that's what it's called ev
sfiera 2016/08/24 14:35:56 Done.
296 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); 305 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
297 306
307 DCHECK(categories_.find(category_id) != categories_.end());
308
309 CategoryContent* category = &categories_[category_id];
Marc Treib 2016/08/22 15:06:46 call this something else, like "content"
sfiera 2016/08/24 14:35:56 Done.
298 auto it = 310 auto it =
299 std::find_if(snippets_.begin(), snippets_.end(), 311 std::find_if(category->snippets.begin(), category->snippets.end(),
300 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 312 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
301 return snippet->id() == snippet_id; 313 return snippet->id() == snippet_id;
302 }); 314 });
303 if (it == snippets_.end()) 315 if (it == category->snippets.end())
304 return; 316 return;
305 317
306 (*it)->set_dismissed(true); 318 (*it)->set_dismissed(true);
307 319
308 database_->SaveSnippet(**it); 320 database_->SaveSnippet(**it);
309 database_->DeleteImage((*it)->id()); 321 database_->DeleteImage((*it)->id());
310 322
311 dismissed_snippets_.push_back(std::move(*it)); 323 category->dismissed.push_back(std::move(*it));
312 snippets_.erase(it); 324 category->snippets.erase(it);
313 } 325 }
314 326
315 void NTPSnippetsService::FetchSuggestionImage( 327 void NTPSnippetsService::FetchSuggestionImage(
316 const std::string& suggestion_id, 328 const std::string& suggestion_id,
317 const ImageFetchedCallback& callback) { 329 const ImageFetchedCallback& callback) {
318 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
319 database_->LoadImage( 330 database_->LoadImage(
320 snippet_id, 331 suggestion_id,
321 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase, 332 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase,
322 base::Unretained(this), callback, snippet_id)); 333 base::Unretained(this), callback, suggestion_id));
323 } 334 }
324 335
325 void NTPSnippetsService::ClearCachedSuggestionsForDebugging(Category category) { 336 void NTPSnippetsService::ClearCachedSuggestionsForDebugging(
326 DCHECK_EQ(category, provided_category_); 337 Category category_id) {
327 if (!initialized()) 338 if (!initialized())
328 return; 339 return;
329 340
330 if (snippets_.empty()) 341 if (categories_.find(category_id) == categories_.end())
342 return;
343 CategoryContent* category = &categories_[category_id];
344 if (category->snippets.empty())
331 return; 345 return;
332 346
333 database_->DeleteSnippets(snippets_); 347 if (category_id == articles_category_)
334 snippets_.clear(); 348 database_->DeleteSnippets(category->snippets);
349 category->snippets.clear();
335 350
336 NotifyNewSuggestions(); 351 NotifyNewSuggestions();
337 } 352 }
338 353
339 std::vector<ContentSuggestion> 354 std::vector<ContentSuggestion>
340 NTPSnippetsService::GetDismissedSuggestionsForDebugging(Category category) { 355 NTPSnippetsService::GetDismissedSuggestionsForDebugging(Category category_id) {
341 DCHECK_EQ(category, provided_category_); 356 DCHECK(categories_.find(category_id) != categories_.end());
357
342 std::vector<ContentSuggestion> result; 358 std::vector<ContentSuggestion> result;
343 for (const std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 359 const CategoryContent& category = categories_[category_id];
360 for (const std::unique_ptr<NTPSnippet>& snippet : category.dismissed) {
344 if (!snippet->is_complete()) 361 if (!snippet->is_complete())
345 continue; 362 continue;
346 ContentSuggestion suggestion( 363 ContentSuggestion suggestion(MakeUniqueID(category_id, snippet->id()),
347 MakeUniqueID(provided_category_, snippet->id()), 364 snippet->best_source().url);
348 snippet->best_source().url);
349 suggestion.set_amp_url(snippet->best_source().amp_url); 365 suggestion.set_amp_url(snippet->best_source().amp_url);
350 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 366 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
351 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 367 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
352 suggestion.set_publish_date(snippet->publish_date()); 368 suggestion.set_publish_date(snippet->publish_date());
353 suggestion.set_publisher_name( 369 suggestion.set_publisher_name(
354 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 370 base::UTF8ToUTF16(snippet->best_source().publisher_name));
355 suggestion.set_score(snippet->score()); 371 suggestion.set_score(snippet->score());
356 result.emplace_back(std::move(suggestion)); 372 result.emplace_back(std::move(suggestion));
357 } 373 }
358 return result; 374 return result;
359 } 375 }
360 376
361 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( 377 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging(
362 Category category) { 378 Category category_id) {
363 DCHECK_EQ(category, provided_category_); 379 DCHECK(categories_.find(category_id) != categories_.end());
380
364 if (!initialized()) 381 if (!initialized())
365 return; 382 return;
366 383
367 if (dismissed_snippets_.empty()) 384 CategoryContent* category = &categories_[category_id];
385 if (category->dismissed.empty())
368 return; 386 return;
369 387
370 database_->DeleteSnippets(dismissed_snippets_); 388 if (category_id == articles_category_)
371 dismissed_snippets_.clear(); 389 database_->DeleteSnippets(category->dismissed);
390 category->dismissed.clear();
372 } 391 }
373 392
374 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { 393 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const {
375 // |suggestions_service_| can be null in tests. 394 // |suggestions_service_| can be null in tests.
376 if (!suggestions_service_) 395 if (!suggestions_service_)
377 return std::set<std::string>(); 396 return std::set<std::string>();
378 397
379 // TODO(treib): This should just call GetSnippetHostsFromPrefs. 398 // TODO(treib): This should just call GetSnippetHostsFromPrefs.
380 return GetSuggestionsHostsImpl( 399 return GetSuggestionsHostsImpl(
381 suggestions_service_->GetSuggestionsDataFromCache()); 400 suggestions_service_->GetSuggestionsDataFromCache());
382 } 401 }
383 402
384 // static 403 // static
385 int NTPSnippetsService::GetMaxSnippetCountForTesting() { 404 int NTPSnippetsService::GetMaxSnippetCountForTesting() {
386 return kMaxSnippetCount; 405 return kMaxSnippetCount;
387 } 406 }
388 407
389 //////////////////////////////////////////////////////////////////////////////// 408 ////////////////////////////////////////////////////////////////////////////////
390 // Private methods 409 // Private methods
391 410
392 // image_fetcher::ImageFetcherDelegate implementation. 411 // image_fetcher::ImageFetcherDelegate implementation.
393 void NTPSnippetsService::OnImageDataFetched(const std::string& snippet_id, 412 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id,
394 const std::string& image_data) { 413 const std::string& image_data) {
395 if (image_data.empty()) 414 if (image_data.empty())
396 return; 415 return;
397 416
417 Category category_id = GetCategoryFromUniqueID(suggestion_id);
418 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
419
420 auto category_it = categories_.find(category_id);
421 if (category_it == categories_.end())
422 return;
423
424 const CategoryContent& category = category_it->second;
425
398 // Only save the image if the corresponding snippet still exists. 426 // Only save the image if the corresponding snippet still exists.
399 auto it = 427 auto it =
400 std::find_if(snippets_.begin(), snippets_.end(), 428 std::find_if(category.snippets.begin(), category.snippets.end(),
401 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 429 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
402 return snippet->id() == snippet_id; 430 return snippet->id() == snippet_id;
403 }); 431 });
404 if (it == snippets_.end()) 432 if (it == category.snippets.end())
405 return; 433 return;
406 434
407 database_->SaveImage(snippet_id, image_data); 435 database_->SaveImage(suggestion_id, image_data);
408 } 436 }
409 437
410 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { 438 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) {
411 if (state_ == State::ERROR_OCCURRED) 439 if (state_ == State::ERROR_OCCURRED)
412 return; 440 return;
413 DCHECK(state_ == State::NOT_INITED); 441 DCHECK(state_ == State::NOT_INITED);
414 DCHECK(snippets_.empty()); 442 DCHECK(categories_.size() == 1); // Only articles category, so far.
415 DCHECK(dismissed_snippets_.empty()); 443 DCHECK(categories_.find(articles_category_) != categories_.end());
444
445 // TODO(sfiera): support non-article categories in database.
446 CategoryContent* category = &categories_[articles_category_];
416 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { 447 for (std::unique_ptr<NTPSnippet>& snippet : snippets) {
417 if (snippet->is_dismissed()) 448 if (snippet->is_dismissed())
418 dismissed_snippets_.emplace_back(std::move(snippet)); 449 category->dismissed.emplace_back(std::move(snippet));
419 else 450 else
420 snippets_.emplace_back(std::move(snippet)); 451 category->snippets.emplace_back(std::move(snippet));
421 } 452 }
422 std::sort(snippets_.begin(), snippets_.end(), 453
454 std::sort(category->snippets.begin(), category->snippets.end(),
423 [](const std::unique_ptr<NTPSnippet>& lhs, 455 [](const std::unique_ptr<NTPSnippet>& lhs,
424 const std::unique_ptr<NTPSnippet>& rhs) { 456 const std::unique_ptr<NTPSnippet>& rhs) {
425 return lhs->score() > rhs->score(); 457 return lhs->score() > rhs->score();
426 }); 458 });
427 459
428 ClearExpiredSnippets(); 460 ClearExpiredSnippets(articles_category_);
429 FinishInitialization(); 461 FinishInitialization();
430 } 462 }
431 463
432 void NTPSnippetsService::OnDatabaseError() { 464 void NTPSnippetsService::OnDatabaseError() {
433 EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); 465 EnterState(State::ERROR_OCCURRED);
466 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
434 } 467 }
435 468
436 // TODO(dgn): name clash between content suggestions and suggestions hosts. 469 // TODO(dgn): name clash between content suggestions and suggestions hosts.
437 // method name should be changed. 470 // method name should be changed.
438 void NTPSnippetsService::OnSuggestionsChanged( 471 void NTPSnippetsService::OnSuggestionsChanged(
439 const SuggestionsProfile& suggestions) { 472 const SuggestionsProfile& suggestions) {
440 DCHECK(initialized()); 473 DCHECK(initialized());
441 474
442 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions); 475 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions);
443 if (hosts == GetSnippetHostsFromPrefs()) 476 if (hosts == GetSnippetHostsFromPrefs())
444 return; 477 return;
445 478
446 // Remove existing snippets that aren't in the suggestions anymore. 479 // Remove existing snippets that aren't in the suggestions anymore.
480 //
447 // TODO(treib,maybelle): If there is another source with an allowed host, 481 // TODO(treib,maybelle): If there is another source with an allowed host,
448 // then we should fall back to that. 482 // then we should fall back to that.
449 // First, move them over into |to_delete|. 483 // First, move them over into |to_delete|.
Marc Treib 2016/08/22 15:06:46 This line isn't part of the TODO; move it to the e
sfiera 2016/08/24 14:35:56 Done.
484 //
485 // TODO(sfiera): determine when non-article categories should restrict hosts,
486 // and apply the same logic to them here. Maybe never?
Marc Treib 2016/08/22 15:06:47 Eh, since host restricts are likely going away any
sfiera 2016/08/24 14:35:56 Acknowledged.
487 CategoryContent* category = &categories_[articles_category_];
450 NTPSnippet::PtrVector to_delete; 488 NTPSnippet::PtrVector to_delete;
451 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 489 for (std::unique_ptr<NTPSnippet>& snippet : category->snippets) {
452 if (!hosts.count(snippet->best_source().url.host())) 490 if (!hosts.count(snippet->best_source().url.host()))
453 to_delete.emplace_back(std::move(snippet)); 491 to_delete.emplace_back(std::move(snippet));
454 } 492 }
455 Compact(&snippets_); 493 Compact(&category->snippets);
456 // Then delete the removed snippets from the database. 494 // Then delete the removed snippets from the database.
457 database_->DeleteSnippets(to_delete); 495 database_->DeleteSnippets(to_delete);
458 496
459 StoreSnippetHostsToPrefs(hosts); 497 StoreSnippetHostsToPrefs(hosts);
460 498
461 // We removed some suggestions, so we want to let the client know about that. 499 // We removed some suggestions, so we want to let the client know about that.
462 // The fetch might take a long time or not complete so we don't want to wait 500 // The fetch might take a long time or not complete so we don't want to wait
463 // for its callback. 501 // for its callback.
464 NotifyNewSuggestions(); 502 NotifyNewSuggestions();
465 503
466 FetchSnippetsFromHosts(hosts, /*force_request=*/false); 504 FetchSnippetsFromHosts(hosts, /*force_request=*/false);
467 } 505 }
468 506
469 void NTPSnippetsService::OnFetchFinished( 507 void NTPSnippetsService::OnFetchFinished(
470 NTPSnippetsFetcher::OptionalSnippets snippets) { 508 NTPSnippetsFetcher::OptionalSnippets snippets) {
471 if (!ready()) 509 if (!ready())
472 return; 510 return;
473 511
474 DCHECK(category_status_ == CategoryStatus::AVAILABLE || 512 // If snippets were fetched successfully, update our |categories_| from each
475 category_status_ == CategoryStatus::AVAILABLE_LOADING); 513 // source and report the category as AVAILABLE.
514 if (snippets) {
515 for (std::pair<const Category, NTPSnippet::PtrVector>& item : *snippets) {
516 Category category_id = item.first;
517 NTPSnippet::PtrVector& new_snippets = item.second;
518 // Sparse histogram used because the number of snippets is small (bound by
519 // kMaxSnippetCount).
520 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount));
521 // TODO(sfiera): per-category histograms
522 if (category_id == articles_category_)
Marc Treib 2016/08/22 15:06:47 Braces please
sfiera 2016/08/24 14:35:56 Done.
523 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched",
524 new_snippets.size());
476 525
477 // TODO(sfiera): support more than just the provided_category_ ARTICLES. 526 MergeSnippets(category_id, std::move(new_snippets));
478 if (snippets && (snippets->find(provided_category_) != snippets->end())) { 527 ClearExpiredSnippets(category_id);
479 // Sparse histogram used because the number of snippets is small (bound by 528 // If there are more snippets than we want to show, delete the extra ones.
480 // kMaxSnippetCount). 529 CategoryContent* category = &categories_[category_id];
481 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); 530 if (category->snippets.size() > kMaxSnippetCount) {
482 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", 531 NTPSnippet::PtrVector to_delete(
483 (*snippets)[provided_category_].size()); 532 std::make_move_iterator(category->snippets.begin() +
484 MergeSnippets(std::move((*snippets)[provided_category_])); 533 kMaxSnippetCount),
534 std::make_move_iterator(category->snippets.end()));
535 category->snippets.resize(kMaxSnippetCount);
536 database_->DeleteSnippets(to_delete);
537 }
538
539 // Report category as available, even if empty. It is provided by the
540 // service, and is no longer loading.
541 UpdateCategoryStatus(category_id, CategoryStatus::AVAILABLE);
542 }
485 } 543 }
486 544
487 ClearExpiredSnippets(); 545 // Go back and expire articles out of categories that weren't updated. If
546 // |snippets| was not provided due to an error, that's all categories.
547 std::vector<Category> categories_to_remove_;
548 for (auto& item : categories_) {
549 Category category_id = item.first;
550 CategoryContent& category = item.second;
551 if (!snippets || (snippets->find(category_id) == snippets->end()))
552 continue; // skip anything we processed in the first loop.
Marc Treib 2016/08/22 15:06:46 Why? I think the main purpose of calling ClearExpi
sfiera 2016/08/24 14:35:56 I'd sort of misunderstood ClearExpiredSnippets(),
488 553
489 // If there are more snippets than we want to show, delete the extra ones. 554 ClearExpiredSnippets(category_id);
490 if (snippets_.size() > kMaxSnippetCount) { 555 if (category.snippets.empty() && (category_id != articles_category_)) {
491 NTPSnippet::PtrVector to_delete( 556 categories_to_remove_.push_back(category_id);
492 std::make_move_iterator(snippets_.begin() + kMaxSnippetCount), 557 }
493 std::make_move_iterator(snippets_.end()));
494 snippets_.resize(kMaxSnippetCount);
495 database_->DeleteSnippets(to_delete);
496 } 558 }
497 559
498 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", 560 // If a category (other than ARTICLES) is now empty, and it was not present in
499 snippets_.size()); 561 // |snippets|, then report that the snippets service no longer provides that
500 if (snippets_.empty() && !dismissed_snippets_.empty()) { 562 // category. If the category is now |empty| but it was in |snippets|, then we
Marc Treib 2016/08/22 15:06:46 nit: no pipes around empty
sfiera 2016/08/24 14:35:57 Done.
501 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", 563 // will continue to report it as AVAILABLE.
502 dismissed_snippets_.size()); 564 for (Category category : categories_to_remove_) {
565 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
566 categories_.erase(category);
503 } 567 }
504 568
505 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 569 // TODO(sfiera): equivalent metrics for non-articles.
570 const CategoryContent& category = categories_[articles_category_];
571 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles",
572 category.snippets.size());
573 if (category.snippets.empty() && !category.dismissed.empty()) {
574 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded",
575 category.dismissed.size());
576 }
577
506 NotifyNewSuggestions(); 578 NotifyNewSuggestions();
507 } 579 }
508 580
509 void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { 581 void NTPSnippetsService::MergeSnippets(Category category_id,
582 NTPSnippet::PtrVector new_snippets) {
510 DCHECK(ready()); 583 DCHECK(ready());
584 CategoryContent* category = &categories_[category_id];
511 585
512 // Remove new snippets that we already have, or that have been dismissed. 586 // Remove new snippets that we already have, or that have been dismissed.
513 std::set<std::string> old_snippet_ids; 587 std::set<std::string> old_snippet_ids;
514 InsertAllIDs(dismissed_snippets_, &old_snippet_ids); 588 InsertAllIDs(category->dismissed, &old_snippet_ids);
515 InsertAllIDs(snippets_, &old_snippet_ids); 589 InsertAllIDs(category->snippets, &old_snippet_ids);
516 new_snippets.erase( 590 new_snippets.erase(
517 std::remove_if( 591 std::remove_if(
518 new_snippets.begin(), new_snippets.end(), 592 new_snippets.begin(), new_snippets.end(),
519 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { 593 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) {
520 if (old_snippet_ids.count(snippet->id())) 594 if (old_snippet_ids.count(snippet->id()))
521 return true; 595 return true;
522 for (const SnippetSource& source : snippet->sources()) { 596 for (const SnippetSource& source : snippet->sources()) {
523 if (old_snippet_ids.count(source.url.spec())) 597 if (old_snippet_ids.count(source.url.spec()))
524 return true; 598 return true;
525 } 599 }
(...skipping 27 matching lines...) Expand all
553 new_snippets.end()); 627 new_snippets.end());
554 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); 628 int num_snippets_dismissed = num_new_snippets - new_snippets.size();
555 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", 629 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
556 num_snippets_dismissed > 0); 630 num_snippets_dismissed > 0);
557 if (num_snippets_dismissed > 0) { 631 if (num_snippets_dismissed > 0) {
558 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", 632 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
559 num_snippets_dismissed); 633 num_snippets_dismissed);
560 } 634 }
561 } 635 }
562 636
563 // Save the new snippets to the DB. 637 // Save new articles to the DB.
564 database_->SaveSnippets(new_snippets); 638 // TODO(sfiera): save non-articles to DB too.
639 if (category_id == articles_category_)
640 database_->SaveSnippets(new_snippets);
565 641
566 // Insert the new snippets at the front. 642 // Insert the new snippets at the front.
567 snippets_.insert(snippets_.begin(), 643 category->snippets.insert(category->snippets.begin(),
568 std::make_move_iterator(new_snippets.begin()), 644 std::make_move_iterator(new_snippets.begin()),
569 std::make_move_iterator(new_snippets.end())); 645 std::make_move_iterator(new_snippets.end()));
570 } 646 }
571 647
572 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { 648 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const {
573 std::set<std::string> hosts; 649 std::set<std::string> hosts;
574 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); 650 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts);
575 for (const auto& value : *list) { 651 for (const auto& value : *list) {
576 std::string str; 652 std::string str;
577 bool success = value->GetAsString(&str); 653 bool success = value->GetAsString(&str);
578 DCHECK(success) << "Failed to parse snippet host from prefs"; 654 DCHECK(success) << "Failed to parse snippet host from prefs";
579 hosts.insert(std::move(str)); 655 hosts.insert(std::move(str));
580 } 656 }
581 return hosts; 657 return hosts;
582 } 658 }
583 659
584 void NTPSnippetsService::StoreSnippetHostsToPrefs( 660 void NTPSnippetsService::StoreSnippetHostsToPrefs(
585 const std::set<std::string>& hosts) { 661 const std::set<std::string>& hosts) {
586 base::ListValue list; 662 base::ListValue list;
587 for (const std::string& host : hosts) 663 for (const std::string& host : hosts)
588 list.AppendString(host); 664 list.AppendString(host);
589 pref_service_->Set(prefs::kSnippetHosts, list); 665 pref_service_->Set(prefs::kSnippetHosts, list);
590 } 666 }
591 667
592 void NTPSnippetsService::ClearExpiredSnippets() { 668 void NTPSnippetsService::ClearExpiredSnippets(Category category_id) {
669 DCHECK(categories_.find(category_id) != categories_.end());
670 CategoryContent* category = &categories_[category_id];
671
593 base::Time expiry = base::Time::Now(); 672 base::Time expiry = base::Time::Now();
594 673
595 // Move expired snippets over into |to_delete|. 674 // Move expired snippets over into |to_delete|.
596 NTPSnippet::PtrVector to_delete; 675 NTPSnippet::PtrVector to_delete;
597 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 676 for (std::unique_ptr<NTPSnippet>& snippet : category->snippets) {
598 if (snippet->expiry_date() <= expiry) 677 if (snippet->expiry_date() <= expiry)
599 to_delete.emplace_back(std::move(snippet)); 678 to_delete.emplace_back(std::move(snippet));
600 } 679 }
601 Compact(&snippets_); 680 Compact(&category->snippets);
602 681
603 // Move expired dismissed snippets over into |to_delete| as well. 682 // Move expired dismissed snippets over into |to_delete| as well.
604 for (std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 683 for (std::unique_ptr<NTPSnippet>& snippet : category->dismissed) {
605 if (snippet->expiry_date() <= expiry) 684 if (snippet->expiry_date() <= expiry)
606 to_delete.emplace_back(std::move(snippet)); 685 to_delete.emplace_back(std::move(snippet));
607 } 686 }
608 Compact(&dismissed_snippets_); 687 Compact(&category->dismissed);
609 688
610 // Finally, actually delete the removed snippets from the DB. 689 // Finally, actually delete the removed snippets from the DB.
611 database_->DeleteSnippets(to_delete); 690 if (category_id == articles_category_)
691 database_->DeleteSnippets(to_delete);
612 692
613 // If there are any snippets left, schedule a timer for the next expiry. 693 // Unless there are no snippets left, schedule a timer for the next expiry.
614 if (snippets_.empty() && dismissed_snippets_.empty()) 694 if (category->snippets.empty() && category->dismissed.empty())
615 return; 695 return;
616 696
617 base::Time next_expiry = base::Time::Max(); 697 base::Time next_expiry = base::Time::Max();
618 for (const auto& snippet : snippets_) { 698 for (const auto& snippet : category->snippets) {
619 if (snippet->expiry_date() < next_expiry) 699 if (snippet->expiry_date() < next_expiry)
620 next_expiry = snippet->expiry_date(); 700 next_expiry = snippet->expiry_date();
621 } 701 }
622 for (const auto& snippet : dismissed_snippets_) { 702 for (const auto& snippet : category->dismissed) {
623 if (snippet->expiry_date() < next_expiry) 703 if (snippet->expiry_date() < next_expiry)
624 next_expiry = snippet->expiry_date(); 704 next_expiry = snippet->expiry_date();
625 } 705 }
706
626 DCHECK_GT(next_expiry, expiry); 707 DCHECK_GT(next_expiry, expiry);
627 expiry_timer_.Start(FROM_HERE, next_expiry - expiry, 708 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
628 base::Bind(&NTPSnippetsService::ClearExpiredSnippets, 709 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
629 base::Unretained(this))); 710 base::Unretained(this), category_id));
630 } 711 }
631 712
632 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( 713 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase(
633 const ImageFetchedCallback& callback, 714 const ImageFetchedCallback& callback,
634 const std::string& snippet_id, 715 const std::string& suggestion_id,
635 std::string data) { 716 std::string data) {
636 // |image_decoder_| is null in tests. 717 // |image_decoder_| is null in tests.
637 if (image_decoder_ && !data.empty()) { 718 if (image_decoder_ && !data.empty()) {
638 image_decoder_->DecodeImage( 719 image_decoder_->DecodeImage(
639 std::move(data), 720 std::move(data),
640 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, 721 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase,
641 base::Unretained(this), callback, snippet_id)); 722 base::Unretained(this), callback, suggestion_id));
642 return; 723 return;
643 } 724 }
644 725
645 // Fetching from the DB failed; start a network fetch. 726 // Fetching from the DB failed; start a network fetch.
646 FetchSnippetImageFromNetwork(snippet_id, callback); 727 FetchSnippetImageFromNetwork(suggestion_id, callback);
647 } 728 }
648 729
649 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase( 730 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase(
650 const ImageFetchedCallback& callback, 731 const ImageFetchedCallback& callback,
651 const std::string& snippet_id, 732 const std::string& suggestion_id,
652 const gfx::Image& image) { 733 const gfx::Image& image) {
653 if (!image.IsEmpty()) { 734 if (!image.IsEmpty()) {
654 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 735 callback.Run(suggestion_id, image);
655 return; 736 return;
656 } 737 }
657 738
658 // If decoding the image failed, delete the DB entry. 739 // If decoding the image failed, delete the DB entry.
659 database_->DeleteImage(snippet_id); 740 database_->DeleteImage(suggestion_id);
660 741
661 FetchSnippetImageFromNetwork(snippet_id, callback); 742 FetchSnippetImageFromNetwork(suggestion_id, callback);
662 } 743 }
663 744
664 void NTPSnippetsService::FetchSnippetImageFromNetwork( 745 void NTPSnippetsService::FetchSnippetImageFromNetwork(
665 const std::string& snippet_id, 746 const std::string& suggestion_id,
666 const ImageFetchedCallback& callback) { 747 const ImageFetchedCallback& callback) {
748 Category category_id = GetCategoryFromUniqueID(suggestion_id);
749 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
750
751 auto category_it = categories_.find(category_id);
752 if (category_it == categories_.end()) {
753 callback.Run(suggestion_id, gfx::Image());
754 return;
755 }
756
757 const CategoryContent& category = category_it->second;
667 auto it = 758 auto it =
668 std::find_if(snippets_.begin(), snippets_.end(), 759 std::find_if(category.snippets.begin(), category.snippets.end(),
669 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 760 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
670 return snippet->id() == snippet_id; 761 return snippet->id() == snippet_id;
671 }); 762 });
672 763
673 if (it == snippets_.end() || 764 if (it == category.snippets.end() ||
674 !thumbnail_requests_throttler_.DemandQuotaForRequest( 765 !thumbnail_requests_throttler_.DemandQuotaForRequest(
675 /*interactive_request=*/true)) { 766 /*interactive_request=*/true)) {
676 // Return an empty image. Directly, this is never synchronous with the 767 // Return an empty image. Directly, this is never synchronous with the
677 // original FetchSuggestionImage() call - an asynchronous database query has 768 // original FetchSuggestionImage() call - an asynchronous database query has
678 // happened in the meantime. 769 // happened in the meantime.
679 OnSnippetImageDecodedFromNetwork(callback, snippet_id, gfx::Image()); 770 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
680 return; 771 return;
681 } 772 }
682 773
683 const NTPSnippet& snippet = *it->get(); 774 const NTPSnippet& snippet = *it->get();
684 775
685 // TODO(jkrcal): We probably should rename OnImageDataFetched() to 776 // TODO(jkrcal): We probably should rename OnImageDataFetched() to
686 // CacheImageData(). This would document that this is actually independent 777 // CacheImageData(). This would document that this is actually independent
687 // from the individual fetch-flow. 778 // from the individual fetch-flow.
688 // The image fetcher calls OnImageDataFetched() with the raw data (this object 779 // The image fetcher calls OnImageDataFetched() with the raw data (this object
689 // is an ImageFetcherDelegate) and then also 780 // is an ImageFetcherDelegate) and then also
690 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. 781 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded.
691 image_fetcher_->StartOrQueueNetworkRequest( 782 image_fetcher_->StartOrQueueNetworkRequest(
692 snippet.id(), snippet.salient_image_url(), 783 suggestion_id, snippet.salient_image_url(),
693 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, 784 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork,
694 base::Unretained(this), callback)); 785 base::Unretained(this), callback));
695 } 786 }
696 787
697 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( 788 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork(
698 const ImageFetchedCallback& callback, 789 const ImageFetchedCallback& callback,
699 const std::string& snippet_id, 790 const std::string& suggestion_id,
700 const gfx::Image& image) { 791 const gfx::Image& image) {
701 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 792 callback.Run(suggestion_id, image);
702 } 793 }
703 794
704 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { 795 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) {
705 if (fetch_snippets) 796 if (fetch_snippets)
706 FetchSnippets(/*force_request=*/false); 797 FetchSnippets(/*force_request=*/false);
707 798
708 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant, 799 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant,
709 // otherwise we transition to |AVAILABLE| here. 800 // otherwise we transition to |AVAILABLE| here.
710 if (category_status_ != CategoryStatus::AVAILABLE_LOADING) 801 if (categories_[articles_category_].status !=
711 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 802 CategoryStatus::AVAILABLE_LOADING)
Marc Treib 2016/08/22 15:06:46 Braces please
sfiera 2016/08/24 14:35:57 Done.
803 UpdateCategoryStatus(articles_category_, CategoryStatus::AVAILABLE);
712 804
713 // If host restrictions are enabled, register for host list updates. 805 // If host restrictions are enabled, register for host list updates.
714 // |suggestions_service_| can be null in tests. 806 // |suggestions_service_| can be null in tests.
715 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { 807 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) {
716 suggestions_service_subscription_ = 808 suggestions_service_subscription_ =
717 suggestions_service_->AddCallback(base::Bind( 809 suggestions_service_->AddCallback(base::Bind(
718 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); 810 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this)));
719 } 811 }
720 812
721 RescheduleFetching(); 813 RescheduleFetching();
722 } 814 }
723 815
724 void NTPSnippetsService::EnterStateDisabled() { 816 void NTPSnippetsService::EnterStateDisabled() {
725 ClearCachedSuggestionsForDebugging(provided_category_); 817 std::vector<Category> category_list;
726 ClearDismissedSuggestionsForDebugging(provided_category_); 818 for (const auto& item : categories_) {
819 Category category_id = item.first;
820 category_list.push_back(category_id);
821 }
822
823 // Empty the ARTICLES category and remove all others, since they may or may
824 // not be personalized.
825 for (Category category_id : category_list) {
826 ClearCachedSuggestionsForDebugging(category_id);
827 ClearDismissedSuggestionsForDebugging(category_id);
828 if (category_id != articles_category_) {
829 UpdateCategoryStatus(category_id, CategoryStatus::NOT_PROVIDED);
830 categories_.erase(category_id);
831 }
832 }
727 833
728 expiry_timer_.Stop(); 834 expiry_timer_.Stop();
729 suggestions_service_subscription_.reset(); 835 suggestions_service_subscription_.reset();
730 RescheduleFetching(); 836 RescheduleFetching();
731 } 837 }
732 838
733 void NTPSnippetsService::EnterStateError() { 839 void NTPSnippetsService::EnterStateError() {
734 expiry_timer_.Stop(); 840 expiry_timer_.Stop();
735 suggestions_service_subscription_.reset(); 841 suggestions_service_subscription_.reset();
736 RescheduleFetching(); 842 RescheduleFetching();
(...skipping 19 matching lines...) Expand all
756 // Always notify here even if we got nothing from the database, because we 862 // Always notify here even if we got nothing from the database, because we
757 // don't know how long the fetch will take or if it will even complete. 863 // don't know how long the fetch will take or if it will even complete.
758 NotifyNewSuggestions(); 864 NotifyNewSuggestions();
759 } 865 }
760 866
761 void NTPSnippetsService::OnDisabledReasonChanged( 867 void NTPSnippetsService::OnDisabledReasonChanged(
762 DisabledReason disabled_reason) { 868 DisabledReason disabled_reason) {
763 switch (disabled_reason) { 869 switch (disabled_reason) {
764 case DisabledReason::NONE: 870 case DisabledReason::NONE:
765 // Do not change the status. That will be done in EnterStateEnabled() 871 // Do not change the status. That will be done in EnterStateEnabled()
766 EnterState(State::READY, category_status_); 872 EnterState(State::READY);
767 break; 873 break;
768 874
769 case DisabledReason::EXPLICITLY_DISABLED: 875 case DisabledReason::EXPLICITLY_DISABLED:
770 EnterState(State::DISABLED, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 876 EnterState(State::DISABLED);
877 UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
771 break; 878 break;
772 879
773 case DisabledReason::SIGNED_OUT: 880 case DisabledReason::SIGNED_OUT:
774 EnterState(State::DISABLED, CategoryStatus::SIGNED_OUT); 881 EnterState(State::DISABLED);
882 UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
775 break; 883 break;
776 } 884 }
777 } 885 }
778 886
779 void NTPSnippetsService::EnterState(State state, CategoryStatus status) { 887 void NTPSnippetsService::EnterState(State state) {
780 UpdateCategoryStatus(status);
781
782 if (state == state_) 888 if (state == state_)
783 return; 889 return;
784 890
785 switch (state) { 891 switch (state) {
786 case State::NOT_INITED: 892 case State::NOT_INITED:
787 // Initial state, it should not be possible to get back there. 893 // Initial state, it should not be possible to get back there.
788 NOTREACHED(); 894 NOTREACHED();
789 return; 895 return;
790 896
791 case State::READY: { 897 case State::READY: {
792 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); 898 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
793 899
794 bool fetch_snippets = snippets_.empty() || fetch_after_load_; 900 // TODO(sfiera): always fetch, because there might be server-side
901 // categories we're interested in?
902 bool fetch_snippets =
903 categories_[articles_category_].snippets.empty() || fetch_after_load_;
795 DVLOG(1) << "Entering state: READY"; 904 DVLOG(1) << "Entering state: READY";
796 state_ = State::READY; 905 state_ = State::READY;
797 fetch_after_load_ = false; 906 fetch_after_load_ = false;
798 EnterStateEnabled(fetch_snippets); 907 EnterStateEnabled(fetch_snippets);
799 return; 908 return;
800 } 909 }
801 910
802 case State::DISABLED: 911 case State::DISABLED:
803 DCHECK(state_ == State::NOT_INITED || state_ == State::READY); 912 DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
804 913
805 DVLOG(1) << "Entering state: DISABLED"; 914 DVLOG(1) << "Entering state: DISABLED";
806 state_ = State::DISABLED; 915 state_ = State::DISABLED;
807 EnterStateDisabled(); 916 EnterStateDisabled();
808 return; 917 return;
809 918
810 case State::ERROR_OCCURRED: 919 case State::ERROR_OCCURRED:
811 DVLOG(1) << "Entering state: ERROR_OCCURRED"; 920 DVLOG(1) << "Entering state: ERROR_OCCURRED";
812 state_ = State::ERROR_OCCURRED; 921 state_ = State::ERROR_OCCURRED;
813 EnterStateError(); 922 EnterStateError();
814 return; 923 return;
815 } 924 }
816 } 925 }
817 926
818 void NTPSnippetsService::NotifyNewSuggestions() { 927 void NTPSnippetsService::NotifyNewSuggestions() {
819 std::vector<ContentSuggestion> result; 928 for (auto& item : categories_) {
820 for (const std::unique_ptr<NTPSnippet>& snippet : snippets_) { 929 Category category_id = item.first;
821 if (!snippet->is_complete()) 930 CategoryContent& category = item.second;
822 continue; 931
823 ContentSuggestion suggestion( 932 std::vector<ContentSuggestion> result;
824 MakeUniqueID(provided_category_, snippet->id()), 933 for (const std::unique_ptr<NTPSnippet>& snippet : category.snippets) {
825 snippet->best_source().url); 934 // TODO(sfiera): if a snippet is not going to be displayed, move it
826 suggestion.set_amp_url(snippet->best_source().amp_url); 935 // directly to category.dismissed on fetch. Otherwise, we might prune
827 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 936 // other snippets to get down to kMaxSnippetCount, only to hide one of the
828 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 937 // incomplete ones we kept.
829 suggestion.set_publish_date(snippet->publish_date()); 938 if (!snippet->is_complete())
830 suggestion.set_publisher_name( 939 continue;
831 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 940 ContentSuggestion suggestion(MakeUniqueID(category_id, snippet->id()),
832 suggestion.set_score(snippet->score()); 941 snippet->best_source().url);
833 result.emplace_back(std::move(suggestion)); 942 suggestion.set_amp_url(snippet->best_source().amp_url);
943 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
944 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
945 suggestion.set_publish_date(snippet->publish_date());
946 suggestion.set_publisher_name(
947 base::UTF8ToUTF16(snippet->best_source().publisher_name));
948 suggestion.set_score(snippet->score());
949 result.emplace_back(std::move(suggestion));
950 }
951 // TODO(sfiera): detect when a category is unchanged and don't notify the
952 // observer in that case.
953 observer()->OnNewSuggestions(this, category_id, std::move(result));
834 } 954 }
835 observer()->OnNewSuggestions(this, provided_category_, std::move(result));
836 } 955 }
837 956
838 void NTPSnippetsService::UpdateCategoryStatus(CategoryStatus status) { 957 void NTPSnippetsService::UpdateCategoryStatus(Category category_id,
839 if (status == category_status_) 958 CategoryStatus status) {
959 DCHECK(categories_.find(category_id) != categories_.end());
960 CategoryContent& category = categories_[category_id];
961 if (status == category.status)
840 return; 962 return;
841 963
842 category_status_ = status; 964 category.status = status;
843 observer()->OnCategoryStatusChanged(this, provided_category_, 965 observer()->OnCategoryStatusChanged(this, category_id, category.status);
844 category_status_); 966 }
967
968 void NTPSnippetsService::UpdateAllCategoryStatus(CategoryStatus status) {
969 for (const auto& category : categories_) {
970 UpdateCategoryStatus(category.first, status);
971 }
845 } 972 }
846 973
847 } // namespace ntp_snippets 974 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698