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

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: Rebase. 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);
537 NotifyNewSuggestions(); 603 NotifyNewSuggestions();
538 } 604 }
539 605
540 void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { 606 void NTPSnippetsService::MergeSnippets(Category category,
607 NTPSnippet::PtrVector new_snippets) {
541 DCHECK(ready()); 608 DCHECK(ready());
609 CategoryContent* content = &categories_[category];
542 610
543 // Remove new snippets that we already have, or that have been dismissed. 611 // Remove new snippets that we already have, or that have been dismissed.
544 std::set<std::string> old_snippet_ids; 612 std::set<std::string> old_snippet_ids;
545 InsertAllIDs(dismissed_snippets_, &old_snippet_ids); 613 InsertAllIDs(content->dismissed, &old_snippet_ids);
546 InsertAllIDs(snippets_, &old_snippet_ids); 614 InsertAllIDs(content->snippets, &old_snippet_ids);
547 new_snippets.erase( 615 new_snippets.erase(
548 std::remove_if( 616 std::remove_if(
549 new_snippets.begin(), new_snippets.end(), 617 new_snippets.begin(), new_snippets.end(),
550 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { 618 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) {
551 if (old_snippet_ids.count(snippet->id())) 619 if (old_snippet_ids.count(snippet->id()))
552 return true; 620 return true;
553 for (const SnippetSource& source : snippet->sources()) { 621 for (const SnippetSource& source : snippet->sources()) {
554 if (old_snippet_ids.count(source.url.spec())) 622 if (old_snippet_ids.count(source.url.spec()))
555 return true; 623 return true;
556 } 624 }
(...skipping 27 matching lines...) Expand all
584 new_snippets.end()); 652 new_snippets.end());
585 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); 653 int num_snippets_dismissed = num_new_snippets - new_snippets.size();
586 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", 654 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
587 num_snippets_dismissed > 0); 655 num_snippets_dismissed > 0);
588 if (num_snippets_dismissed > 0) { 656 if (num_snippets_dismissed > 0) {
589 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", 657 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
590 num_snippets_dismissed); 658 num_snippets_dismissed);
591 } 659 }
592 } 660 }
593 661
594 // Save the new snippets to the DB. 662 // Save new articles to the DB.
595 database_->SaveSnippets(new_snippets); 663 // TODO(sfiera): save non-articles to DB too.
664 if (category == articles_category_)
665 database_->SaveSnippets(new_snippets);
596 666
597 // Insert the new snippets at the front. 667 // Insert the new snippets at the front.
598 snippets_.insert(snippets_.begin(), 668 content->snippets.insert(content->snippets.begin(),
599 std::make_move_iterator(new_snippets.begin()), 669 std::make_move_iterator(new_snippets.begin()),
600 std::make_move_iterator(new_snippets.end())); 670 std::make_move_iterator(new_snippets.end()));
601 } 671 }
602 672
603 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { 673 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const {
604 std::set<std::string> hosts; 674 std::set<std::string> hosts;
605 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); 675 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts);
606 for (const auto& value : *list) { 676 for (const auto& value : *list) {
607 std::string str; 677 std::string str;
608 bool success = value->GetAsString(&str); 678 bool success = value->GetAsString(&str);
609 DCHECK(success) << "Failed to parse snippet host from prefs"; 679 DCHECK(success) << "Failed to parse snippet host from prefs";
610 hosts.insert(std::move(str)); 680 hosts.insert(std::move(str));
611 } 681 }
612 return hosts; 682 return hosts;
613 } 683 }
614 684
615 void NTPSnippetsService::StoreSnippetHostsToPrefs( 685 void NTPSnippetsService::StoreSnippetHostsToPrefs(
616 const std::set<std::string>& hosts) { 686 const std::set<std::string>& hosts) {
617 base::ListValue list; 687 base::ListValue list;
618 for (const std::string& host : hosts) 688 for (const std::string& host : hosts)
619 list.AppendString(host); 689 list.AppendString(host);
620 pref_service_->Set(prefs::kSnippetHosts, list); 690 pref_service_->Set(prefs::kSnippetHosts, list);
621 } 691 }
622 692
623 void NTPSnippetsService::ClearExpiredSnippets() { 693 void NTPSnippetsService::ClearExpiredSnippets() {
624 base::Time expiry = base::Time::Now(); 694 std::vector<Category> categories_to_erase;
625 695
626 // Move expired snippets over into |to_delete|. 696 const base::Time expiry = base::Time::Now();
627 NTPSnippet::PtrVector to_delete; 697 base::Time next_expiry = base::Time::Max();
628 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 698
629 if (snippet->expiry_date() <= expiry) 699 for (auto& item : categories_) {
630 to_delete.emplace_back(std::move(snippet)); 700 Category category = item.first;
701 CategoryContent* content = &item.second;
702
703 // Move expired snippets over into |to_delete|.
704 NTPSnippet::PtrVector to_delete;
705 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) {
706 if (snippet->expiry_date() <= expiry)
707 to_delete.emplace_back(std::move(snippet));
708 }
709 Compact(&content->snippets);
710
711 // Move expired dismissed snippets over into |to_delete| as well.
712 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) {
713 if (snippet->expiry_date() <= expiry)
714 to_delete.emplace_back(std::move(snippet));
715 }
716 Compact(&content->dismissed);
717
718 // Finally, actually delete the removed snippets from the DB.
719 if (category == articles_category_)
720 database_->DeleteSnippets(to_delete);
721
722 if (content->snippets.empty() && content->dismissed.empty()) {
723 if ((category != articles_category_) && !content->provided_by_server)
724 categories_to_erase.push_back(category);
725 continue;
726 }
727
728 for (const auto& snippet : content->snippets) {
729 if (snippet->expiry_date() < next_expiry)
730 next_expiry = snippet->expiry_date();
731 }
732 for (const auto& snippet : content->dismissed) {
733 if (snippet->expiry_date() < next_expiry)
734 next_expiry = snippet->expiry_date();
735 }
631 } 736 }
632 Compact(&snippets_);
633 737
634 // Move expired dismissed snippets over into |to_delete| as well. 738 for (Category category : categories_to_erase) {
635 for (std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 739 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
636 if (snippet->expiry_date() <= expiry) 740 categories_.erase(category);
637 to_delete.emplace_back(std::move(snippet));
638 } 741 }
639 Compact(&dismissed_snippets_);
640 742
641 // Finally, actually delete the removed snippets from the DB. 743 // Unless there are no snippets left, schedule a timer for the next expiry.
642 database_->DeleteSnippets(to_delete); 744 DCHECK_GT(next_expiry, expiry);
643 745 if (next_expiry < base::Time::Max()) {
644 // If there are any snippets left, schedule a timer for the next expiry. 746 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
645 if (snippets_.empty() && dismissed_snippets_.empty()) 747 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
646 return; 748 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 } 749 }
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 } 750 }
662 751
663 void NTPSnippetsService::NukeAllSnippets() { 752 void NTPSnippetsService::NukeAllSnippets() {
664 ClearCachedSuggestions(provided_category_); 753 std::vector<Category> categories_to_erase;
665 ClearDismissedSuggestionsForDebugging(provided_category_);
666 754
667 // Temporarily enter an "explicitly disabled" state, so that any open UIs 755 // Empty the ARTICLES category and remove all others, since they may or may
668 // will clear the suggestions too. 756 // not be personalized.
669 if (category_status_ != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) { 757 for (const auto& item : categories_) {
670 CategoryStatus old_category_status = category_status_; 758 Category category = item.first;
671 UpdateCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 759
672 UpdateCategoryStatus(old_category_status); 760 ClearCachedSuggestions(category);
761 ClearDismissedSuggestionsForDebugging(category);
762
763 if (category == articles_category_) {
764 // Temporarily enter an "explicitly disabled" state, so that any open UIs
765 // will clear the suggestions too.
766 CategoryContent& content = categories_[category];
767 if (content.status != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) {
768 CategoryStatus old_category_status = content.status;
769 UpdateCategoryStatus(category,
770 CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
771 UpdateCategoryStatus(category, old_category_status);
772 }
773 } else {
774 // Remove other categories entirely; they may or may not reappear.
775 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
776 categories_to_erase.push_back(category);
777 }
778 }
779
780 for (Category category : categories_to_erase) {
781 categories_.erase(category);
673 } 782 }
674 } 783 }
675 784
676 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( 785 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase(
677 const ImageFetchedCallback& callback, 786 const ImageFetchedCallback& callback,
678 const std::string& snippet_id, 787 const std::string& suggestion_id,
679 std::string data) { 788 std::string data) {
680 // |image_decoder_| is null in tests. 789 // |image_decoder_| is null in tests.
681 if (image_decoder_ && !data.empty()) { 790 if (image_decoder_ && !data.empty()) {
682 image_decoder_->DecodeImage( 791 image_decoder_->DecodeImage(
683 std::move(data), 792 std::move(data),
684 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, 793 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase,
685 base::Unretained(this), callback, snippet_id)); 794 base::Unretained(this), callback, suggestion_id));
686 return; 795 return;
687 } 796 }
688 797
689 // Fetching from the DB failed; start a network fetch. 798 // Fetching from the DB failed; start a network fetch.
690 FetchSnippetImageFromNetwork(snippet_id, callback); 799 FetchSnippetImageFromNetwork(suggestion_id, callback);
691 } 800 }
692 801
693 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase( 802 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase(
694 const ImageFetchedCallback& callback, 803 const ImageFetchedCallback& callback,
695 const std::string& snippet_id, 804 const std::string& suggestion_id,
696 const gfx::Image& image) { 805 const gfx::Image& image) {
697 if (!image.IsEmpty()) { 806 if (!image.IsEmpty()) {
698 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 807 callback.Run(suggestion_id, image);
699 return; 808 return;
700 } 809 }
701 810
702 // If decoding the image failed, delete the DB entry. 811 // If decoding the image failed, delete the DB entry.
812 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
703 database_->DeleteImage(snippet_id); 813 database_->DeleteImage(snippet_id);
704 814
705 FetchSnippetImageFromNetwork(snippet_id, callback); 815 FetchSnippetImageFromNetwork(suggestion_id, callback);
706 } 816 }
707 817
708 void NTPSnippetsService::FetchSnippetImageFromNetwork( 818 void NTPSnippetsService::FetchSnippetImageFromNetwork(
709 const std::string& snippet_id, 819 const std::string& suggestion_id,
710 const ImageFetchedCallback& callback) { 820 const ImageFetchedCallback& callback) {
821 Category category = GetCategoryFromUniqueID(suggestion_id);
822 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
823
824 auto category_it = categories_.find(category);
825 if (category_it == categories_.end()) {
826 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
827 return;
828 }
829
830 const CategoryContent& content = category_it->second;
711 auto it = 831 auto it =
712 std::find_if(snippets_.begin(), snippets_.end(), 832 std::find_if(content.snippets.begin(), content.snippets.end(),
713 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 833 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
714 return snippet->id() == snippet_id; 834 return snippet->id() == snippet_id;
715 }); 835 });
716 836
717 if (it == snippets_.end() || 837 if (it == content.snippets.end() ||
718 !thumbnail_requests_throttler_.DemandQuotaForRequest( 838 !thumbnail_requests_throttler_.DemandQuotaForRequest(
719 /*interactive_request=*/true)) { 839 /*interactive_request=*/true)) {
720 // Return an empty image. Directly, this is never synchronous with the 840 // Return an empty image. Directly, this is never synchronous with the
721 // original FetchSuggestionImage() call - an asynchronous database query has 841 // original FetchSuggestionImage() call - an asynchronous database query has
722 // happened in the meantime. 842 // happened in the meantime.
723 OnSnippetImageDecodedFromNetwork(callback, snippet_id, gfx::Image()); 843 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
724 return; 844 return;
725 } 845 }
726 846
727 const NTPSnippet& snippet = *it->get(); 847 const NTPSnippet& snippet = *it->get();
728 848
729 // TODO(jkrcal): We probably should rename OnImageDataFetched() to 849 // TODO(jkrcal): We probably should rename OnImageDataFetched() to
730 // CacheImageData(). This would document that this is actually independent 850 // CacheImageData(). This would document that this is actually independent
731 // from the individual fetch-flow. 851 // from the individual fetch-flow.
732 // The image fetcher calls OnImageDataFetched() with the raw data (this object 852 // The image fetcher calls OnImageDataFetched() with the raw data (this object
733 // is an ImageFetcherDelegate) and then also 853 // is an ImageFetcherDelegate) and then also
734 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. 854 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded.
735 image_fetcher_->StartOrQueueNetworkRequest( 855 image_fetcher_->StartOrQueueNetworkRequest(
736 snippet.id(), snippet.salient_image_url(), 856 suggestion_id, snippet.salient_image_url(),
737 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, 857 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork,
738 base::Unretained(this), callback)); 858 base::Unretained(this), callback));
739 } 859 }
740 860
741 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( 861 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork(
742 const ImageFetchedCallback& callback, 862 const ImageFetchedCallback& callback,
743 const std::string& snippet_id, 863 const std::string& suggestion_id,
744 const gfx::Image& image) { 864 const gfx::Image& image) {
745 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 865 callback.Run(suggestion_id, image);
746 } 866 }
747 867
748 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { 868 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) {
749 if (fetch_snippets) 869 if (fetch_snippets)
750 FetchSnippets(/*force_request=*/false); 870 FetchSnippets(/*force_request=*/false);
751 871
752 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant, 872 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant,
753 // otherwise we transition to |AVAILABLE| here. 873 // otherwise we transition to |AVAILABLE| here.
754 if (category_status_ != CategoryStatus::AVAILABLE_LOADING) 874 if (categories_[articles_category_].status !=
755 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 875 CategoryStatus::AVAILABLE_LOADING) {
876 UpdateCategoryStatus(articles_category_, CategoryStatus::AVAILABLE);
877 }
756 878
757 // If host restrictions are enabled, register for host list updates. 879 // If host restrictions are enabled, register for host list updates.
758 // |suggestions_service_| can be null in tests. 880 // |suggestions_service_| can be null in tests.
759 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { 881 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) {
760 suggestions_service_subscription_ = 882 suggestions_service_subscription_ =
761 suggestions_service_->AddCallback(base::Bind( 883 suggestions_service_->AddCallback(base::Bind(
762 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); 884 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this)));
763 } 885 }
764 886
765 RescheduleFetching(); 887 RescheduleFetching();
766 } 888 }
767 889
768 void NTPSnippetsService::EnterStateDisabled() { 890 void NTPSnippetsService::EnterStateDisabled() {
769 ClearCachedSuggestions(provided_category_); 891 std::vector<Category> categories_to_erase;
770 ClearDismissedSuggestionsForDebugging(provided_category_); 892
893 // Empty the ARTICLES category and remove all others, since they may or may
894 // not be personalized.
895 for (const auto& item : categories_) {
896 Category category = item.first;
897 ClearCachedSuggestions(category);
898 ClearDismissedSuggestionsForDebugging(category);
899 if (category != articles_category_) {
900 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
901 categories_to_erase.push_back(category);
902 }
903 }
904
905 for (Category category : categories_to_erase) {
906 categories_.erase(category);
907 }
771 908
772 expiry_timer_.Stop(); 909 expiry_timer_.Stop();
773 suggestions_service_subscription_.reset(); 910 suggestions_service_subscription_.reset();
774 RescheduleFetching(); 911 RescheduleFetching();
775 } 912 }
776 913
777 void NTPSnippetsService::EnterStateError() { 914 void NTPSnippetsService::EnterStateError() {
778 expiry_timer_.Stop(); 915 expiry_timer_.Stop();
779 suggestions_service_subscription_.reset(); 916 suggestions_service_subscription_.reset();
780 RescheduleFetching(); 917 RescheduleFetching();
(...skipping 24 matching lines...) Expand all
805 // Always notify here even if we got nothing from the database, because we 942 // 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. 943 // don't know how long the fetch will take or if it will even complete.
807 NotifyNewSuggestions(); 944 NotifyNewSuggestions();
808 } 945 }
809 946
810 void NTPSnippetsService::OnDisabledReasonChanged( 947 void NTPSnippetsService::OnDisabledReasonChanged(
811 DisabledReason disabled_reason) { 948 DisabledReason disabled_reason) {
812 switch (disabled_reason) { 949 switch (disabled_reason) {
813 case DisabledReason::NONE: 950 case DisabledReason::NONE:
814 // Do not change the status. That will be done in EnterStateEnabled() 951 // Do not change the status. That will be done in EnterStateEnabled()
815 EnterState(State::READY, category_status_); 952 EnterState(State::READY);
816 break; 953 break;
817 954
818 case DisabledReason::EXPLICITLY_DISABLED: 955 case DisabledReason::EXPLICITLY_DISABLED:
819 EnterState(State::DISABLED, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 956 EnterState(State::DISABLED);
957 UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
820 break; 958 break;
821 959
822 case DisabledReason::SIGNED_OUT: 960 case DisabledReason::SIGNED_OUT:
823 EnterState(State::DISABLED, CategoryStatus::SIGNED_OUT); 961 EnterState(State::DISABLED);
962 UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
824 break; 963 break;
825 } 964 }
826 } 965 }
827 966
828 void NTPSnippetsService::EnterState(State state, CategoryStatus status) { 967 void NTPSnippetsService::EnterState(State state) {
829 UpdateCategoryStatus(status);
830
831 if (state == state_) 968 if (state == state_)
832 return; 969 return;
833 970
834 switch (state) { 971 switch (state) {
835 case State::NOT_INITED: 972 case State::NOT_INITED:
836 // Initial state, it should not be possible to get back there. 973 // Initial state, it should not be possible to get back there.
837 NOTREACHED(); 974 NOTREACHED();
838 return; 975 return;
839 976
840 case State::READY: { 977 case State::READY: {
841 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); 978 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
842 979
843 bool fetch_snippets = snippets_.empty() || fetch_after_load_; 980 bool fetch_snippets =
981 categories_[articles_category_].snippets.empty() || fetch_after_load_;
844 DVLOG(1) << "Entering state: READY"; 982 DVLOG(1) << "Entering state: READY";
845 state_ = State::READY; 983 state_ = State::READY;
846 fetch_after_load_ = false; 984 fetch_after_load_ = false;
847 EnterStateEnabled(fetch_snippets); 985 EnterStateEnabled(fetch_snippets);
848 return; 986 return;
849 } 987 }
850 988
851 case State::DISABLED: 989 case State::DISABLED:
852 DCHECK(state_ == State::NOT_INITED || state_ == State::READY); 990 DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
853 991
854 DVLOG(1) << "Entering state: DISABLED"; 992 DVLOG(1) << "Entering state: DISABLED";
855 state_ = State::DISABLED; 993 state_ = State::DISABLED;
856 EnterStateDisabled(); 994 EnterStateDisabled();
857 return; 995 return;
858 996
859 case State::ERROR_OCCURRED: 997 case State::ERROR_OCCURRED:
860 DVLOG(1) << "Entering state: ERROR_OCCURRED"; 998 DVLOG(1) << "Entering state: ERROR_OCCURRED";
861 state_ = State::ERROR_OCCURRED; 999 state_ = State::ERROR_OCCURRED;
862 EnterStateError(); 1000 EnterStateError();
863 return; 1001 return;
864 } 1002 }
865 } 1003 }
866 1004
867 void NTPSnippetsService::NotifyNewSuggestions() { 1005 void NTPSnippetsService::NotifyNewSuggestions() {
868 std::vector<ContentSuggestion> result; 1006 for (const auto& item : categories_) {
869 for (const std::unique_ptr<NTPSnippet>& snippet : snippets_) { 1007 Category category = item.first;
870 if (!snippet->is_complete()) 1008 const CategoryContent& content = item.second;
871 continue; 1009
872 ContentSuggestion suggestion( 1010 std::vector<ContentSuggestion> result;
873 MakeUniqueID(provided_category_, snippet->id()), 1011 for (const std::unique_ptr<NTPSnippet>& snippet : content.snippets) {
874 snippet->best_source().url); 1012 // TODO(sfiera): if a snippet is not going to be displayed, move it
875 suggestion.set_amp_url(snippet->best_source().amp_url); 1013 // directly to content.dismissed on fetch. Otherwise, we might prune
876 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 1014 // other snippets to get down to kMaxSnippetCount, only to hide one of the
877 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 1015 // incomplete ones we kept.
878 suggestion.set_publish_date(snippet->publish_date()); 1016 if (!snippet->is_complete())
879 suggestion.set_publisher_name( 1017 continue;
880 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 1018 ContentSuggestion suggestion(MakeUniqueID(category, snippet->id()),
881 suggestion.set_score(snippet->score()); 1019 snippet->best_source().url);
882 result.emplace_back(std::move(suggestion)); 1020 suggestion.set_amp_url(snippet->best_source().amp_url);
1021 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
1022 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
1023 suggestion.set_publish_date(snippet->publish_date());
1024 suggestion.set_publisher_name(
1025 base::UTF8ToUTF16(snippet->best_source().publisher_name));
1026 suggestion.set_score(snippet->score());
1027 result.emplace_back(std::move(suggestion));
1028 }
1029
1030 DVLOG(1) << "NotifyNewSuggestions(): " << result.size()
1031 << " items in category " << category;
1032 // TODO(sfiera): detect when a category is unchanged and don't notify the
1033 // observer in that case.
1034 observer()->OnNewSuggestions(this, category, std::move(result));
883 } 1035 }
884 observer()->OnNewSuggestions(this, provided_category_, std::move(result));
885 } 1036 }
886 1037
887 void NTPSnippetsService::UpdateCategoryStatus(CategoryStatus status) { 1038 void NTPSnippetsService::UpdateCategoryStatus(Category category,
888 if (status == category_status_) 1039 CategoryStatus status) {
1040 DCHECK(categories_.find(category) != categories_.end());
1041 CategoryContent& content = categories_[category];
1042 if (status == content.status)
889 return; 1043 return;
890 1044
891 category_status_ = status; 1045 DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": "
892 observer()->OnCategoryStatusChanged(this, provided_category_, 1046 << static_cast<int>(content.status) << " -> "
893 category_status_); 1047 << static_cast<int>(status);
1048 content.status = status;
1049 observer()->OnCategoryStatusChanged(this, category, content.status);
1050 }
1051
1052 void NTPSnippetsService::UpdateAllCategoryStatus(CategoryStatus status) {
1053 for (const auto& category : categories_) {
1054 UpdateCategoryStatus(category.first, status);
1055 }
894 } 1056 }
895 1057
896 } // namespace ntp_snippets 1058 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698