1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/search_provider_logos/logo_tracker.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/task_runner_util.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/time/default_clock.h"
13 #include "net/http/http_response_headers.h"
14 #include "net/url_request/url_fetcher.h"
15 #include "net/url_request/url_request_context_getter.h"
16 #include "net/url_request/url_request_status.h"
18 namespace search_provider_logos
{
22 const int64 kMaxDownloadBytes
= 1024 * 1024;
24 //const int kDecodeLogoTimeoutSeconds = 30;
26 // Returns whether the metadata for the cached logo indicates that the logo is
27 // OK to show, i.e. it's not expired or it's allowed to be shown temporarily
29 bool IsLogoOkToShow(const LogoMetadata
& metadata
, base::Time now
) {
30 base::TimeDelta offset
=
31 base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS
* 3 / 2);
32 base::Time distant_past
= now
- offset
;
33 base::Time distant_future
= now
+ offset
;
34 // Sanity check so logos aren't accidentally cached forever.
35 if (metadata
.expiration_time
< distant_past
||
36 metadata
.expiration_time
> distant_future
) {
39 return metadata
.can_show_after_expiration
|| metadata
.expiration_time
>= now
;
42 // Reads the logo from the cache and returns it. Returns NULL if the cache is
43 // empty, corrupt, expired, or doesn't apply to the current logo URL.
44 scoped_ptr
<EncodedLogo
> GetLogoFromCacheOnFileThread(LogoCache
* logo_cache
,
47 const LogoMetadata
* metadata
= logo_cache
->GetCachedLogoMetadata();
49 return scoped_ptr
<EncodedLogo
>();
51 if (metadata
->source_url
!= logo_url
.spec() ||
52 !IsLogoOkToShow(*metadata
, now
)) {
53 logo_cache
->SetCachedLogo(NULL
);
54 return scoped_ptr
<EncodedLogo
>();
57 return logo_cache
->GetCachedLogo().Pass();
60 void DeleteLogoCacheOnFileThread(LogoCache
* logo_cache
) {
66 LogoTracker::LogoTracker(
67 base::FilePath cached_logo_directory
,
68 scoped_refptr
<base::SequencedTaskRunner
> file_task_runner
,
69 scoped_refptr
<base::TaskRunner
> background_task_runner
,
70 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter
,
71 scoped_ptr
<LogoDelegate
> delegate
)
73 is_cached_logo_valid_(false),
74 logo_delegate_(delegate
.Pass()),
75 logo_cache_(new LogoCache(cached_logo_directory
)),
76 clock_(new base::DefaultClock()),
77 file_task_runner_(file_task_runner
),
78 background_task_runner_(background_task_runner
),
79 request_context_getter_(request_context_getter
),
80 weak_ptr_factory_(this) {}
82 LogoTracker::~LogoTracker() {
84 file_task_runner_
->PostTask(
85 FROM_HERE
, base::Bind(&DeleteLogoCacheOnFileThread
, logo_cache_
));
89 void LogoTracker::SetServerAPI(
91 const ParseLogoResponse
& parse_logo_response_func
,
92 const AppendQueryparamsToLogoURL
& append_queryparams_func
,
94 if (logo_url
== logo_url_
)
100 parse_logo_response_func_
= parse_logo_response_func
;
101 append_queryparams_func_
= append_queryparams_func
;
102 wants_cta_
= wants_cta
;
105 void LogoTracker::GetLogo(LogoObserver
* observer
) {
106 DCHECK(!logo_url_
.is_empty());
107 logo_observers_
.AddObserver(observer
);
111 base::PostTaskAndReplyWithResult(
112 file_task_runner_
.get(),
114 base::Bind(&GetLogoFromCacheOnFileThread
,
118 base::Bind(&LogoTracker::OnCachedLogoRead
,
119 weak_ptr_factory_
.GetWeakPtr()));
120 } else if (is_cached_logo_valid_
) {
121 observer
->OnLogoAvailable(cached_logo_
.get(), true);
125 void LogoTracker::RemoveObserver(LogoObserver
* observer
) {
126 logo_observers_
.RemoveObserver(observer
);
129 void LogoTracker::SetLogoCacheForTests(scoped_ptr
<LogoCache
> cache
) {
131 file_task_runner_
->PostTask(
132 FROM_HERE
, base::Bind(&DeleteLogoCacheOnFileThread
, logo_cache_
));
133 logo_cache_
= cache
.release();
136 void LogoTracker::SetClockForTests(scoped_ptr
<base::Clock
> clock
) {
137 clock_
= clock
.Pass();
140 void LogoTracker::ReturnToIdle() {
141 // Cancel the current asynchronous operation, if any.
143 weak_ptr_factory_
.InvalidateWeakPtrs();
147 cached_logo_
.reset();
148 is_cached_logo_valid_
= false;
151 FOR_EACH_OBSERVER(LogoObserver
, logo_observers_
, OnObserverRemoved());
152 logo_observers_
.Clear();
155 void LogoTracker::OnCachedLogoRead(scoped_ptr
<EncodedLogo
> cached_logo
) {
159 logo_delegate_
->DecodeUntrustedImage(
160 cached_logo
->encoded_image
,
161 base::Bind(&LogoTracker::OnCachedLogoAvailable
,
162 weak_ptr_factory_
.GetWeakPtr(),
163 cached_logo
->metadata
));
165 OnCachedLogoAvailable(LogoMetadata(), SkBitmap());
169 void LogoTracker::OnCachedLogoAvailable(const LogoMetadata
& metadata
,
170 const SkBitmap
& image
) {
173 if (!image
.isNull()) {
174 cached_logo_
.reset(new Logo());
175 cached_logo_
->metadata
= metadata
;
176 cached_logo_
->image
= image
;
178 is_cached_logo_valid_
= true;
179 Logo
* logo
= cached_logo_
.get();
180 FOR_EACH_OBSERVER(LogoObserver
, logo_observers_
, OnLogoAvailable(logo
, true));
184 void LogoTracker::SetCachedLogo(scoped_ptr
<EncodedLogo
> logo
) {
185 file_task_runner_
->PostTask(
187 base::Bind(&LogoCache::SetCachedLogo
,
188 base::Unretained(logo_cache_
),
189 base::Owned(logo
.release())));
192 void LogoTracker::SetCachedMetadata(const LogoMetadata
& metadata
) {
193 file_task_runner_
->PostTask(FROM_HERE
,
194 base::Bind(&LogoCache::UpdateCachedLogoMetadata
,
195 base::Unretained(logo_cache_
),
199 void LogoTracker::FetchLogo() {
204 std::string fingerprint
;
205 if (cached_logo_
&& !cached_logo_
->metadata
.fingerprint
.empty() &&
206 cached_logo_
->metadata
.expiration_time
>= clock_
->Now()) {
207 fingerprint
= cached_logo_
->metadata
.fingerprint
;
209 url
= append_queryparams_func_
.Run(logo_url_
, fingerprint
, wants_cta_
);
211 fetcher_
= net::URLFetcher::Create(url
, net::URLFetcher::GET
, this);
212 fetcher_
->SetRequestContext(request_context_getter_
.get());
216 void LogoTracker::OnFreshLogoParsed(scoped_ptr
<EncodedLogo
> logo
) {
220 logo
->metadata
.source_url
= logo_url_
.spec();
222 if (!logo
|| !logo
->encoded_image
.get()) {
223 OnFreshLogoAvailable(logo
.Pass(), SkBitmap());
225 // Store the value of logo->encoded_image for use below. This ensures that
226 // logo->encoded_image is evaulated before base::Passed(&logo), which sets
228 scoped_refptr
<base::RefCountedString
> encoded_image
= logo
->encoded_image
;
229 logo_delegate_
->DecodeUntrustedImage(
231 base::Bind(&LogoTracker::OnFreshLogoAvailable
,
232 weak_ptr_factory_
.GetWeakPtr(),
233 base::Passed(&logo
)));
237 void LogoTracker::OnFreshLogoAvailable(scoped_ptr
<EncodedLogo
> encoded_logo
,
238 const SkBitmap
& image
) {
241 if (encoded_logo
&& !encoded_logo
->encoded_image
.get() && cached_logo_
&&
242 !encoded_logo
->metadata
.fingerprint
.empty() &&
243 encoded_logo
->metadata
.fingerprint
==
244 cached_logo_
->metadata
.fingerprint
) {
245 // The cached logo was revalidated, i.e. its fingerprint was verified.
246 // mime_type isn't sent when revalidating, so copy it from the cached logo.
247 encoded_logo
->metadata
.mime_type
= cached_logo_
->metadata
.mime_type
;
248 SetCachedMetadata(encoded_logo
->metadata
);
249 } else if (encoded_logo
&& image
.isNull()) {
250 // Image decoding failed. Do nothing.
252 scoped_ptr
<Logo
> logo
;
253 // Check if the server returned a valid, non-empty response.
255 DCHECK(!image
.isNull());
256 logo
.reset(new Logo());
257 logo
->metadata
= encoded_logo
->metadata
;
261 // Notify observers if a new logo was fetched, or if the new logo is NULL
262 // but the cached logo was non-NULL.
263 if (logo
|| cached_logo_
) {
264 FOR_EACH_OBSERVER(LogoObserver
,
266 OnLogoAvailable(logo
.get(), false));
267 SetCachedLogo(encoded_logo
.Pass());
274 void LogoTracker::OnURLFetchComplete(const net::URLFetcher
* source
) {
276 scoped_ptr
<net::URLFetcher
> cleanup_fetcher(fetcher_
.release());
278 if (!source
->GetStatus().is_success() || (source
->GetResponseCode() != 200)) {
283 scoped_ptr
<std::string
> response(new std::string());
284 source
->GetResponseAsString(response
.get());
285 base::Time response_time
= clock_
->Now();
287 base::PostTaskAndReplyWithResult(
288 background_task_runner_
.get(),
291 parse_logo_response_func_
, base::Passed(&response
), response_time
),
292 base::Bind(&LogoTracker::OnFreshLogoParsed
,
293 weak_ptr_factory_
.GetWeakPtr()));
296 void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher
* source
,
299 if (total
> kMaxDownloadBytes
|| current
> kMaxDownloadBytes
) {
300 LOG(WARNING
) << "Search provider logo exceeded download size limit";
305 } // namespace search_provider_logos