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/password_manager/core/browser/affiliation_backend.h" | 5 #include "components/password_manager/core/browser/affiliation_backend.h" |
6 | 6 |
| 7 #include <stdint.h> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/location.h" |
7 #include "base/task_runner.h" | 11 #include "base/task_runner.h" |
| 12 #include "base/threading/thread_checker.h" |
| 13 #include "base/time/clock.h" |
| 14 #include "base/time/time.h" |
| 15 #include "components/password_manager/core/browser/affiliation_database.h" |
| 16 #include "components/password_manager/core/browser/affiliation_fetcher.h" |
| 17 #include "net/url_request/url_request_context_getter.h" |
8 | 18 |
9 namespace password_manager { | 19 namespace password_manager { |
10 | 20 namespace { |
11 AffiliationBackend::AffiliationBackend() { | 21 |
| 22 // The duration after which cached affiliation data is considered stale and will |
| 23 // not be used to serve requests any longer. |
| 24 const int kCacheLifetimeInHours = 24; |
| 25 |
| 26 // RequestInfo ---------------------------------------------------------------- |
| 27 |
| 28 // Encapsulates the details of a pending GetAffiliations() request. |
| 29 struct RequestInfo { |
| 30 AffiliationService::ResultCallback callback; |
| 31 scoped_refptr<base::TaskRunner> callback_task_runner; |
| 32 }; |
| 33 |
| 34 } // namespace |
| 35 |
| 36 // AffiliationBackend::FacetManager ------------------------------------------- |
| 37 |
| 38 // Manages and performs ongoing tasks concerning a single facet. |
| 39 class AffiliationBackend::FacetManager { |
| 40 public: |
| 41 // The |backend| must outlive this object. |
| 42 FacetManager(AffiliationBackend* backend, const FacetURI& facet_uri); |
| 43 ~FacetManager(); |
| 44 |
| 45 // Called when |affiliation| information regarding this facet has just been |
| 46 // fetched from the Affiliation API. |
| 47 void OnFetchSucceeded(const AffiliatedFacetsWithUpdateTime& affiliation); |
| 48 |
| 49 // Returns whether this instance has becomes redundant, that is, it has no |
| 50 // more meaningful state than a newly created instance would have. |
| 51 bool CanBeDiscarded() const; |
| 52 |
| 53 // Returns whether or not affiliation information relating to this facet needs |
| 54 // to be fetched right now. |
| 55 bool DoesRequireFetch() const; |
| 56 |
| 57 // Facet-specific implementations for methods in AffiliationService of the |
| 58 // same name. See documentation in affiliation_service.h for details: |
| 59 void GetAffiliations(bool cached_only, const RequestInfo& request_info); |
| 60 |
| 61 private: |
| 62 // Returns the time when cached data for this facet will expire. The data is |
| 63 // already considered expired at the returned microsecond. |
| 64 base::Time GetCacheExpirationTime() const; |
| 65 |
| 66 // Returns whether or not the cache has fresh data for this facet. |
| 67 bool IsCachedDataFresh() const; |
| 68 |
| 69 // Posts the callback of the request described by |request_info| with success. |
| 70 static void ServeRequestWithSuccess(const RequestInfo& request_info, |
| 71 const AffiliatedFacets& affiliation); |
| 72 |
| 73 // Posts the callback of the request described by |request_info| with failure. |
| 74 static void ServeRequestWithFailure(const RequestInfo& request_info); |
| 75 |
| 76 AffiliationBackend* backend_; |
| 77 FacetURI facet_uri_; |
| 78 |
| 79 // The last time affiliation information was fetched for this facet, i.e. the |
| 80 // freshness of the data in the cache. If there is no corresponding data in |
| 81 // the database, this will contain the NULL time. Otherwise, the update time |
| 82 // in the database should match this value; it is stored to reduce disk I/O. |
| 83 base::Time last_update_time_; |
| 84 |
| 85 // Contains information about the GetAffiliations() requests that are waiting |
| 86 // for the result of looking up this facet. |
| 87 std::vector<RequestInfo> pending_requests_; |
| 88 |
| 89 DISALLOW_COPY_AND_ASSIGN(FacetManager); |
| 90 }; |
| 91 |
| 92 AffiliationBackend::FacetManager::FacetManager(AffiliationBackend* backend, |
| 93 const FacetURI& facet_uri) |
| 94 : backend_(backend), |
| 95 facet_uri_(facet_uri), |
| 96 last_update_time_(backend_->ReadLastUpdateTimeFromDatabase(facet_uri)) { |
| 97 } |
| 98 |
| 99 AffiliationBackend::FacetManager::~FacetManager() { |
| 100 // The manager will only be destroyed while there are pending requests if the |
| 101 // entire backend is going. Call failure on pending requests in this case. |
| 102 for (const auto& request_info : pending_requests_) |
| 103 ServeRequestWithFailure(request_info); |
| 104 } |
| 105 |
| 106 void AffiliationBackend::FacetManager::GetAffiliations( |
| 107 bool cached_only, |
| 108 const RequestInfo& request_info) { |
| 109 if (IsCachedDataFresh()) { |
| 110 AffiliatedFacetsWithUpdateTime affiliation; |
| 111 if (!backend_->ReadAffiliationsFromDatabase(facet_uri_, &affiliation)) { |
| 112 // TODO(engedy): Implement this. crbug.com/437865. |
| 113 NOTIMPLEMENTED(); |
| 114 } |
| 115 DCHECK_EQ(affiliation.last_update_time, last_update_time_) << facet_uri_; |
| 116 ServeRequestWithSuccess(request_info, affiliation.facets); |
| 117 } else if (cached_only) { |
| 118 ServeRequestWithFailure(request_info); |
| 119 } else { |
| 120 pending_requests_.push_back(request_info); |
| 121 backend_->SignalNeedNetworkRequest(); |
| 122 } |
| 123 } |
| 124 |
| 125 void AffiliationBackend::FacetManager::OnFetchSucceeded( |
| 126 const AffiliatedFacetsWithUpdateTime& affiliation) { |
| 127 last_update_time_ = affiliation.last_update_time; |
| 128 DCHECK(IsCachedDataFresh()) << facet_uri_; |
| 129 for (const auto& request_info : pending_requests_) |
| 130 ServeRequestWithSuccess(request_info, affiliation.facets); |
| 131 pending_requests_.clear(); |
| 132 } |
| 133 |
| 134 bool AffiliationBackend::FacetManager::CanBeDiscarded() const { |
| 135 return pending_requests_.empty(); |
| 136 } |
| 137 |
| 138 bool AffiliationBackend::FacetManager::DoesRequireFetch() const { |
| 139 return !pending_requests_.empty() && !IsCachedDataFresh(); |
| 140 } |
| 141 |
| 142 base::Time AffiliationBackend::FacetManager::GetCacheExpirationTime() const { |
| 143 if (last_update_time_.is_null()) |
| 144 return base::Time(); |
| 145 return last_update_time_ + base::TimeDelta::FromHours(kCacheLifetimeInHours); |
| 146 } |
| 147 |
| 148 bool AffiliationBackend::FacetManager::IsCachedDataFresh() const { |
| 149 return backend_->GetCurrentTime() < GetCacheExpirationTime(); |
| 150 } |
| 151 |
| 152 // static |
| 153 void AffiliationBackend::FacetManager::ServeRequestWithSuccess( |
| 154 const RequestInfo& request_info, |
| 155 const AffiliatedFacets& affiliation) { |
| 156 request_info.callback_task_runner->PostTask( |
| 157 FROM_HERE, base::Bind(request_info.callback, affiliation, true)); |
| 158 } |
| 159 |
| 160 // static |
| 161 void AffiliationBackend::FacetManager::ServeRequestWithFailure( |
| 162 const RequestInfo& request_info) { |
| 163 request_info.callback_task_runner->PostTask( |
| 164 FROM_HERE, base::Bind(request_info.callback, AffiliatedFacets(), false)); |
| 165 } |
| 166 |
| 167 // AffiliationBackend --------------------------------------------------------- |
| 168 |
| 169 AffiliationBackend::AffiliationBackend( |
| 170 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, |
| 171 scoped_ptr<base::Clock> time_source) |
| 172 : request_context_getter_(request_context_getter), |
| 173 clock_(time_source.Pass()), |
| 174 weak_ptr_factory_(this) { |
| 175 DCHECK_LT(base::Time(), clock_->Now()); |
12 } | 176 } |
13 | 177 |
14 AffiliationBackend::~AffiliationBackend() { | 178 AffiliationBackend::~AffiliationBackend() { |
15 } | 179 } |
16 | 180 |
17 void AffiliationBackend::Initialize() { | 181 void AffiliationBackend::Initialize(const base::FilePath& db_path) { |
18 NOTIMPLEMENTED(); | 182 thread_checker_.reset(new base::ThreadChecker); |
| 183 cache_.reset(new AffiliationDatabase()); |
| 184 if (!cache_->Init(db_path)) { |
| 185 // TODO(engedy): Implement this. crbug.com/437865. |
| 186 NOTIMPLEMENTED(); |
| 187 } |
19 } | 188 } |
20 | 189 |
21 void AffiliationBackend::GetAffiliations( | 190 void AffiliationBackend::GetAffiliations( |
22 const FacetURI& facet_uri, | 191 const FacetURI& facet_uri, |
23 bool cached_only, | 192 bool cached_only, |
24 const AffiliationService::ResultCallback& callback, | 193 const AffiliationService::ResultCallback& callback, |
25 scoped_refptr<base::TaskRunner> callback_task_runner) { | 194 const scoped_refptr<base::TaskRunner>& callback_task_runner) { |
26 NOTIMPLEMENTED(); | 195 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 196 if (!facet_managers_.contains(facet_uri)) { |
| 197 scoped_ptr<FacetManager> new_manager(new FacetManager(this, facet_uri)); |
| 198 facet_managers_.add(facet_uri, new_manager.Pass()); |
| 199 } |
| 200 |
| 201 FacetManager* facet_manager = facet_managers_.get(facet_uri); |
| 202 DCHECK(facet_manager); |
| 203 RequestInfo request_info; |
| 204 request_info.callback = callback; |
| 205 request_info.callback_task_runner = callback_task_runner; |
| 206 facet_manager->GetAffiliations(cached_only, request_info); |
| 207 |
| 208 if (facet_manager->CanBeDiscarded()) |
| 209 facet_managers_.erase(facet_uri); |
27 } | 210 } |
28 | 211 |
29 void AffiliationBackend::Prefetch(const FacetURI& facet_uri, | 212 void AffiliationBackend::Prefetch(const FacetURI& facet_uri, |
30 const base::Time& keep_fresh_until) { | 213 const base::Time& keep_fresh_until) { |
| 214 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 215 |
| 216 // TODO(engedy): Implement this. crbug.com/437865. |
31 NOTIMPLEMENTED(); | 217 NOTIMPLEMENTED(); |
32 } | 218 } |
33 | 219 |
34 void AffiliationBackend::CancelPrefetch(const FacetURI& facet_uri, | 220 void AffiliationBackend::CancelPrefetch(const FacetURI& facet_uri, |
35 const base::Time& keep_fresh_until) { | 221 const base::Time& keep_fresh_until) { |
| 222 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 223 |
| 224 // TODO(engedy): Implement this. crbug.com/437865. |
36 NOTIMPLEMENTED(); | 225 NOTIMPLEMENTED(); |
37 } | 226 } |
38 | 227 |
39 void AffiliationBackend::TrimCache() { | 228 void AffiliationBackend::TrimCache() { |
40 NOTIMPLEMENTED(); | 229 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
41 } | 230 |
42 | 231 // TODO(engedy): Implement this. crbug.com/437865. |
| 232 NOTIMPLEMENTED(); |
| 233 } |
| 234 |
| 235 void AffiliationBackend::SendNetworkRequest() { |
| 236 DCHECK(!fetcher_); |
| 237 |
| 238 std::vector<FacetURI> requested_facet_uris; |
| 239 for (const auto& facet_manager_pair : facet_managers_) { |
| 240 if (facet_manager_pair.second->DoesRequireFetch()) |
| 241 requested_facet_uris.push_back(facet_manager_pair.first); |
| 242 } |
| 243 DCHECK(!requested_facet_uris.empty()); |
| 244 fetcher_.reset(AffiliationFetcher::Create(request_context_getter_.get(), |
| 245 requested_facet_uris, this)); |
| 246 fetcher_->StartRequest(); |
| 247 } |
| 248 |
| 249 base::Time AffiliationBackend::GetCurrentTime() { |
| 250 return clock_->Now(); |
| 251 } |
| 252 |
| 253 base::Time AffiliationBackend::ReadLastUpdateTimeFromDatabase( |
| 254 const FacetURI& facet_uri) { |
| 255 AffiliatedFacetsWithUpdateTime affiliation; |
| 256 return ReadAffiliationsFromDatabase(facet_uri, &affiliation) |
| 257 ? affiliation.last_update_time |
| 258 : base::Time(); |
| 259 } |
| 260 |
| 261 bool AffiliationBackend::ReadAffiliationsFromDatabase( |
| 262 const FacetURI& facet_uri, |
| 263 AffiliatedFacetsWithUpdateTime* affiliations) { |
| 264 return cache_->GetAffiliationsForFacet(facet_uri, affiliations); |
| 265 } |
| 266 |
| 267 void AffiliationBackend::SignalNeedNetworkRequest() { |
| 268 // TODO(engedy): Add more sophisticated throttling logic. crbug.com/437865. |
| 269 if (fetcher_) |
| 270 return; |
| 271 SendNetworkRequest(); |
| 272 } |
| 273 |
| 274 void AffiliationBackend::OnFetchSucceeded( |
| 275 scoped_ptr<AffiliationFetcherDelegate::Result> result) { |
| 276 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 277 fetcher_.reset(); |
| 278 |
| 279 for (const AffiliatedFacets& affiliated_facets : *result) { |
| 280 AffiliatedFacetsWithUpdateTime affiliation; |
| 281 affiliation.facets = affiliated_facets; |
| 282 affiliation.last_update_time = clock_->Now(); |
| 283 |
| 284 std::vector<AffiliatedFacetsWithUpdateTime> obsoleted_affiliations; |
| 285 cache_->StoreAndRemoveConflicting(affiliation, &obsoleted_affiliations); |
| 286 |
| 287 // Cached data in contradiction with newly stored data automatically gets |
| 288 // removed from the DB, and will be stored into |obsoleted_affiliations|. |
| 289 // Let facet managers know if data is removed from under them. |
| 290 // TODO(engedy): Implement this. crbug.com/437865. |
| 291 if (!obsoleted_affiliations.empty()) |
| 292 NOTIMPLEMENTED(); |
| 293 |
| 294 for (const auto& facet_uri : affiliated_facets) { |
| 295 if (!facet_managers_.contains(facet_uri)) |
| 296 continue; |
| 297 FacetManager* facet_manager = facet_managers_.get(facet_uri); |
| 298 facet_manager->OnFetchSucceeded(affiliation); |
| 299 if (facet_manager->CanBeDiscarded()) |
| 300 facet_managers_.erase(facet_uri); |
| 301 } |
| 302 } |
| 303 |
| 304 // A subsequent fetch may be needed if any additional GetAffiliations() |
| 305 // requests came in while the current fetch was in flight. |
| 306 for (const auto& facet_manager_pair : facet_managers_) { |
| 307 if (facet_manager_pair.second->DoesRequireFetch()) { |
| 308 SendNetworkRequest(); |
| 309 return; |
| 310 } |
| 311 } |
| 312 } |
| 313 |
| 314 void AffiliationBackend::OnFetchFailed() { |
| 315 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 316 |
| 317 // TODO(engedy): Implement this. crbug.com/437865. |
| 318 NOTIMPLEMENTED(); |
| 319 } |
| 320 |
| 321 void AffiliationBackend::OnMalformedResponse() { |
| 322 DCHECK(thread_checker_ && thread_checker_->CalledOnValidThread()); |
| 323 |
| 324 // TODO(engedy): Implement this. crbug.com/437865. |
| 325 NOTIMPLEMENTED(); |
| 326 } |
| 327 |
43 } // namespace password_manager | 328 } // namespace password_manager |
OLD | NEW |