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