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 AppendFingerprintToLogoURL
& append_fingerprint_func
) {
93 if (logo_url
== logo_url_
)
99 parse_logo_response_func_
= parse_logo_response_func
;
100 append_fingerprint_func_
= append_fingerprint_func
;
103 void LogoTracker::GetLogo(LogoObserver
* observer
) {
104 DCHECK(!logo_url_
.is_empty());
105 logo_observers_
.AddObserver(observer
);
109 base::PostTaskAndReplyWithResult(
112 base::Bind(&GetLogoFromCacheOnFileThread
,
116 base::Bind(&LogoTracker::OnCachedLogoRead
,
117 weak_ptr_factory_
.GetWeakPtr()));
118 } else if (is_cached_logo_valid_
) {
119 observer
->OnLogoAvailable(cached_logo_
.get(), true);
123 void LogoTracker::RemoveObserver(LogoObserver
* observer
) {
124 logo_observers_
.RemoveObserver(observer
);
127 void LogoTracker::SetLogoCacheForTests(scoped_ptr
<LogoCache
> cache
) {
129 file_task_runner_
->PostTask(
130 FROM_HERE
, base::Bind(&DeleteLogoCacheOnFileThread
, logo_cache_
));
131 logo_cache_
= cache
.release();
134 void LogoTracker::SetClockForTests(scoped_ptr
<base::Clock
> clock
) {
135 clock_
= clock
.Pass();
138 void LogoTracker::ReturnToIdle() {
139 // Cancel the current asynchronous operation, if any.
141 weak_ptr_factory_
.InvalidateWeakPtrs();
145 cached_logo_
.reset();
146 is_cached_logo_valid_
= false;
149 FOR_EACH_OBSERVER(LogoObserver
, logo_observers_
, OnObserverRemoved());
150 logo_observers_
.Clear();
153 void LogoTracker::OnCachedLogoRead(scoped_ptr
<EncodedLogo
> cached_logo
) {
157 logo_delegate_
->DecodeUntrustedImage(
158 cached_logo
->encoded_image
,
159 base::Bind(&LogoTracker::OnCachedLogoAvailable
,
160 weak_ptr_factory_
.GetWeakPtr(),
161 cached_logo
->metadata
));
163 OnCachedLogoAvailable(LogoMetadata(), SkBitmap());
167 void LogoTracker::OnCachedLogoAvailable(const LogoMetadata
& metadata
,
168 const SkBitmap
& image
) {
171 if (!image
.isNull()) {
172 cached_logo_
.reset(new Logo());
173 cached_logo_
->metadata
= metadata
;
174 cached_logo_
->image
= image
;
176 is_cached_logo_valid_
= true;
177 Logo
* logo
= cached_logo_
.get();
178 FOR_EACH_OBSERVER(LogoObserver
, logo_observers_
, OnLogoAvailable(logo
, true));
182 void LogoTracker::SetCachedLogo(scoped_ptr
<EncodedLogo
> logo
) {
183 file_task_runner_
->PostTask(
185 base::Bind(&LogoCache::SetCachedLogo
,
186 base::Unretained(logo_cache_
),
187 base::Owned(logo
.release())));
190 void LogoTracker::SetCachedMetadata(const LogoMetadata
& metadata
) {
191 file_task_runner_
->PostTask(FROM_HERE
,
192 base::Bind(&LogoCache::UpdateCachedLogoMetadata
,
193 base::Unretained(logo_cache_
),
197 void LogoTracker::FetchLogo() {
202 if (cached_logo_
&& !cached_logo_
->metadata
.fingerprint
.empty() &&
203 cached_logo_
->metadata
.expiration_time
>= clock_
->Now()) {
204 url
= append_fingerprint_func_
.Run(logo_url_
,
205 cached_logo_
->metadata
.fingerprint
);
210 fetcher_
.reset(net::URLFetcher::Create(url
, net::URLFetcher::GET
, this));
211 fetcher_
->SetRequestContext(request_context_getter_
);
215 void LogoTracker::OnFreshLogoParsed(scoped_ptr
<EncodedLogo
> logo
) {
219 logo
->metadata
.source_url
= logo_url_
.spec();
221 if (!logo
|| !logo
->encoded_image
) {
222 OnFreshLogoAvailable(logo
.Pass(), SkBitmap());
224 // Store the value of logo->encoded_image for use below. This ensures that
225 // logo->encoded_image is evaulated before base::Passed(&logo), which sets
227 scoped_refptr
<base::RefCountedString
> encoded_image
= logo
->encoded_image
;
228 logo_delegate_
->DecodeUntrustedImage(
230 base::Bind(&LogoTracker::OnFreshLogoAvailable
,
231 weak_ptr_factory_
.GetWeakPtr(),
232 base::Passed(&logo
)));
236 void LogoTracker::OnFreshLogoAvailable(scoped_ptr
<EncodedLogo
> encoded_logo
,
237 const SkBitmap
& image
) {
240 if (encoded_logo
&& !encoded_logo
->encoded_image
&& cached_logo_
&&
241 !encoded_logo
->metadata
.fingerprint
.empty() &&
242 encoded_logo
->metadata
.fingerprint
==
243 cached_logo_
->metadata
.fingerprint
) {
244 // The cached logo was revalidated, i.e. its fingerprint was verified.
245 SetCachedMetadata(encoded_logo
->metadata
);
246 } else if (encoded_logo
&& image
.isNull()) {
247 // Image decoding failed. Do nothing.
249 scoped_ptr
<Logo
> logo
;
250 // Check if the server returned a valid, non-empty response.
252 DCHECK(!image
.isNull());
253 logo
.reset(new Logo());
254 logo
->metadata
= encoded_logo
->metadata
;
258 // Notify observers if a new logo was fetched, or if the new logo is NULL
259 // but the cached logo was non-NULL.
260 if (logo
|| cached_logo_
) {
261 FOR_EACH_OBSERVER(LogoObserver
,
263 OnLogoAvailable(logo
.get(), false));
264 SetCachedLogo(encoded_logo
.Pass());
271 void LogoTracker::OnURLFetchComplete(const net::URLFetcher
* source
) {
273 scoped_ptr
<net::URLFetcher
> cleanup_fetcher(fetcher_
.release());
275 if (!source
->GetStatus().is_success() || (source
->GetResponseCode() != 200)) {
280 scoped_ptr
<std::string
> response(new std::string());
281 source
->GetResponseAsString(response
.get());
282 base::Time response_time
= clock_
->Now();
284 base::PostTaskAndReplyWithResult(
285 background_task_runner_
,
287 base::Bind(parse_logo_response_func_
,
288 base::Passed(&response
),
290 base::Bind(&LogoTracker::OnFreshLogoParsed
,
291 weak_ptr_factory_
.GetWeakPtr()));
294 void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher
* source
,
297 if (total
> kMaxDownloadBytes
|| current
> kMaxDownloadBytes
) {
298 LOG(WARNING
) << "Search provider logo exceeded download size limit";
303 } // namespace search_provider_logos