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