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 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 200 database_(std::move(database)), | 200 database_(std::move(database)), |
| 201 fetch_after_load_(false) { | 201 fetch_after_load_(false) { |
| 202 // TODO(dgn) should be removed after branch point (https:://crbug.com/617585). | 202 // TODO(dgn) should be removed after branch point (https:://crbug.com/617585). |
| 203 ClearDeprecatedPrefs(); | 203 ClearDeprecatedPrefs(); |
| 204 | 204 |
| 205 if (explicitly_disabled_) { | 205 if (explicitly_disabled_) { |
| 206 EnterState(State::DISABLED); | 206 EnterState(State::DISABLED); |
| 207 return; | 207 return; |
| 208 } | 208 } |
| 209 | 209 |
| 210 snippets_fetcher_->SetCallback(base::Bind( | 210 // We transition to other states while finalizing the initialization, when the |
| 211 &NTPSnippetsService::OnFetchFinished, base::Unretained(this))); | 211 // database is done loading. |
| 212 | |
| 213 // |sync_service_| can be null in tests or if sync is disabled. | |
| 214 // This is a service we want to keep listening to all the time, independently | |
| 215 // from the state, since it will allow us to enable or disable the snippets | |
| 216 // service. | |
| 217 if (sync_service_) | |
| 218 sync_service_observer_.Add(sync_service_); | |
| 219 | |
| 220 // Will trigger the transition to the READY state. | |
| 221 database_->Load(base::Bind(&NTPSnippetsService::OnDatabaseLoaded, | 212 database_->Load(base::Bind(&NTPSnippetsService::OnDatabaseLoaded, |
| 222 base::Unretained(this))); | 213 base::Unretained(this))); |
| 223 } | 214 } |
| 224 | 215 |
| 225 NTPSnippetsService::~NTPSnippetsService() { | 216 NTPSnippetsService::~NTPSnippetsService() { |
| 226 DCHECK(state_ == State::SHUT_DOWN); | 217 DCHECK(state_ == State::SHUT_DOWN); |
| 227 } | 218 } |
| 228 | 219 |
| 229 // static | 220 // static |
| 230 void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) { | 221 void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| (...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 382 | 373 |
| 383 // static | 374 // static |
| 384 int NTPSnippetsService::GetMaxSnippetCountForTesting() { | 375 int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
| 385 return kMaxSnippetCount; | 376 return kMaxSnippetCount; |
| 386 } | 377 } |
| 387 | 378 |
| 388 //////////////////////////////////////////////////////////////////////////////// | 379 //////////////////////////////////////////////////////////////////////////////// |
| 389 // Private methods | 380 // Private methods |
| 390 | 381 |
| 391 void NTPSnippetsService::OnStateChanged() { | 382 void NTPSnippetsService::OnStateChanged() { |
| 392 if (!initialized()) | 383 if (state_ == State::SHUT_DOWN) |
| 393 return; | 384 return; |
| 394 | 385 |
| 395 DVLOG(1) << "[OnStateChanged]"; | 386 DVLOG(1) << "[OnStateChanged]"; |
| 396 EnterState(GetStateForDependenciesStatus()); | 387 EnterState(GetStateForDependenciesStatus()); |
| 397 } | 388 } |
| 398 | 389 |
| 399 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { | 390 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
| 400 DCHECK(state_ == State::NOT_INITED || state_ == State::SHUT_DOWN); | 391 DCHECK(state_ == State::NOT_INITED || state_ == State::SHUT_DOWN); |
| 401 if (state_ == State::SHUT_DOWN) | 392 if (state_ == State::SHUT_DOWN) |
| 402 return; | 393 return; |
| 403 | 394 |
| 404 DCHECK(snippets_.empty()); | 395 DCHECK(snippets_.empty()); |
| 405 DCHECK(discarded_snippets_.empty()); | 396 DCHECK(discarded_snippets_.empty()); |
| 406 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { | 397 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { |
| 407 if (snippet->is_discarded()) | 398 if (snippet->is_discarded()) |
| 408 discarded_snippets_.emplace_back(std::move(snippet)); | 399 discarded_snippets_.emplace_back(std::move(snippet)); |
| 409 else | 400 else |
| 410 snippets_.emplace_back(std::move(snippet)); | 401 snippets_.emplace_back(std::move(snippet)); |
| 411 } | 402 } |
| 412 std::sort(snippets_.begin(), snippets_.end(), | 403 std::sort(snippets_.begin(), snippets_.end(), |
| 413 [](const std::unique_ptr<NTPSnippet>& lhs, | 404 [](const std::unique_ptr<NTPSnippet>& lhs, |
| 414 const std::unique_ptr<NTPSnippet>& rhs) { | 405 const std::unique_ptr<NTPSnippet>& rhs) { |
| 415 return lhs->score() > rhs->score(); | 406 return lhs->score() > rhs->score(); |
| 416 }); | 407 }); |
| 417 | 408 |
| 418 // Change state after we started loading the snippets. During startup, the | 409 ClearExpiredSnippets(); |
| 419 // Sync service might not be completely loaded when we initialize this | 410 FinishInitialization(); |
| 420 // service. Whether we had snippets in the last season is used to guess if | |
| 421 // we should enable the service or not. See |GetStateForDependenciesStatus|. | |
| 422 EnterState(GetStateForDependenciesStatus()); | |
| 423 | |
| 424 LoadingSnippetsFinished(); | |
| 425 | |
| 426 // Start a fetch if we don't have any snippets yet, or a fetch was requested | |
| 427 // earlier. | |
| 428 if (snippets_.empty() || fetch_after_load_) { | |
| 429 fetch_after_load_ = false; | |
| 430 FetchSnippets(); | |
| 431 } | |
| 432 } | 411 } |
| 433 | 412 |
| 434 void NTPSnippetsService::OnSuggestionsChanged( | 413 void NTPSnippetsService::OnSuggestionsChanged( |
| 435 const SuggestionsProfile& suggestions) { | 414 const SuggestionsProfile& suggestions) { |
| 436 DCHECK(initialized()); | 415 DCHECK(initialized()); |
| 437 | 416 |
| 438 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions); | 417 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions); |
| 439 if (hosts == GetSnippetHostsFromPrefs()) | 418 if (hosts == GetSnippetHostsFromPrefs()) |
| 440 return; | 419 return; |
| 441 | 420 |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 552 } | 531 } |
| 553 | 532 |
| 554 void NTPSnippetsService::StoreSnippetHostsToPrefs( | 533 void NTPSnippetsService::StoreSnippetHostsToPrefs( |
| 555 const std::set<std::string>& hosts) { | 534 const std::set<std::string>& hosts) { |
| 556 base::ListValue list; | 535 base::ListValue list; |
| 557 for (const std::string& host : hosts) | 536 for (const std::string& host : hosts) |
| 558 list.AppendString(host); | 537 list.AppendString(host); |
| 559 pref_service_->Set(prefs::kSnippetHosts, list); | 538 pref_service_->Set(prefs::kSnippetHosts, list); |
| 560 } | 539 } |
| 561 | 540 |
| 562 void NTPSnippetsService::LoadingSnippetsFinished() { | 541 void NTPSnippetsService::ClearExpiredSnippets() { |
| 563 DCHECK(ready()); | |
| 564 | |
| 565 // Remove expired snippets. | |
| 566 base::Time expiry = base::Time::Now(); | 542 base::Time expiry = base::Time::Now(); |
| 567 | 543 |
| 568 // Move expired snippets over into |to_delete|. | 544 // Move expired snippets over into |to_delete|. |
| 569 NTPSnippet::PtrVector to_delete; | 545 NTPSnippet::PtrVector to_delete; |
| 570 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { | 546 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { |
| 571 if (snippet->expiry_date() <= expiry) | 547 if (snippet->expiry_date() <= expiry) |
| 572 to_delete.emplace_back(std::move(snippet)); | 548 to_delete.emplace_back(std::move(snippet)); |
| 573 } | 549 } |
| 574 Compact(&snippets_); | 550 Compact(&snippets_); |
| 575 | 551 |
| 576 // If there are still more snippets than we want to show, move the extra ones | |
| 577 // over into |to_delete| as well. | |
| 578 if (snippets_.size() > kMaxSnippetCount) { | |
| 579 to_delete.insert( | |
| 580 to_delete.end(), | |
| 581 std::make_move_iterator(snippets_.begin() + kMaxSnippetCount), | |
| 582 std::make_move_iterator(snippets_.end())); | |
| 583 snippets_.resize(kMaxSnippetCount); | |
| 584 } | |
| 585 | |
| 586 // Move expired discarded snippets over into |to_delete| as well. | 552 // Move expired discarded snippets over into |to_delete| as well. |
| 587 for (std::unique_ptr<NTPSnippet>& snippet : discarded_snippets_) { | 553 for (std::unique_ptr<NTPSnippet>& snippet : discarded_snippets_) { |
| 588 if (snippet->expiry_date() <= expiry) | 554 if (snippet->expiry_date() <= expiry) |
| 589 to_delete.emplace_back(std::move(snippet)); | 555 to_delete.emplace_back(std::move(snippet)); |
| 590 } | 556 } |
| 591 Compact(&discarded_snippets_); | 557 Compact(&discarded_snippets_); |
| 592 | 558 |
| 593 // Finally, actually delete the removed snippets from the DB. | 559 // Finally, actually delete the removed snippets from the DB. |
| 594 database_->Delete(to_delete); | 560 database_->Delete(to_delete); |
| 595 | 561 |
| 596 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", | |
| 597 snippets_.size()); | |
| 598 if (snippets_.empty() && !discarded_snippets_.empty()) { | |
| 599 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", | |
| 600 discarded_snippets_.size()); | |
| 601 } | |
| 602 | |
| 603 FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_, | |
| 604 NTPSnippetsServiceLoaded()); | |
| 605 | |
| 606 // If there are any snippets left, schedule a timer for the next expiry. | 562 // If there are any snippets left, schedule a timer for the next expiry. |
| 607 if (snippets_.empty() && discarded_snippets_.empty()) | 563 if (snippets_.empty() && discarded_snippets_.empty()) |
| 608 return; | 564 return; |
| 609 | 565 |
| 610 base::Time next_expiry = base::Time::Max(); | 566 base::Time next_expiry = base::Time::Max(); |
| 611 for (const auto& snippet : snippets_) { | 567 for (const auto& snippet : snippets_) { |
| 612 if (snippet->expiry_date() < next_expiry) | 568 if (snippet->expiry_date() < next_expiry) |
| 613 next_expiry = snippet->expiry_date(); | 569 next_expiry = snippet->expiry_date(); |
| 614 } | 570 } |
| 615 for (const auto& snippet : discarded_snippets_) { | 571 for (const auto& snippet : discarded_snippets_) { |
| 616 if (snippet->expiry_date() < next_expiry) | 572 if (snippet->expiry_date() < next_expiry) |
| 617 next_expiry = snippet->expiry_date(); | 573 next_expiry = snippet->expiry_date(); |
| 618 } | 574 } |
| 619 DCHECK_GT(next_expiry, expiry); | 575 DCHECK_GT(next_expiry, expiry); |
| 620 expiry_timer_.Start(FROM_HERE, next_expiry - expiry, | 576 expiry_timer_.Start(FROM_HERE, next_expiry - expiry, |
| 621 base::Bind(&NTPSnippetsService::LoadingSnippetsFinished, | 577 base::Bind(&NTPSnippetsService::ClearExpiredSnippets, |
| 622 base::Unretained(this))); | 578 base::Unretained(this))); |
| 623 } | 579 } |
| 624 | 580 |
| 581 void NTPSnippetsService::LoadingSnippetsFinished() { | |
|
Bernhard Bauer
2016/06/09 15:21:33
This is now more FetchingSnippetsFinished(). You c
dgn
2016/06/09 16:03:38
Done.
| |
| 582 DCHECK(ready()); | |
| 583 | |
| 584 ClearExpiredSnippets(); | |
| 585 | |
| 586 // If there are still more snippets than we want to show, move the extra ones | |
| 587 // over into |to_delete| as well. | |
|
Marc Treib
2016/06/09 15:52:44
nit: remove "as well"
dgn
2016/06/09 16:03:38
Done.
| |
| 588 NTPSnippet::PtrVector to_delete; | |
| 589 if (snippets_.size() > kMaxSnippetCount) { | |
| 590 to_delete.insert( | |
| 591 to_delete.end(), | |
| 592 std::make_move_iterator(snippets_.begin() + kMaxSnippetCount), | |
| 593 std::make_move_iterator(snippets_.end())); | |
| 594 snippets_.resize(kMaxSnippetCount); | |
| 595 } | |
| 596 database_->Delete(to_delete); | |
| 597 | |
| 598 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", | |
| 599 snippets_.size()); | |
| 600 if (snippets_.empty() && !discarded_snippets_.empty()) { | |
| 601 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", | |
| 602 discarded_snippets_.size()); | |
| 603 } | |
| 604 | |
| 605 FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_, | |
| 606 NTPSnippetsServiceLoaded()); | |
| 607 } | |
| 608 | |
| 625 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { | 609 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { |
| 626 if (fetch_snippets) | 610 if (fetch_snippets) |
| 627 FetchSnippets(); | 611 FetchSnippets(); |
| 628 | 612 |
| 629 // If host restrictions are enabled, register for host list updates. | 613 // If host restrictions are enabled, register for host list updates. |
| 630 // |suggestions_service_| can be null in tests. | 614 // |suggestions_service_| can be null in tests. |
| 631 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { | 615 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { |
| 632 suggestions_service_subscription_ = | 616 suggestions_service_subscription_ = |
| 633 suggestions_service_->AddCallback(base::Bind( | 617 suggestions_service_->AddCallback(base::Bind( |
| 634 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); | 618 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 653 FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_, | 637 FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_, |
| 654 NTPSnippetsServiceShutdown()); | 638 NTPSnippetsServiceShutdown()); |
| 655 | 639 |
| 656 expiry_timer_.Stop(); | 640 expiry_timer_.Stop(); |
| 657 suggestions_service_subscription_.reset(); | 641 suggestions_service_subscription_.reset(); |
| 658 | 642 |
| 659 if (sync_service_) | 643 if (sync_service_) |
| 660 sync_service_observer_.Remove(sync_service_); | 644 sync_service_observer_.Remove(sync_service_); |
| 661 } | 645 } |
| 662 | 646 |
| 647 void NTPSnippetsService::FinishInitialization() { | |
| 648 snippets_fetcher_->SetCallback( | |
| 649 base::Bind(&NTPSnippetsService::OnFetchFinished, base::Unretained(this))); | |
| 650 | |
| 651 // |sync_service_| can be null in tests or if sync is disabled. | |
| 652 // This is a service we want to keep listening to all the time, independently | |
| 653 // from the state, since it will allow us to enable or disable the snippets | |
| 654 // service. | |
| 655 if (sync_service_) | |
| 656 sync_service_observer_.Add(sync_service_); | |
| 657 | |
| 658 // Change state after we started loading the snippets. During startup, the | |
| 659 // Sync service might not be completely loaded when we initialize this | |
| 660 // service. Whether we had snippets in the last season is used to guess if | |
|
Bernhard Bauer
2016/06/09 15:21:33
"last session" :)
dgn
2016/06/09 16:03:38
Thanks. The comment was outdated also u_u
| |
| 661 // we should enable the service or not. See |GetStateForDependenciesStatus|. | |
| 662 EnterState(GetStateForDependenciesStatus()); | |
| 663 | |
| 664 FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_, | |
| 665 NTPSnippetsServiceLoaded()); | |
| 666 | |
| 667 // Start a fetch if we don't have any snippets yet, or a fetch was requested | |
| 668 // earlier. | |
| 669 if (ready() && (snippets_.empty() || fetch_after_load_)) { | |
| 670 fetch_after_load_ = false; | |
| 671 FetchSnippets(); | |
| 672 } | |
| 673 } | |
| 674 | |
| 663 NTPSnippetsService::State NTPSnippetsService::GetStateForDependenciesStatus() { | 675 NTPSnippetsService::State NTPSnippetsService::GetStateForDependenciesStatus() { |
| 664 switch (GetDisabledReason()) { | 676 switch (GetDisabledReason()) { |
| 665 case DisabledReason::NONE: | 677 case DisabledReason::NONE: |
| 666 return State::READY; | 678 return State::READY; |
| 667 | 679 |
| 668 case DisabledReason::HISTORY_SYNC_STATE_UNKNOWN: | 680 case DisabledReason::HISTORY_SYNC_STATE_UNKNOWN: |
| 669 // HistorySync is not initialized yet, so we don't know what the actual | 681 // HistorySync is not initialized yet, so we don't know what the actual |
| 670 // state is. However, because we retreived the previous snippets from the | 682 // state is and we just return the current one. If things change, |
| 671 // database, if we got something, we know that the service was previously | 683 // |OnStateChanged| will call this function again to update the state. |
| 672 // enabled, so we just restore that state. If things changed, | |
| 673 // |OnStateChanged| will call this function again to fix the state. | |
| 674 DVLOG(1) << "Sync configuration incomplete, continuing based on the " | 684 DVLOG(1) << "Sync configuration incomplete, continuing based on the " |
| 675 "current state."; | 685 "current state."; |
| 676 return snippets_.empty() ? State::DISABLED : State::READY; | 686 return state_; |
| 677 | 687 |
| 678 case DisabledReason::EXPLICITLY_DISABLED: | 688 case DisabledReason::EXPLICITLY_DISABLED: |
| 679 case DisabledReason::HISTORY_SYNC_DISABLED: | 689 case DisabledReason::HISTORY_SYNC_DISABLED: |
| 680 return State::DISABLED; | 690 return State::DISABLED; |
| 681 } | 691 } |
| 682 | 692 |
| 683 // All cases should be handled by the above switch | 693 // All cases should be handled by the above switch |
| 684 NOTREACHED(); | 694 NOTREACHED(); |
| 685 return State::DISABLED; | 695 return State::DISABLED; |
| 686 } | 696 } |
| 687 | 697 |
| 688 void NTPSnippetsService::EnterState(State state) { | 698 void NTPSnippetsService::EnterState(State state) { |
| 699 if (state == state_) | |
| 700 return; | |
| 701 | |
| 689 switch (state) { | 702 switch (state) { |
| 690 case State::NOT_INITED: | 703 case State::NOT_INITED: |
| 691 // Initial state, it should not be possible to get back there. | 704 // Initial state, it should not be possible to get back there. |
| 692 NOTREACHED(); | 705 NOTREACHED(); |
| 693 return; | 706 return; |
| 694 | 707 |
| 695 case State::READY: { | 708 case State::READY: { |
| 696 DCHECK(state_ == State::NOT_INITED || state_ == State::READY || | 709 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); |
| 697 state_ == State::DISABLED); | |
| 698 if (state_ == State::READY) | |
| 699 return; | |
| 700 | 710 |
| 701 // If the service was previously disabled, we will need to start a fetch | 711 // If the service was previously disabled, we will need to start a fetch |
| 702 // because otherwise there won't be any. | 712 // because otherwise there won't be any. |
| 703 bool fetch_snippets = state_ == State::DISABLED; | 713 bool fetch_snippets = state_ == State::DISABLED || fetch_after_load_; |
| 704 DVLOG(1) << "Entering state: READY"; | 714 DVLOG(1) << "Entering state: READY"; |
| 705 state_ = State::READY; | 715 state_ = State::READY; |
| 716 fetch_after_load_ = false; | |
| 706 EnterStateEnabled(fetch_snippets); | 717 EnterStateEnabled(fetch_snippets); |
| 707 return; | 718 return; |
| 708 } | 719 } |
| 709 | 720 |
| 710 case State::DISABLED: | 721 case State::DISABLED: |
| 711 DCHECK(state_ == State::NOT_INITED || state_ == State::READY || | 722 DCHECK(state_ == State::NOT_INITED || state_ == State::READY); |
| 712 state_ == State::DISABLED); | |
| 713 if (state_ == State::DISABLED) | |
| 714 return; | |
| 715 | 723 |
| 716 DVLOG(1) << "Entering state: DISABLED"; | 724 DVLOG(1) << "Entering state: DISABLED"; |
| 717 state_ = State::DISABLED; | 725 state_ = State::DISABLED; |
| 718 EnterStateDisabled(); | 726 EnterStateDisabled(); |
| 719 return; | 727 return; |
| 720 | 728 |
| 721 case State::SHUT_DOWN: | 729 case State::SHUT_DOWN: |
| 722 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED || | |
| 723 state_ == State::READY); | |
| 724 DVLOG(1) << "Entering state: SHUT_DOWN"; | 730 DVLOG(1) << "Entering state: SHUT_DOWN"; |
| 725 state_ = State::SHUT_DOWN; | 731 state_ = State::SHUT_DOWN; |
| 726 EnterStateShutdown(); | 732 EnterStateShutdown(); |
| 727 return; | 733 return; |
| 728 } | 734 } |
| 729 } | 735 } |
| 730 | 736 |
| 731 void NTPSnippetsService::ClearDeprecatedPrefs() { | 737 void NTPSnippetsService::ClearDeprecatedPrefs() { |
| 732 pref_service_->ClearPref(prefs::kDeprecatedSnippets); | 738 pref_service_->ClearPref(prefs::kDeprecatedSnippets); |
| 733 pref_service_->ClearPref(prefs::kDeprecatedDiscardedSnippets); | 739 pref_service_->ClearPref(prefs::kDeprecatedDiscardedSnippets); |
| 734 } | 740 } |
| 735 | 741 |
| 736 } // namespace ntp_snippets | 742 } // namespace ntp_snippets |
| OLD | NEW |