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