Index: components/password_manager/core/browser/affiliation_backend.cc |
diff --git a/components/password_manager/core/browser/affiliation_backend.cc b/components/password_manager/core/browser/affiliation_backend.cc |
index e1668444f854199aad3bc333fdf54753008f4539..2555260bb779524e7eb11d856abc4ea8fd15575d 100644 |
--- a/components/password_manager/core/browser/affiliation_backend.cc |
+++ b/components/password_manager/core/browser/affiliation_backend.cc |
@@ -4,26 +4,208 @@ |
#include "components/password_manager/core/browser/affiliation_backend.h" |
+#include <stdint.h> |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
#include "base/task_runner.h" |
+#include "base/time/clock.h" |
+#include "base/time/time.h" |
+#include "components/password_manager/core/browser/affiliation_database.h" |
+#include "components/password_manager/core/browser/affiliation_fetcher.h" |
+#include "net/url_request/url_request_context_getter.h" |
namespace password_manager { |
+namespace { |
+ |
+// The duration after which cached affiliation data is considered stale and will |
+// not be used to serve requests any longer. |
+const int64_t kCacheHardExpiryDurationInSeconds = 24 * 3600; |
Mike West
2015/02/02 15:19:44
Nit: "24 * 60 * 60" seems slightly more common in
engedy
2015/02/02 17:00:12
Done. I have replaced this with kCacheHardExpiryDu
|
+ |
+// RequestInfo ---------------------------------------------------------------- |
+ |
+// Encapsulates the details of a pending GetAffiliations() request. |
+struct RequestInfo { |
+ RequestInfo(); |
+ ~RequestInfo(); |
+ |
+ AffiliationService::ResultCallback callback; |
+ scoped_refptr<base::TaskRunner> callback_task_runner; |
+}; |
+ |
+RequestInfo::RequestInfo() { |
+} |
+ |
+RequestInfo::~RequestInfo() { |
+} |
Mike West
2015/02/02 15:19:44
Nit: Any reason not to inline these? FacetManager
engedy
2015/02/02 15:33:15
Vaclav prefers we do not inline in production code
vabr (Chromium)
2015/02/02 15:37:37
http://dev.chromium.org/developers/coding-style/cp
Garrett Casto
2015/02/03 08:20:37
Further down on that page it talks about how in th
engedy
2015/02/03 10:49:22
Done.
|
+ |
+} // namespace |
+ |
+// AffiliationBackend::FacetManager ------------------------------------------- |
+ |
+// Manages and performs ongoing tasks concerning a single facet. |
+class AffiliationBackend::FacetManager { |
+ public: |
+ // The |backend| must outlive this object. |
+ FacetManager(AffiliationBackend* backend, const FacetURI& facet_uri); |
+ ~FacetManager(); |
+ |
+ // Called when |affiliation| information regarding this facet has just been |
+ // fetched from the Affiliation API. |
+ void OnFetchSucceeded(const AffiliatedFacetsWithUpdateTime& affiliation); |
+ |
+ // Returns whether this instance has becomes redundant, that is, it has no |
+ // more meaningful state than a newly created instance would have. |
+ bool CanBeDiscarded() const; |
+ |
+ // Returns whether or not affiliation information relating to this facet needs |
+ // to be fetched right now. |
+ bool DoesRequireFetch() const; |
+ |
+ // Facet-specific implementations for methods in AffiliationService of the |
+ // same name. See documentation in affiliation_service.h for details: |
+ void GetAffiliations(bool cached_only, const RequestInfo& request_info); |
+ |
+ private: |
+ // Returns the time when cached data for this facet will become stale. |
+ base::Time GetCacheHardExpiryTime() const; |
+ |
+ // Returns whether or not the cache has fresh data for this facet. |
+ bool IsCachedDataFresh() const; |
+ |
+ // Posts the callback of the request described by |request_info| with success. |
+ static void ServeRequestWithSuccess(const RequestInfo& request_info, |
+ const AffiliatedFacets& affiliation); |
-AffiliationBackend::AffiliationBackend() { |
+ // Posts the callback of the request described by |request_info| with failure. |
+ static void ServeRequestWithFailure(const RequestInfo& request_info); |
+ |
+ AffiliationBackend* backend_; |
+ FacetURI facet_uri_; |
+ |
+ // The last time affiliation information was fetched for this facet, i.e. the |
+ // freshness of the data in the database. If there is no corresponding data in |
+ // in the database, this will contain something sufficiently far away in the |
+ // past to be considered stale for sure. Otherwise, the last_update_time in |
+ // the database should match this value; it is only cached to reduce disk I/O. |
+ base::Time last_update_time_; |
+ |
+ // Contains information about the GetAffiliations() requests that are waiting |
+ // for the result of looking up this facet. |
+ std::vector<RequestInfo> pending_requests_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(FacetManager); |
+}; |
+ |
+AffiliationBackend::FacetManager::FacetManager(AffiliationBackend* backend, |
+ const FacetURI& facet_uri) |
+ : backend_(backend), |
+ facet_uri_(facet_uri), |
+ last_update_time_(backend_->ReadLastUpdateTimeFromDatabase(facet_uri)) { |
+} |
+ |
+AffiliationBackend::FacetManager::~FacetManager() { |
+ // The manager will only be destroyed while there are pending requests if the |
+ // entire backend is going. Call failure on pending requests in this case. |
+ for (const auto& request_info : pending_requests_) |
+ ServeRequestWithFailure(request_info); |
+} |
+ |
+void AffiliationBackend::FacetManager::GetAffiliations( |
+ bool cached_only, |
+ const RequestInfo& request_info) { |
+ if (IsCachedDataFresh()) { |
+ AffiliatedFacetsWithUpdateTime affiliation; |
+ if (!backend_->ReadAffiliationsFromDatabase(facet_uri_, &affiliation)) |
+ NOTIMPLEMENTED(); |
+ DCHECK_EQ(affiliation.last_update_time, last_update_time_) << facet_uri_; |
+ ServeRequestWithSuccess(request_info, affiliation.facets); |
+ } else if (cached_only) { |
+ ServeRequestWithFailure(request_info); |
+ } else { |
+ pending_requests_.push_back(request_info); |
+ backend_->SignalNeedNetworkRequest(); |
+ } |
+} |
+ |
+void AffiliationBackend::FacetManager::OnFetchSucceeded( |
+ const AffiliatedFacetsWithUpdateTime& affiliation) { |
+ last_update_time_ = affiliation.last_update_time; |
+ DCHECK(IsCachedDataFresh()) << facet_uri_; |
+ for (const auto& request_info : pending_requests_) |
+ ServeRequestWithSuccess(request_info, affiliation.facets); |
+ pending_requests_.clear(); |
+} |
+ |
+bool AffiliationBackend::FacetManager::CanBeDiscarded() const { |
+ return pending_requests_.empty(); |
+} |
+ |
+bool AffiliationBackend::FacetManager::DoesRequireFetch() const { |
+ return !pending_requests_.empty() && !IsCachedDataFresh(); |
+} |
+ |
+base::Time AffiliationBackend::FacetManager::GetCacheHardExpiryTime() const { |
+ return last_update_time_ + |
+ base::TimeDelta::FromSeconds(kCacheHardExpiryDurationInSeconds); |
+} |
+ |
+bool AffiliationBackend::FacetManager::IsCachedDataFresh() const { |
+ return backend_->GetCurrentTime() < GetCacheHardExpiryTime(); |
+} |
+ |
+// static |
+void AffiliationBackend::FacetManager::ServeRequestWithSuccess( |
+ const RequestInfo& request_info, |
+ const AffiliatedFacets& affiliation) { |
+ request_info.callback_task_runner->PostTask( |
+ FROM_HERE, base::Bind(request_info.callback, affiliation, true)); |
+} |
+ |
+// static |
+void AffiliationBackend::FacetManager::ServeRequestWithFailure( |
+ const RequestInfo& request_info) { |
+ request_info.callback_task_runner->PostTask( |
+ FROM_HERE, base::Bind(request_info.callback, AffiliatedFacets(), false)); |
+} |
+ |
+// AffiliationBackend --------------------------------------------------------- |
+ |
+AffiliationBackend::AffiliationBackend( |
+ const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, |
+ scoped_ptr<base::Clock> time_source) |
+ : request_context_getter_(request_context_getter), |
+ clock_(time_source.Pass()), |
+ weak_ptr_factory_(this) { |
} |
AffiliationBackend::~AffiliationBackend() { |
} |
-void AffiliationBackend::Initialize() { |
- NOTIMPLEMENTED(); |
+void AffiliationBackend::Initialize(const base::FilePath& db_path) { |
Mike West
2015/02/02 15:19:44
Should initialize be called on the same thread as
engedy
2015/02/02 17:00:12
As discussed, I have added a thread checker in a s
|
+ cache_.reset(new AffiliationDatabase()); |
+ if (!cache_->Init(db_path)) |
+ NOTIMPLEMENTED(); |
} |
void AffiliationBackend::GetAffiliations( |
const FacetURI& facet_uri, |
bool cached_only, |
const AffiliationService::ResultCallback& callback, |
- scoped_refptr<base::TaskRunner> callback_task_runner) { |
- NOTIMPLEMENTED(); |
+ const scoped_refptr<base::TaskRunner>& callback_task_runner) { |
+ if (!facet_managers_.contains(facet_uri)) { |
+ scoped_ptr<FacetManager> new_manager(new FacetManager(this, facet_uri)); |
+ facet_managers_.add(facet_uri, new_manager.Pass()); |
+ } |
+ |
+ FacetManager* facet_manager = facet_managers_.get(facet_uri); |
Mike West
2015/02/02 15:19:45
`.add()` can fail, can't it? OOM, etc. Perhaps `DC
engedy
2015/02/02 17:00:12
Do I understand correctly that you prefer this bec
Mike West
2015/02/03 09:59:02
Right. The value of a DCHECK is that you crash pre
|
+ RequestInfo request_info; |
+ request_info.callback = callback; |
+ request_info.callback_task_runner = callback_task_runner; |
+ facet_manager->GetAffiliations(cached_only, request_info); |
+ |
+ if (facet_manager->CanBeDiscarded()) |
+ facet_managers_.erase(facet_uri); |
} |
void AffiliationBackend::Prefetch(const FacetURI& facet_uri, |
@@ -40,4 +222,88 @@ void AffiliationBackend::TrimCache() { |
NOTIMPLEMENTED(); |
} |
+void AffiliationBackend::SendNetworkRequest() { |
+ DCHECK(!fetcher_); |
Mike West
2015/02/02 15:19:44
DCHECK that you have more than 0 managers? I guess
engedy
2015/02/02 17:00:11
Yes, I'd prefer not checking that separately.
|
+ |
+ std::vector<FacetURI> requested_facet_uris; |
+ for (const auto& facet_manager_pair : facet_managers_) { |
+ if (facet_manager_pair.second->DoesRequireFetch()) |
+ requested_facet_uris.push_back(facet_manager_pair.first); |
+ } |
+ DCHECK(!requested_facet_uris.empty()); |
+ fetcher_.reset(AffiliationFetcher::Create(request_context_getter_.get(), |
+ requested_facet_uris, this)); |
+ fetcher_->StartRequest(); |
+} |
+ |
+base::Time AffiliationBackend::GetCurrentTime() { |
+ return clock_->Now(); |
+} |
+ |
+base::Time AffiliationBackend::ReadLastUpdateTimeFromDatabase( |
+ const FacetURI& facet_uri) { |
+ AffiliatedFacetsWithUpdateTime affiliation; |
+ if (cache_->GetAffiliationsForFacet(facet_uri, &affiliation)) |
Mike West
2015/02/02 15:19:44
You could spell this `ReadAffiliationsFromDatabase
engedy
2015/02/02 17:00:11
Done.
|
+ return affiliation.last_update_time; |
+ else |
+ return clock_->Now() - |
+ base::TimeDelta::FromSeconds(kCacheHardExpiryDurationInSeconds); |
Mike West
2015/02/02 15:19:45
Nit: I think a ternary if would be prettier here.
engedy
2015/02/02 17:00:11
Done.
|
+} |
+ |
+bool AffiliationBackend::ReadAffiliationsFromDatabase( |
+ const FacetURI& facet_uri, |
+ AffiliatedFacetsWithUpdateTime* affiliations) { |
+ return cache_->GetAffiliationsForFacet(facet_uri, affiliations); |
+} |
+ |
+void AffiliationBackend::SignalNeedNetworkRequest() { |
+ // TODO(engedy): Integrate more sophisticated throttling logic here. |
+ if (fetcher_) |
+ return; |
+ SendNetworkRequest(); |
+} |
+ |
+void AffiliationBackend::OnFetchSucceeded( |
+ scoped_ptr<AffiliationFetcherDelegate::Result> result) { |
+ fetcher_.reset(); |
+ |
+ for (const AffiliatedFacets& affiliated_facets : *result) { |
+ AffiliatedFacetsWithUpdateTime affiliation; |
+ affiliation.facets = affiliated_facets; |
+ affiliation.last_update_time = clock_->Now(); |
+ |
+ std::vector<AffiliatedFacetsWithUpdateTime> obsoleted_affiliations; |
+ cache_->StoreAndRemoveConflicting(affiliation, &obsoleted_affiliations); |
+ |
+ if (!obsoleted_affiliations.empty()) |
+ NOTIMPLEMENTED(); |
Mike West
2015/02/02 15:19:45
Can you add a comment here? It's not clear to me w
engedy
2015/02/02 17:00:12
Done.
|
+ |
+ for (const auto& facet_uri : affiliated_facets) { |
+ if (!facet_managers_.contains(facet_uri)) |
Mike West
2015/02/02 15:19:44
It feels like there can be a race here if requests
engedy
2015/02/02 17:00:11
Well, if the response happens to contain the data
|
+ continue; |
+ FacetManager* facet_manager = facet_managers_.get(facet_uri); |
+ facet_manager->OnFetchSucceeded(affiliation); |
+ if (facet_manager->CanBeDiscarded()) |
+ facet_managers_.erase(facet_uri); |
+ } |
+ } |
+ |
+ // A subsequent fetch is needed if additional GetAffiliations() request came |
Mike West
2015/02/02 15:19:45
Nit: s/request came/requests come/ or s/if additio
engedy
2015/02/02 17:00:11
Done.
|
+ // in while the current fetch was in flight. |
+ for (const auto& facet_manager_pair : facet_managers_) { |
+ if (facet_manager_pair.second->DoesRequireFetch()) { |
+ SendNetworkRequest(); |
+ return; |
+ } |
+ } |
+} |
+ |
+void AffiliationBackend::OnFetchFailed() { |
+ NOTIMPLEMENTED(); |
Mike West
2015/02/02 15:19:44
Please add TODO comments pointing to a bug for thi
engedy
2015/02/02 17:00:11
Done. I have mentioned the very same bug that this
|
+} |
+ |
+void AffiliationBackend::OnMalformedResponse() { |
+ NOTIMPLEMENTED(); |
+} |
+ |
} // namespace password_manager |