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

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: Empty category handling. 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((*it)->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);
327 database_->LoadImage( 340 database_->LoadImage(
328 snippet_id, 341 suggestion_id,
329 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase, 342 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase,
330 base::Unretained(this), callback, snippet_id)); 343 base::Unretained(this), callback, suggestion_id));
331 } 344 }
332 345
333 void NTPSnippetsService::ClearCachedSuggestionsForDebugging(Category category) { 346 void NTPSnippetsService::ClearCachedSuggestionsForDebugging(Category category) {
334 DCHECK_EQ(category, provided_category_);
335 if (!initialized()) 347 if (!initialized())
336 return; 348 return;
337 349
338 if (snippets_.empty()) 350 if (categories_.find(category) == categories_.end())
351 return;
352 CategoryContent* content = &categories_[category];
353 if (content->snippets.empty())
339 return; 354 return;
340 355
341 database_->DeleteSnippets(snippets_); 356 if (category == articles_category_)
342 snippets_.clear(); 357 database_->DeleteSnippets(content->snippets);
358 content->snippets.clear();
343 359
344 NotifyNewSuggestions(); 360 NotifyNewSuggestions();
345 } 361 }
346 362
347 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( 363 void NTPSnippetsService::GetDismissedSuggestionsForDebugging(
348 Category category, 364 Category category,
349 const DismissedSuggestionsCallback& callback) { 365 const DismissedSuggestionsCallback& callback) {
350 DCHECK_EQ(category, provided_category_); 366 DCHECK(categories_.find(category) != categories_.end());
367
351 std::vector<ContentSuggestion> result; 368 std::vector<ContentSuggestion> result;
352 for (const std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 369 const CategoryContent& content = categories_[category];
370 for (const std::unique_ptr<NTPSnippet>& snippet : content.dismissed) {
353 if (!snippet->is_complete()) 371 if (!snippet->is_complete())
354 continue; 372 continue;
355 ContentSuggestion suggestion( 373 ContentSuggestion suggestion(MakeUniqueID(category, snippet->id()),
356 MakeUniqueID(provided_category_, snippet->id()), 374 snippet->best_source().url);
357 snippet->best_source().url);
358 suggestion.set_amp_url(snippet->best_source().amp_url); 375 suggestion.set_amp_url(snippet->best_source().amp_url);
359 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 376 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
360 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 377 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
361 suggestion.set_publish_date(snippet->publish_date()); 378 suggestion.set_publish_date(snippet->publish_date());
362 suggestion.set_publisher_name( 379 suggestion.set_publisher_name(
363 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 380 base::UTF8ToUTF16(snippet->best_source().publisher_name));
364 suggestion.set_score(snippet->score()); 381 suggestion.set_score(snippet->score());
365 result.emplace_back(std::move(suggestion)); 382 result.emplace_back(std::move(suggestion));
366 } 383 }
367 callback.Run(std::move(result)); 384 callback.Run(std::move(result));
368 } 385 }
369 386
370 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( 387 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging(
371 Category category) { 388 Category category) {
372 DCHECK_EQ(category, provided_category_); 389 DCHECK(categories_.find(category) != categories_.end());
390
373 if (!initialized()) 391 if (!initialized())
374 return; 392 return;
375 393
376 if (dismissed_snippets_.empty()) 394 CategoryContent* content = &categories_[category];
395 if (content->dismissed.empty())
377 return; 396 return;
378 397
379 database_->DeleteSnippets(dismissed_snippets_); 398 if (category == articles_category_)
380 dismissed_snippets_.clear(); 399 database_->DeleteSnippets(content->dismissed);
400 content->dismissed.clear();
381 } 401 }
382 402
383 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { 403 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const {
384 // |suggestions_service_| can be null in tests. 404 // |suggestions_service_| can be null in tests.
385 if (!suggestions_service_) 405 if (!suggestions_service_)
386 return std::set<std::string>(); 406 return std::set<std::string>();
387 407
388 // TODO(treib): This should just call GetSnippetHostsFromPrefs. 408 // TODO(treib): This should just call GetSnippetHostsFromPrefs.
389 return GetSuggestionsHostsImpl( 409 return GetSuggestionsHostsImpl(
390 suggestions_service_->GetSuggestionsDataFromCache()); 410 suggestions_service_->GetSuggestionsDataFromCache());
(...skipping 24 matching lines...) Expand all
415 else 435 else
416 NukeAllSnippets(); 436 NukeAllSnippets();
417 } 437 }
418 438
419 void NTPSnippetsService::HistoryServiceBeingDeleted( 439 void NTPSnippetsService::HistoryServiceBeingDeleted(
420 history::HistoryService* history_service) { 440 history::HistoryService* history_service) {
421 history_service_observer_.RemoveAll(); 441 history_service_observer_.RemoveAll();
422 } 442 }
423 443
424 // image_fetcher::ImageFetcherDelegate implementation. 444 // image_fetcher::ImageFetcherDelegate implementation.
425 void NTPSnippetsService::OnImageDataFetched(const std::string& snippet_id, 445 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id,
426 const std::string& image_data) { 446 const std::string& image_data) {
427 if (image_data.empty()) 447 if (image_data.empty())
428 return; 448 return;
429 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
430 // Only save the image if the corresponding snippet still exists. 459 // Only save the image if the corresponding snippet still exists.
431 auto it = 460 auto it =
432 std::find_if(snippets_.begin(), snippets_.end(), 461 std::find_if(content.snippets.begin(), content.snippets.end(),
433 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 462 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
434 return snippet->id() == snippet_id; 463 return snippet->id() == snippet_id;
435 }); 464 });
436 if (it == snippets_.end()) 465 if (it == content.snippets.end())
437 return; 466 return;
438 467
439 database_->SaveImage(snippet_id, image_data); 468 database_->SaveImage(suggestion_id, image_data);
440 } 469 }
441 470
442 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { 471 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) {
443 if (state_ == State::ERROR_OCCURRED) 472 if (state_ == State::ERROR_OCCURRED)
444 return; 473 return;
445 DCHECK(state_ == State::NOT_INITED); 474 DCHECK(state_ == State::NOT_INITED);
446 DCHECK(snippets_.empty()); 475 DCHECK(categories_.size() == 1); // Only articles category, so far.
447 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_];
448 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { 480 for (std::unique_ptr<NTPSnippet>& snippet : snippets) {
449 if (snippet->is_dismissed()) 481 if (snippet->is_dismissed())
450 dismissed_snippets_.emplace_back(std::move(snippet)); 482 content->dismissed.emplace_back(std::move(snippet));
451 else 483 else
452 snippets_.emplace_back(std::move(snippet)); 484 content->snippets.emplace_back(std::move(snippet));
453 } 485 }
454 std::sort(snippets_.begin(), snippets_.end(), 486
487 std::sort(content->snippets.begin(), content->snippets.end(),
455 [](const std::unique_ptr<NTPSnippet>& lhs, 488 [](const std::unique_ptr<NTPSnippet>& lhs,
456 const std::unique_ptr<NTPSnippet>& rhs) { 489 const std::unique_ptr<NTPSnippet>& rhs) {
457 return lhs->score() > rhs->score(); 490 return lhs->score() > rhs->score();
458 }); 491 });
459 492
460 ClearExpiredSnippets(); 493 ClearExpiredSnippets();
461 FinishInitialization(); 494 FinishInitialization();
462 } 495 }
463 496
464 void NTPSnippetsService::OnDatabaseError() { 497 void NTPSnippetsService::OnDatabaseError() {
465 EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); 498 EnterState(State::ERROR_OCCURRED);
499 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
466 } 500 }
467 501
468 // TODO(dgn): name clash between content suggestions and suggestions hosts. 502 // TODO(dgn): name clash between content suggestions and suggestions hosts.
469 // method name should be changed. 503 // method name should be changed.
470 void NTPSnippetsService::OnSuggestionsChanged( 504 void NTPSnippetsService::OnSuggestionsChanged(
471 const SuggestionsProfile& suggestions) { 505 const SuggestionsProfile& suggestions) {
472 DCHECK(initialized()); 506 DCHECK(initialized());
473 507
474 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions); 508 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions);
475 if (hosts == GetSnippetHostsFromPrefs()) 509 if (hosts == GetSnippetHostsFromPrefs())
476 return; 510 return;
477 511
478 // Remove existing snippets that aren't in the suggestions anymore. 512 // Remove existing snippets that aren't in the suggestions anymore.
513 //
479 // 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,
480 // 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 //
481 // First, move them over into |to_delete|. 520 // First, move them over into |to_delete|.
521 CategoryContent* content = &categories_[articles_category_];
482 NTPSnippet::PtrVector to_delete; 522 NTPSnippet::PtrVector to_delete;
483 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 523 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) {
484 if (!hosts.count(snippet->best_source().url.host())) 524 if (!hosts.count(snippet->best_source().url.host()))
485 to_delete.emplace_back(std::move(snippet)); 525 to_delete.emplace_back(std::move(snippet));
486 } 526 }
487 Compact(&snippets_); 527 Compact(&content->snippets);
488 // Then delete the removed snippets from the database. 528 // Then delete the removed snippets from the database.
489 database_->DeleteSnippets(to_delete); 529 database_->DeleteSnippets(to_delete);
490 530
491 StoreSnippetHostsToPrefs(hosts); 531 StoreSnippetHostsToPrefs(hosts);
492 532
493 // 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.
494 // 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
495 // for its callback. 535 // for its callback.
496 NotifyNewSuggestions(); 536 NotifyNewSuggestions();
497 537
498 FetchSnippetsFromHosts(hosts, /*force_request=*/false); 538 FetchSnippetsFromHosts(hosts, /*force_request=*/false);
499 } 539 }
500 540
501 void NTPSnippetsService::OnFetchFinished( 541 void NTPSnippetsService::OnFetchFinished(
502 NTPSnippetsFetcher::OptionalSnippets snippets) { 542 NTPSnippetsFetcher::OptionalSnippets snippets) {
503 if (!ready()) 543 if (!ready())
504 return; 544 return;
505 545
506 DCHECK(category_status_ == CategoryStatus::AVAILABLE || 546 for (auto& item : categories_) {
507 category_status_ == CategoryStatus::AVAILABLE_LOADING); 547 CategoryContent* content = &item.second;
508 548 content->provided_by_server = false;
509 // TODO(sfiera): support more than just the provided_category_ ARTICLES.
510 if (snippets && (snippets->find(provided_category_) != snippets->end())) {
511 // Sparse histogram used because the number of snippets is small (bound by
512 // kMaxSnippetCount).
513 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount));
514 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched",
515 (*snippets)[provided_category_].size());
516 MergeSnippets(std::move((*snippets)[provided_category_]));
517 } 549 }
518 550
551 // If snippets were fetched successfully, update our |categories_| from each
552 // source and report the category as AVAILABLE.
Marc Treib 2016/08/24 15:22:08 What's a "source" here?
sfiera 2016/08/24 18:56:45 Done.
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 // Sparse histogram used because the number of snippets is small (bound by
559 // kMaxSnippetCount).
560 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount));
561 // TODO(sfiera): per-category histograms
Marc Treib 2016/08/24 15:22:08 Since histogram names have to be known at compile
sfiera 2016/08/24 18:56:45 We can also parameterize histograms, right? e.g. t
Marc Treib 2016/08/25 09:10:50 Parameterize: Yes, but again, the parameters have
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.
Marc Treib 2016/08/24 15:22:08 Move this comment right before the "if" below?
sfiera 2016/08/24 18:56:45 Done.
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 database_->DeleteSnippets(to_delete);
Marc Treib 2016/08/24 15:22:08 While only articles are in the DB, we should proba
sfiera 2016/08/24 18:56:45 Done.
579 }
580 }
581 }
582
583 // Trigger expiration. This probably won't expire any current snippets (old
584 // ones should have already been expired by the timer, and new ones shouldn't
585 // have expired yet), but it will update the timer for the next run.
519 ClearExpiredSnippets(); 586 ClearExpiredSnippets();
520 587
521 // If there are more snippets than we want to show, delete the extra ones. 588 for (auto& item : categories_) {
Marc Treib 2016/08/24 15:22:08 const?
sfiera 2016/08/24 18:56:45 Done.
522 if (snippets_.size() > kMaxSnippetCount) { 589 Category category = item.first;
523 NTPSnippet::PtrVector to_delete( 590 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
524 std::make_move_iterator(snippets_.begin() + kMaxSnippetCount),
525 std::make_move_iterator(snippets_.end()));
526 snippets_.resize(kMaxSnippetCount);
527 database_->DeleteSnippets(to_delete);
528 } 591 }
529 592
593 // TODO(sfiera): equivalent metrics for non-articles.
594 const CategoryContent& content = categories_[articles_category_];
530 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", 595 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles",
531 snippets_.size()); 596 content.snippets.size());
532 if (snippets_.empty() && !dismissed_snippets_.empty()) { 597 if (content.snippets.empty() && !content.dismissed.empty()) {
533 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", 598 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded",
534 dismissed_snippets_.size()); 599 content.dismissed.size());
535 } 600 }
536 601
537 UpdateCategoryStatus(CategoryStatus::AVAILABLE);
538 NotifyNewSuggestions(); 602 NotifyNewSuggestions();
539 } 603 }
540 604
541 void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { 605 void NTPSnippetsService::MergeSnippets(Category category,
606 NTPSnippet::PtrVector new_snippets) {
542 DCHECK(ready()); 607 DCHECK(ready());
608 CategoryContent* content = &categories_[category];
543 609
544 // Remove new snippets that we already have, or that have been dismissed. 610 // Remove new snippets that we already have, or that have been dismissed.
545 std::set<std::string> old_snippet_ids; 611 std::set<std::string> old_snippet_ids;
546 InsertAllIDs(dismissed_snippets_, &old_snippet_ids); 612 InsertAllIDs(content->dismissed, &old_snippet_ids);
547 InsertAllIDs(snippets_, &old_snippet_ids); 613 InsertAllIDs(content->snippets, &old_snippet_ids);
548 new_snippets.erase( 614 new_snippets.erase(
549 std::remove_if( 615 std::remove_if(
550 new_snippets.begin(), new_snippets.end(), 616 new_snippets.begin(), new_snippets.end(),
551 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { 617 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) {
552 if (old_snippet_ids.count(snippet->id())) 618 if (old_snippet_ids.count(snippet->id()))
553 return true; 619 return true;
554 for (const SnippetSource& source : snippet->sources()) { 620 for (const SnippetSource& source : snippet->sources()) {
555 if (old_snippet_ids.count(source.url.spec())) 621 if (old_snippet_ids.count(source.url.spec()))
556 return true; 622 return true;
557 } 623 }
(...skipping 27 matching lines...) Expand all
585 new_snippets.end()); 651 new_snippets.end());
586 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); 652 int num_snippets_dismissed = num_new_snippets - new_snippets.size();
587 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", 653 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
588 num_snippets_dismissed > 0); 654 num_snippets_dismissed > 0);
589 if (num_snippets_dismissed > 0) { 655 if (num_snippets_dismissed > 0) {
590 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", 656 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
591 num_snippets_dismissed); 657 num_snippets_dismissed);
592 } 658 }
593 } 659 }
594 660
595 // Save the new snippets to the DB. 661 // Save new articles to the DB.
596 database_->SaveSnippets(new_snippets); 662 // TODO(sfiera): save non-articles to DB too.
663 if (category == articles_category_)
664 database_->SaveSnippets(new_snippets);
597 665
598 // Insert the new snippets at the front. 666 // Insert the new snippets at the front.
599 snippets_.insert(snippets_.begin(), 667 content->snippets.insert(content->snippets.begin(),
600 std::make_move_iterator(new_snippets.begin()), 668 std::make_move_iterator(new_snippets.begin()),
601 std::make_move_iterator(new_snippets.end())); 669 std::make_move_iterator(new_snippets.end()));
602 } 670 }
603 671
604 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { 672 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const {
605 std::set<std::string> hosts; 673 std::set<std::string> hosts;
606 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); 674 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts);
607 for (const auto& value : *list) { 675 for (const auto& value : *list) {
608 std::string str; 676 std::string str;
609 bool success = value->GetAsString(&str); 677 bool success = value->GetAsString(&str);
610 DCHECK(success) << "Failed to parse snippet host from prefs"; 678 DCHECK(success) << "Failed to parse snippet host from prefs";
611 hosts.insert(std::move(str)); 679 hosts.insert(std::move(str));
612 } 680 }
613 return hosts; 681 return hosts;
614 } 682 }
615 683
616 void NTPSnippetsService::StoreSnippetHostsToPrefs( 684 void NTPSnippetsService::StoreSnippetHostsToPrefs(
617 const std::set<std::string>& hosts) { 685 const std::set<std::string>& hosts) {
618 base::ListValue list; 686 base::ListValue list;
619 for (const std::string& host : hosts) 687 for (const std::string& host : hosts)
620 list.AppendString(host); 688 list.AppendString(host);
621 pref_service_->Set(prefs::kSnippetHosts, list); 689 pref_service_->Set(prefs::kSnippetHosts, list);
622 } 690 }
623 691
624 void NTPSnippetsService::ClearExpiredSnippets() { 692 void NTPSnippetsService::ClearExpiredSnippets() {
625 base::Time expiry = base::Time::Now(); 693 std::vector<Category> categories_to_erase;
626 694
627 // Move expired snippets over into |to_delete|. 695 const base::Time expiry = base::Time::Now();
628 NTPSnippet::PtrVector to_delete; 696 base::Time next_expiry = base::Time::Max();
629 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 697
630 if (snippet->expiry_date() <= expiry) 698 for (auto& item : categories_) {
631 to_delete.emplace_back(std::move(snippet)); 699 Category category = item.first;
700 CategoryContent* content = &item.second;
701
702 // Move expired snippets over into |to_delete|.
703 NTPSnippet::PtrVector to_delete;
704 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) {
705 if (snippet->expiry_date() <= expiry)
706 to_delete.emplace_back(std::move(snippet));
707 }
708 Compact(&content->snippets);
709
710 // Move expired dismissed snippets over into |to_delete| as well.
711 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) {
712 if (snippet->expiry_date() <= expiry)
713 to_delete.emplace_back(std::move(snippet));
714 }
715 Compact(&content->dismissed);
716
717 // Finally, actually delete the removed snippets from the DB.
Marc Treib 2016/08/24 15:22:08 Add a TODO to extend this to non-articles?
sfiera 2016/08/24 18:56:45 There's one TODO for putting non-articles in the d
Marc Treib 2016/08/25 09:10:50 Acknowledged.
718 if (category == articles_category_)
719 database_->DeleteSnippets(to_delete);
720
721 if (content->snippets.empty() && content->dismissed.empty()) {
722 if ((category != articles_category_) && !content->provided_by_server)
723 categories_to_erase.push_back(category);
724 continue;
725 }
726
727 for (const auto& snippet : content->snippets) {
728 if (snippet->expiry_date() < next_expiry)
729 next_expiry = snippet->expiry_date();
730 }
731 for (const auto& snippet : content->dismissed) {
732 if (snippet->expiry_date() < next_expiry)
733 next_expiry = snippet->expiry_date();
734 }
632 } 735 }
633 Compact(&snippets_);
634 736
635 // Move expired dismissed snippets over into |to_delete| as well. 737 for (Category category : categories_to_erase) {
636 for (std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 738 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
637 if (snippet->expiry_date() <= expiry) 739 categories_.erase(category);
638 to_delete.emplace_back(std::move(snippet));
639 } 740 }
640 Compact(&dismissed_snippets_);
641 741
642 // Finally, actually delete the removed snippets from the DB. 742 // Unless there are no snippets left, schedule a timer for the next expiry.
643 database_->DeleteSnippets(to_delete); 743 DCHECK_GT(next_expiry, expiry);
644 744 if (next_expiry < base::Time::Max()) {
645 // If there are any snippets left, schedule a timer for the next expiry. 745 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
646 if (snippets_.empty() && dismissed_snippets_.empty()) 746 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
647 return; 747 base::Unretained(this)));
648
649 base::Time next_expiry = base::Time::Max();
650 for (const auto& snippet : snippets_) {
651 if (snippet->expiry_date() < next_expiry)
652 next_expiry = snippet->expiry_date();
653 } 748 }
654 for (const auto& snippet : dismissed_snippets_) {
655 if (snippet->expiry_date() < next_expiry)
656 next_expiry = snippet->expiry_date();
657 }
658 DCHECK_GT(next_expiry, expiry);
659 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
660 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
661 base::Unretained(this)));
662 } 749 }
663 750
664 void NTPSnippetsService::NukeAllSnippets() { 751 void NTPSnippetsService::NukeAllSnippets() {
665 ClearCachedSuggestionsForDebugging(provided_category_); 752 std::vector<Category> categories_to_erase;
666 ClearDismissedSuggestionsForDebugging(provided_category_);
667 753
668 // Temporarily enter an "explicitly disabled" state, so that any open UIs 754 // Empty the ARTICLES category and remove all others, since they may or may
669 // will clear the suggestions too. 755 // not be personalized.
670 if (category_status_ != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) { 756 for (const auto& item : categories_) {
671 CategoryStatus old_category_status = category_status_; 757 Category category = item.first;
672 UpdateCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 758
673 UpdateCategoryStatus(old_category_status); 759 ClearCachedSuggestionsForDebugging(category);
760 ClearDismissedSuggestionsForDebugging(category);
761
762 if (category == articles_category_) {
763 // Temporarily enter an "explicitly disabled" state, so that any open UIs
764 // will clear the suggestions too.
765 CategoryContent& content = categories_[category];
766 if (content.status != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) {
767 CategoryStatus old_category_status = content.status;
768 UpdateCategoryStatus(category,
769 CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
770 UpdateCategoryStatus(category, old_category_status);
771 }
772 } else {
773 // Remove other categories entirely; they may or may not reappear.
774 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
775 categories_to_erase.push_back(category);
776 }
777 }
778
779 for (Category category : categories_to_erase) {
780 categories_.erase(category);
674 } 781 }
675 } 782 }
676 783
677 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( 784 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase(
678 const ImageFetchedCallback& callback, 785 const ImageFetchedCallback& callback,
679 const std::string& snippet_id, 786 const std::string& suggestion_id,
680 std::string data) { 787 std::string data) {
681 // |image_decoder_| is null in tests. 788 // |image_decoder_| is null in tests.
682 if (image_decoder_ && !data.empty()) { 789 if (image_decoder_ && !data.empty()) {
683 image_decoder_->DecodeImage( 790 image_decoder_->DecodeImage(
684 std::move(data), 791 std::move(data),
685 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, 792 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase,
686 base::Unretained(this), callback, snippet_id)); 793 base::Unretained(this), callback, suggestion_id));
687 return; 794 return;
688 } 795 }
689 796
690 // Fetching from the DB failed; start a network fetch. 797 // Fetching from the DB failed; start a network fetch.
691 FetchSnippetImageFromNetwork(snippet_id, callback); 798 FetchSnippetImageFromNetwork(suggestion_id, callback);
692 } 799 }
693 800
694 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase( 801 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase(
695 const ImageFetchedCallback& callback, 802 const ImageFetchedCallback& callback,
696 const std::string& snippet_id, 803 const std::string& suggestion_id,
697 const gfx::Image& image) { 804 const gfx::Image& image) {
698 if (!image.IsEmpty()) { 805 if (!image.IsEmpty()) {
699 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 806 callback.Run(suggestion_id, image);
700 return; 807 return;
701 } 808 }
702 809
703 // If decoding the image failed, delete the DB entry. 810 // If decoding the image failed, delete the DB entry.
704 database_->DeleteImage(snippet_id); 811 database_->DeleteImage(suggestion_id);
705 812
706 FetchSnippetImageFromNetwork(snippet_id, callback); 813 FetchSnippetImageFromNetwork(suggestion_id, callback);
707 } 814 }
708 815
709 void NTPSnippetsService::FetchSnippetImageFromNetwork( 816 void NTPSnippetsService::FetchSnippetImageFromNetwork(
710 const std::string& snippet_id, 817 const std::string& suggestion_id,
711 const ImageFetchedCallback& callback) { 818 const ImageFetchedCallback& callback) {
819 Category category = GetCategoryFromUniqueID(suggestion_id);
820 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
821
822 auto category_it = categories_.find(category);
823 if (category_it == categories_.end()) {
824 callback.Run(suggestion_id, gfx::Image());
Marc Treib 2016/08/24 15:22:08 Call OnSnippetImageDecodedFromNetwork instead, to
sfiera 2016/08/24 18:56:45 Sure, done. (was that function introduced recentl
Marc Treib 2016/08/25 09:10:50 Semi-recently I think - a week or two ago maybe?
825 return;
826 }
827
828 const CategoryContent& content = category_it->second;
712 auto it = 829 auto it =
713 std::find_if(snippets_.begin(), snippets_.end(), 830 std::find_if(content.snippets.begin(), content.snippets.end(),
714 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 831 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
715 return snippet->id() == snippet_id; 832 return snippet->id() == snippet_id;
716 }); 833 });
717 834
718 if (it == snippets_.end() || 835 if (it == content.snippets.end() ||
719 !thumbnail_requests_throttler_.DemandQuotaForRequest( 836 !thumbnail_requests_throttler_.DemandQuotaForRequest(
720 /*interactive_request=*/true)) { 837 /*interactive_request=*/true)) {
721 // Return an empty image. Directly, this is never synchronous with the 838 // Return an empty image. Directly, this is never synchronous with the
722 // original FetchSuggestionImage() call - an asynchronous database query has 839 // original FetchSuggestionImage() call - an asynchronous database query has
723 // happened in the meantime. 840 // happened in the meantime.
724 OnSnippetImageDecodedFromNetwork(callback, snippet_id, gfx::Image()); 841 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
725 return; 842 return;
726 } 843 }
727 844
728 const NTPSnippet& snippet = *it->get(); 845 const NTPSnippet& snippet = *it->get();
729 846
730 // TODO(jkrcal): We probably should rename OnImageDataFetched() to 847 // TODO(jkrcal): We probably should rename OnImageDataFetched() to
731 // CacheImageData(). This would document that this is actually independent 848 // CacheImageData(). This would document that this is actually independent
732 // from the individual fetch-flow. 849 // from the individual fetch-flow.
733 // The image fetcher calls OnImageDataFetched() with the raw data (this object 850 // The image fetcher calls OnImageDataFetched() with the raw data (this object
734 // is an ImageFetcherDelegate) and then also 851 // is an ImageFetcherDelegate) and then also
735 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. 852 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded.
736 image_fetcher_->StartOrQueueNetworkRequest( 853 image_fetcher_->StartOrQueueNetworkRequest(
737 snippet.id(), snippet.salient_image_url(), 854 suggestion_id, snippet.salient_image_url(),
738 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, 855 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork,
739 base::Unretained(this), callback)); 856 base::Unretained(this), callback));
740 } 857 }
741 858
742 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( 859 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork(
743 const ImageFetchedCallback& callback, 860 const ImageFetchedCallback& callback,
744 const std::string& snippet_id, 861 const std::string& suggestion_id,
745 const gfx::Image& image) { 862 const gfx::Image& image) {
746 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 863 callback.Run(suggestion_id, image);
747 } 864 }
748 865
749 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { 866 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) {
750 if (fetch_snippets) 867 if (fetch_snippets)
751 FetchSnippets(/*force_request=*/false); 868 FetchSnippets(/*force_request=*/false);
752 869
753 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant, 870 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant,
754 // otherwise we transition to |AVAILABLE| here. 871 // otherwise we transition to |AVAILABLE| here.
755 if (category_status_ != CategoryStatus::AVAILABLE_LOADING) 872 if (categories_[articles_category_].status !=
756 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 873 CategoryStatus::AVAILABLE_LOADING) {
874 UpdateCategoryStatus(articles_category_, CategoryStatus::AVAILABLE);
875 }
757 876
758 // If host restrictions are enabled, register for host list updates. 877 // If host restrictions are enabled, register for host list updates.
759 // |suggestions_service_| can be null in tests. 878 // |suggestions_service_| can be null in tests.
760 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { 879 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) {
761 suggestions_service_subscription_ = 880 suggestions_service_subscription_ =
762 suggestions_service_->AddCallback(base::Bind( 881 suggestions_service_->AddCallback(base::Bind(
763 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); 882 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this)));
764 } 883 }
765 884
766 RescheduleFetching(); 885 RescheduleFetching();
767 } 886 }
768 887
769 void NTPSnippetsService::EnterStateDisabled() { 888 void NTPSnippetsService::EnterStateDisabled() {
770 ClearCachedSuggestionsForDebugging(provided_category_); 889 std::vector<Category> categories_to_erase;
771 ClearDismissedSuggestionsForDebugging(provided_category_); 890
891 // Empty the ARTICLES category and remove all others, since they may or may
892 // not be personalized.
893 for (const auto& item : categories_) {
894 Category category = item.first;
895 ClearCachedSuggestionsForDebugging(category);
896 ClearDismissedSuggestionsForDebugging(category);
897 if (category != articles_category_) {
898 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
899 categories_to_erase.push_back(category);
900 }
901 }
902
903 for (Category category : categories_to_erase) {
904 categories_.erase(category);
905 }
772 906
773 expiry_timer_.Stop(); 907 expiry_timer_.Stop();
774 suggestions_service_subscription_.reset(); 908 suggestions_service_subscription_.reset();
775 RescheduleFetching(); 909 RescheduleFetching();
776 } 910 }
777 911
778 void NTPSnippetsService::EnterStateError() { 912 void NTPSnippetsService::EnterStateError() {
779 expiry_timer_.Stop(); 913 expiry_timer_.Stop();
780 suggestions_service_subscription_.reset(); 914 suggestions_service_subscription_.reset();
781 RescheduleFetching(); 915 RescheduleFetching();
(...skipping 24 matching lines...) Expand all
806 // Always notify here even if we got nothing from the database, because we 940 // Always notify here even if we got nothing from the database, because we
807 // don't know how long the fetch will take or if it will even complete. 941 // don't know how long the fetch will take or if it will even complete.
808 NotifyNewSuggestions(); 942 NotifyNewSuggestions();
809 } 943 }
810 944
811 void NTPSnippetsService::OnDisabledReasonChanged( 945 void NTPSnippetsService::OnDisabledReasonChanged(
812 DisabledReason disabled_reason) { 946 DisabledReason disabled_reason) {
813 switch (disabled_reason) { 947 switch (disabled_reason) {
814 case DisabledReason::NONE: 948 case DisabledReason::NONE:
815 // Do not change the status. That will be done in EnterStateEnabled() 949 // Do not change the status. That will be done in EnterStateEnabled()
816 EnterState(State::READY, category_status_); 950 EnterState(State::READY);
817 break; 951 break;
818 952
819 case DisabledReason::EXPLICITLY_DISABLED: 953 case DisabledReason::EXPLICITLY_DISABLED:
820 EnterState(State::DISABLED, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 954 EnterState(State::DISABLED);
955 UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
821 break; 956 break;
822 957
823 case DisabledReason::SIGNED_OUT: 958 case DisabledReason::SIGNED_OUT:
824 EnterState(State::DISABLED, CategoryStatus::SIGNED_OUT); 959 EnterState(State::DISABLED);
960 UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
825 break; 961 break;
826 } 962 }
827 } 963 }
828 964
829 void NTPSnippetsService::EnterState(State state, CategoryStatus status) { 965 void NTPSnippetsService::EnterState(State state) {
830 UpdateCategoryStatus(status);
831
832 if (state == state_) 966 if (state == state_)
833 return; 967 return;
834 968
835 switch (state) { 969 switch (state) {
836 case State::NOT_INITED: 970 case State::NOT_INITED:
837 // Initial state, it should not be possible to get back there. 971 // Initial state, it should not be possible to get back there.
838 NOTREACHED(); 972 NOTREACHED();
839 return; 973 return;
840 974
841 case State::READY: { 975 case State::READY: {
842 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); 976 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
843 977
844 bool fetch_snippets = snippets_.empty() || fetch_after_load_; 978 // TODO(sfiera): always fetch, because there might be server-side
979 // categories we're interested in?
Marc Treib 2016/08/24 15:22:08 No, I don't think so. When we get here, we should
sfiera 2016/08/24 18:56:45 Removed.
980 bool fetch_snippets =
981 categories_[articles_category_].snippets.empty() || fetch_after_load_;
845 DVLOG(1) << "Entering state: READY"; 982 DVLOG(1) << "Entering state: READY";
846 state_ = State::READY; 983 state_ = State::READY;
847 fetch_after_load_ = false; 984 fetch_after_load_ = false;
848 EnterStateEnabled(fetch_snippets); 985 EnterStateEnabled(fetch_snippets);
849 return; 986 return;
850 } 987 }
851 988
852 case State::DISABLED: 989 case State::DISABLED:
853 DCHECK(state_ == State::NOT_INITED || state_ == State::READY); 990 DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
854 991
855 DVLOG(1) << "Entering state: DISABLED"; 992 DVLOG(1) << "Entering state: DISABLED";
856 state_ = State::DISABLED; 993 state_ = State::DISABLED;
857 EnterStateDisabled(); 994 EnterStateDisabled();
858 return; 995 return;
859 996
860 case State::ERROR_OCCURRED: 997 case State::ERROR_OCCURRED:
861 DVLOG(1) << "Entering state: ERROR_OCCURRED"; 998 DVLOG(1) << "Entering state: ERROR_OCCURRED";
862 state_ = State::ERROR_OCCURRED; 999 state_ = State::ERROR_OCCURRED;
863 EnterStateError(); 1000 EnterStateError();
864 return; 1001 return;
865 } 1002 }
866 } 1003 }
867 1004
868 void NTPSnippetsService::NotifyNewSuggestions() { 1005 void NTPSnippetsService::NotifyNewSuggestions() {
Marc Treib 2016/08/24 15:22:09 Do we always want to notify for all categories?
sfiera 2016/08/24 18:56:45 I think it's fine for a first cut (but there's the
Marc Treib 2016/08/25 09:10:50 Acknowledged.
869 std::vector<ContentSuggestion> result; 1006 for (auto& item : categories_) {
Marc Treib 2016/08/24 15:22:08 const?
sfiera 2016/08/24 18:56:45 Done.
870 for (const std::unique_ptr<NTPSnippet>& snippet : snippets_) { 1007 Category category = item.first;
871 if (!snippet->is_complete()) 1008 CategoryContent& content = item.second;
872 continue; 1009
873 ContentSuggestion suggestion( 1010 std::vector<ContentSuggestion> result;
874 MakeUniqueID(provided_category_, snippet->id()), 1011 for (const std::unique_ptr<NTPSnippet>& snippet : content.snippets) {
875 snippet->best_source().url); 1012 // TODO(sfiera): if a snippet is not going to be displayed, move it
876 suggestion.set_amp_url(snippet->best_source().amp_url); 1013 // directly to content.dismissed on fetch. Otherwise, we might prune
877 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 1014 // other snippets to get down to kMaxSnippetCount, only to hide one of the
878 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 1015 // incomplete ones we kept.
879 suggestion.set_publish_date(snippet->publish_date()); 1016 if (!snippet->is_complete())
880 suggestion.set_publisher_name( 1017 continue;
881 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 1018 ContentSuggestion suggestion(MakeUniqueID(category, snippet->id()),
882 suggestion.set_score(snippet->score()); 1019 snippet->best_source().url);
883 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.
Marc Treib 2016/08/24 15:22:09 I don't think it's possible to detect that here?
sfiera 2016/08/24 18:56:45 It wouldn't be possible here. We could detect it i
Marc Treib 2016/08/25 09:10:50 I think so, yes - to me, the TODO being here kinda
sfiera 2016/08/25 12:45:13 Moved.
1034 observer()->OnNewSuggestions(this, category, std::move(result));
884 } 1035 }
885 observer()->OnNewSuggestions(this, provided_category_, std::move(result));
886 } 1036 }
887 1037
888 void NTPSnippetsService::UpdateCategoryStatus(CategoryStatus status) { 1038 void NTPSnippetsService::UpdateCategoryStatus(Category category,
889 if (status == category_status_) 1039 CategoryStatus status) {
1040 DCHECK(categories_.find(category) != categories_.end());
1041 CategoryContent& content = categories_[category];
1042 if (status == content.status)
890 return; 1043 return;
891 1044
892 category_status_ = status; 1045 DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": "
893 observer()->OnCategoryStatusChanged(this, provided_category_, 1046 << static_cast<int>(content.status) << " -> "
894 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 }
895 } 1056 }
896 1057
897 } // namespace ntp_snippets 1058 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/ntp_snippets_service.h ('k') | components/ntp_snippets/ntp_snippets_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698