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