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

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

Powered by Google App Engine
This is Rietveld 408576698