// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/search_provider_logos/logo_tracker.h"
#include <algorithm>
#include "base/message_loop/message_loop.h"
#include "base/task_runner_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
namespace search_provider_logos {
namespace {
const int64 kMaxDownloadBytes = 1024 * 1024;
//const int kDecodeLogoTimeoutSeconds = 30;
// Returns whether the metadata for the cached logo indicates that the logo is
// OK to show, i.e. it's not expired or it's allowed to be shown temporarily
// after expiration.
bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) {
base::TimeDelta offset =
base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2);
base::Time distant_past = now - offset;
base::Time distant_future = now + offset;
// Sanity check so logos aren't accidentally cached forever.
if (metadata.expiration_time < distant_past ||
metadata.expiration_time > distant_future) {
return false;
}
return metadata.can_show_after_expiration || metadata.expiration_time >= now;
}
// Reads the logo from the cache and returns it. Returns NULL if the cache is
// empty, corrupt, expired, or doesn't apply to the current logo URL.
scoped_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache,
const GURL& logo_url,
base::Time now) {
const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata();
if (!metadata)
return scoped_ptr<EncodedLogo>();
if (metadata->source_url != logo_url.spec() ||
!IsLogoOkToShow(*metadata, now)) {
logo_cache->SetCachedLogo(NULL);
return scoped_ptr<EncodedLogo>();
}
return logo_cache->GetCachedLogo().Pass();
}
void DeleteLogoCacheOnFileThread(LogoCache* logo_cache) {
delete logo_cache;
}
} // namespace
LogoTracker::LogoTracker(
base::FilePath cached_logo_directory,
scoped_refptr<base::SequencedTaskRunner> file_task_runner,
scoped_refptr<base::TaskRunner> background_task_runner,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
scoped_ptr<LogoDelegate> delegate)
: is_idle_(true),
is_cached_logo_valid_(false),
logo_delegate_(delegate.Pass()),
logo_cache_(new LogoCache(cached_logo_directory)),
clock_(new base::DefaultClock()),
file_task_runner_(file_task_runner),
background_task_runner_(background_task_runner),
request_context_getter_(request_context_getter),
weak_ptr_factory_(this) {}
LogoTracker::~LogoTracker() {
ReturnToIdle();
file_task_runner_->PostTask(
FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
logo_cache_ = NULL;
}
void LogoTracker::SetServerAPI(
const GURL& logo_url,
const ParseLogoResponse& parse_logo_response_func,
const AppendFingerprintToLogoURL& append_fingerprint_func) {
if (logo_url == logo_url_)
return;
ReturnToIdle();
logo_url_ = logo_url;
parse_logo_response_func_ = parse_logo_response_func;
append_fingerprint_func_ = append_fingerprint_func;
}
void LogoTracker::GetLogo(LogoObserver* observer) {
DCHECK(!logo_url_.is_empty());
logo_observers_.AddObserver(observer);
if (is_idle_) {
is_idle_ = false;
base::PostTaskAndReplyWithResult(
file_task_runner_.get(),
FROM_HERE,
base::Bind(&GetLogoFromCacheOnFileThread,
logo_cache_,
logo_url_,
clock_->Now()),
base::Bind(&LogoTracker::OnCachedLogoRead,
weak_ptr_factory_.GetWeakPtr()));
} else if (is_cached_logo_valid_) {
observer->OnLogoAvailable(cached_logo_.get(), true);
}
}
void LogoTracker::RemoveObserver(LogoObserver* observer) {
logo_observers_.RemoveObserver(observer);
}
void LogoTracker::SetLogoCacheForTests(scoped_ptr<LogoCache> cache) {
DCHECK(cache);
file_task_runner_->PostTask(
FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
logo_cache_ = cache.release();
}
void LogoTracker::SetClockForTests(scoped_ptr<base::Clock> clock) {
clock_ = clock.Pass();
}
void LogoTracker::ReturnToIdle() {
// Cancel the current asynchronous operation, if any.
fetcher_.reset();
weak_ptr_factory_.InvalidateWeakPtrs();
// Reset state.
is_idle_ = true;
cached_logo_.reset();
is_cached_logo_valid_ = false;
// Clear obsevers.
FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnObserverRemoved());
logo_observers_.Clear();
}
void LogoTracker::OnCachedLogoRead(scoped_ptr<EncodedLogo> cached_logo) {
DCHECK(!is_idle_);
if (cached_logo) {
logo_delegate_->DecodeUntrustedImage(
cached_logo->encoded_image,
base::Bind(&LogoTracker::OnCachedLogoAvailable,
weak_ptr_factory_.GetWeakPtr(),
cached_logo->metadata));
} else {
OnCachedLogoAvailable(LogoMetadata(), SkBitmap());
}
}
void LogoTracker::OnCachedLogoAvailable(const LogoMetadata& metadata,
const SkBitmap& image) {
DCHECK(!is_idle_);
if (!image.isNull()) {
cached_logo_.reset(new Logo());
cached_logo_->metadata = metadata;
cached_logo_->image = image;
}
is_cached_logo_valid_ = true;
Logo* logo = cached_logo_.get();
FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo, true));
FetchLogo();
}
void LogoTracker::SetCachedLogo(scoped_ptr<EncodedLogo> logo) {
file_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LogoCache::SetCachedLogo,
base::Unretained(logo_cache_),
base::Owned(logo.release())));
}
void LogoTracker::SetCachedMetadata(const LogoMetadata& metadata) {
file_task_runner_->PostTask(FROM_HERE,
base::Bind(&LogoCache::UpdateCachedLogoMetadata,
base::Unretained(logo_cache_),
metadata));
}
void LogoTracker::FetchLogo() {
DCHECK(!fetcher_);
DCHECK(!is_idle_);
GURL url;
if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() &&
cached_logo_->metadata.expiration_time >= clock_->Now()) {
url = append_fingerprint_func_.Run(logo_url_,
cached_logo_->metadata.fingerprint);
} else {
url = logo_url_;
}
fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
fetcher_->SetRequestContext(request_context_getter_.get());
fetcher_->Start();
}
void LogoTracker::OnFreshLogoParsed(scoped_ptr<EncodedLogo> logo) {
DCHECK(!is_idle_);
if (logo)
logo->metadata.source_url = logo_url_.spec();
if (!logo || !logo->encoded_image.get()) {
OnFreshLogoAvailable(logo.Pass(), SkBitmap());
} else {
// Store the value of logo->encoded_image for use below. This ensures that
// logo->encoded_image is evaulated before base::Passed(&logo), which sets
// logo to NULL.
scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image;
logo_delegate_->DecodeUntrustedImage(
encoded_image,
base::Bind(&LogoTracker::OnFreshLogoAvailable,
weak_ptr_factory_.GetWeakPtr(),
base::Passed(&logo)));
}
}
void LogoTracker::OnFreshLogoAvailable(scoped_ptr<EncodedLogo> encoded_logo,
const SkBitmap& image) {
DCHECK(!is_idle_);
if (encoded_logo && !encoded_logo->encoded_image.get() && cached_logo_ &&
!encoded_logo->metadata.fingerprint.empty() &&
encoded_logo->metadata.fingerprint ==
cached_logo_->metadata.fingerprint) {
// The cached logo was revalidated, i.e. its fingerprint was verified.
SetCachedMetadata(encoded_logo->metadata);
} else if (encoded_logo && image.isNull()) {
// Image decoding failed. Do nothing.
} else {
scoped_ptr<Logo> logo;
// Check if the server returned a valid, non-empty response.
if (encoded_logo) {
DCHECK(!image.isNull());
logo.reset(new Logo());
logo->metadata = encoded_logo->metadata;
logo->image = image;
}
// Notify observers if a new logo was fetched, or if the new logo is NULL
// but the cached logo was non-NULL.
if (logo || cached_logo_) {
FOR_EACH_OBSERVER(LogoObserver,
logo_observers_,
OnLogoAvailable(logo.get(), false));
SetCachedLogo(encoded_logo.Pass());
}
}
ReturnToIdle();
}
void LogoTracker::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK(!is_idle_);
scoped_ptr<net::URLFetcher> cleanup_fetcher(fetcher_.release());
if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
ReturnToIdle();
return;
}
scoped_ptr<std::string> response(new std::string());
source->GetResponseAsString(response.get());
base::Time response_time = clock_->Now();
base::PostTaskAndReplyWithResult(
background_task_runner_.get(),
FROM_HERE,
base::Bind(
parse_logo_response_func_, base::Passed(&response), response_time),
base::Bind(&LogoTracker::OnFreshLogoParsed,
weak_ptr_factory_.GetWeakPtr()));
}
void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher* source,
int64 current,
int64 total) {
if (total > kMaxDownloadBytes || current > kMaxDownloadBytes) {
LOG(WARNING) << "Search provider logo exceeded download size limit";
ReturnToIdle();
}
}
} // namespace search_provider_logos