blob: f350805386385043d744471ef262abc0c05b5c76 [file] [log] [blame]
// 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_cache.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
namespace search_provider_logos {
namespace {
// The cached logo metadata is persisted as JSON using these keys.
const char kSourceUrlKey[] = "url";
const char kExpirationTimeKey[] = "expiration_time";
const char kCanShowAfterExpirationKey[] = "can_show_after_expiration";
const char kFingerprintKey[] = "fingerprint";
const char kTypeKey[] = "type";
const char kOnClickURLKey[] = "on_click_url";
const char kFullPageURLKey[] = "full_page_url";
const char kAltTextKey[] = "alt_text";
const char kMimeTypeKey[] = "mime_type";
const char kNumBytesKey[] = "num_bytes";
const char kAnimatedUrlKey[] = "animated_url";
const char kLogUrlKey[] = "log_url";
const char kCtaLogUrlKey[] = "cta_log_url";
const char kShortLinkKey[] = "short_link";
const char kIframeWidthPx[] = "iframe_width_px";
const char kIframeHeightPx[] = "iframe_height_px";
const char kShareButtonX[] = "share_button_x";
const char kShareButtonY[] = "share_button_y";
const char kShareButtonOpacity[] = "share_button_opacity";
const char kShareButtonIcon[] = "share_button_icon";
const char kShareButtonBg[] = "share_button_bg";
const char kSimpleType[] = "SIMPLE";
const char kAnimatedType[] = "ANIMATED";
const char kInteractiveType[] = "INTERACTIVE";
bool GetTimeValue(const base::DictionaryValue& dict,
const std::string& key,
base::Time* time) {
std::string str;
int64_t internal_time_value;
if (dict.GetString(key, &str) &&
base::StringToInt64(str, &internal_time_value)) {
*time = base::Time::FromInternalValue(internal_time_value);
return true;
}
return false;
}
void SetTimeValue(base::DictionaryValue& dict,
const std::string& key,
const base::Time& time) {
int64_t internal_time_value = time.ToInternalValue();
dict.SetString(key, base::Int64ToString(internal_time_value));
}
LogoType LogoTypeFromString(base::StringPiece type) {
if (type == kSimpleType) {
return LogoType::SIMPLE;
}
if (type == kAnimatedType) {
return LogoType::ANIMATED;
}
if (type == kInteractiveType) {
return LogoType::INTERACTIVE;
}
LOG(WARNING) << "invalid type " << type;
return LogoType::SIMPLE;
}
std::string LogoTypeToString(LogoType type) {
switch (type) {
case LogoType::SIMPLE:
return kSimpleType;
case LogoType::ANIMATED:
return kAnimatedType;
case LogoType::INTERACTIVE:
return kInteractiveType;
}
NOTREACHED();
return "";
}
} // namespace
LogoCache::LogoCache(const base::FilePath& cache_directory)
: cache_directory_(cache_directory),
metadata_is_valid_(false) {
// The LogoCache can be constructed on any thread, as long as it's used
// on a single sequence after construction.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
LogoCache::~LogoCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void LogoCache::UpdateCachedLogoMetadata(const LogoMetadata& metadata) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_);
DCHECK_EQ(metadata_->fingerprint, metadata.fingerprint);
UpdateMetadata(std::make_unique<LogoMetadata>(metadata));
WriteMetadata();
}
const LogoMetadata* LogoCache::GetCachedLogoMetadata() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ReadMetadataIfNeeded();
return metadata_.get();
}
void LogoCache::SetCachedLogo(const EncodedLogo* logo) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!logo) {
UpdateMetadata(nullptr);
DeleteLogoAndMetadata();
return;
}
logo_num_bytes_ =
logo->encoded_image ? static_cast<int>(logo->encoded_image->size()) : 0;
UpdateMetadata(std::make_unique<LogoMetadata>(logo->metadata));
WriteLogo(logo->encoded_image);
}
std::unique_ptr<EncodedLogo> LogoCache::GetCachedLogo() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ReadMetadataIfNeeded();
if (!metadata_)
return nullptr;
scoped_refptr<base::RefCountedString> encoded_image;
if (logo_num_bytes_ != 0) {
encoded_image = new base::RefCountedString();
if (!base::ReadFileToString(GetLogoPath(), &encoded_image->data())) {
UpdateMetadata(nullptr);
return nullptr;
}
if (encoded_image->size() != static_cast<size_t>(logo_num_bytes_)) {
// Delete corrupt metadata and logo.
DeleteLogoAndMetadata();
UpdateMetadata(nullptr);
return nullptr;
}
}
std::unique_ptr<EncodedLogo> logo(new EncodedLogo());
logo->encoded_image = encoded_image;
logo->metadata = *metadata_;
return logo;
}
// static
std::unique_ptr<LogoMetadata> LogoCache::LogoMetadataFromString(
const std::string& str,
int* logo_num_bytes) {
std::unique_ptr<base::Value> value = base::JSONReader::Read(str);
base::DictionaryValue* dict;
if (!value || !value->GetAsDictionary(&dict))
return nullptr;
std::unique_ptr<LogoMetadata> metadata(new LogoMetadata());
std::string source_url;
std::string type;
std::string on_click_url;
std::string full_page_url;
std::string animated_url;
std::string log_url;
std::string cta_log_url;
std::string short_link;
if (!dict->GetString(kSourceUrlKey, &source_url) ||
!dict->GetString(kFingerprintKey, &metadata->fingerprint) ||
!dict->GetString(kTypeKey, &type) ||
!dict->GetString(kOnClickURLKey, &on_click_url) ||
!dict->GetString(kFullPageURLKey, &full_page_url) ||
!dict->GetString(kAltTextKey, &metadata->alt_text) ||
!dict->GetString(kAnimatedUrlKey, &animated_url) ||
!dict->GetString(kLogUrlKey, &log_url) ||
!dict->GetString(kCtaLogUrlKey, &cta_log_url) ||
!dict->GetString(kShortLinkKey, &short_link) ||
!dict->GetString(kMimeTypeKey, &metadata->mime_type) ||
!dict->GetBoolean(kCanShowAfterExpirationKey,
&metadata->can_show_after_expiration) ||
!dict->GetInteger(kNumBytesKey, logo_num_bytes) ||
!dict->GetInteger(kShareButtonX, &metadata->share_button_x) ||
!dict->GetInteger(kShareButtonY, &metadata->share_button_y) ||
!dict->GetDouble(kShareButtonOpacity, &metadata->share_button_opacity) ||
!dict->GetString(kShareButtonIcon, &metadata->share_button_icon) ||
!dict->GetString(kShareButtonBg, &metadata->share_button_bg) ||
!dict->GetInteger(kIframeWidthPx, &metadata->iframe_width_px) ||
!dict->GetInteger(kIframeHeightPx, &metadata->iframe_height_px) ||
!GetTimeValue(*dict, kExpirationTimeKey, &metadata->expiration_time)) {
return nullptr;
}
metadata->type = LogoTypeFromString(type);
metadata->source_url = GURL(source_url);
metadata->on_click_url = GURL(on_click_url);
metadata->full_page_url = GURL(full_page_url);
metadata->animated_url = GURL(animated_url);
metadata->log_url = GURL(log_url);
metadata->cta_log_url = GURL(cta_log_url);
metadata->short_link = GURL(short_link);
return metadata;
}
// static
void LogoCache::LogoMetadataToString(const LogoMetadata& metadata,
int num_bytes,
std::string* str) {
base::DictionaryValue dict;
dict.SetString(kSourceUrlKey, metadata.source_url.spec());
dict.SetString(kFingerprintKey, metadata.fingerprint);
dict.SetString(kTypeKey, LogoTypeToString(metadata.type));
dict.SetString(kOnClickURLKey, metadata.on_click_url.spec());
dict.SetString(kFullPageURLKey, metadata.full_page_url.spec());
dict.SetString(kAltTextKey, metadata.alt_text);
dict.SetString(kAnimatedUrlKey, metadata.animated_url.spec());
dict.SetString(kLogUrlKey, metadata.log_url.spec());
dict.SetString(kCtaLogUrlKey, metadata.cta_log_url.spec());
dict.SetString(kShortLinkKey, metadata.short_link.spec());
dict.SetString(kMimeTypeKey, metadata.mime_type);
dict.SetBoolean(kCanShowAfterExpirationKey,
metadata.can_show_after_expiration);
dict.SetInteger(kNumBytesKey, num_bytes);
dict.SetInteger(kShareButtonX, metadata.share_button_x);
dict.SetInteger(kShareButtonY, metadata.share_button_y);
dict.SetDouble(kShareButtonOpacity, metadata.share_button_opacity);
dict.SetString(kShareButtonIcon, metadata.share_button_icon);
dict.SetString(kShareButtonBg, metadata.share_button_bg);
dict.SetInteger(kIframeWidthPx, metadata.iframe_width_px);
dict.SetInteger(kIframeHeightPx, metadata.iframe_height_px);
SetTimeValue(dict, kExpirationTimeKey, metadata.expiration_time);
base::JSONWriter::Write(dict, str);
}
base::FilePath LogoCache::GetLogoPath() {
return cache_directory_.Append(FILE_PATH_LITERAL("logo"));
}
base::FilePath LogoCache::GetMetadataPath() {
return cache_directory_.Append(FILE_PATH_LITERAL("metadata"));
}
void LogoCache::UpdateMetadata(std::unique_ptr<LogoMetadata> metadata) {
metadata_ = std::move(metadata);
metadata_is_valid_ = true;
}
void LogoCache::ReadMetadataIfNeeded() {
if (metadata_is_valid_)
return;
std::unique_ptr<LogoMetadata> metadata;
base::FilePath metadata_path = GetMetadataPath();
std::string str;
if (base::ReadFileToString(metadata_path, &str)) {
metadata = LogoMetadataFromString(str, &logo_num_bytes_);
if (!metadata) {
// Delete corrupt metadata and logo.
DeleteLogoAndMetadata();
}
}
UpdateMetadata(std::move(metadata));
}
void LogoCache::WriteMetadata() {
if (!EnsureCacheDirectoryExists())
return;
std::string str;
LogoMetadataToString(*metadata_, logo_num_bytes_, &str);
base::WriteFile(GetMetadataPath(), str.data(), static_cast<int>(str.size()));
}
void LogoCache::WriteLogo(scoped_refptr<base::RefCountedMemory> encoded_image) {
if (!EnsureCacheDirectoryExists())
return;
if (!metadata_) {
DeleteLogoAndMetadata();
return;
}
// To minimize the chances of ending up in an undetectably broken state:
// First, delete the metadata file, then update the logo file, then update the
// metadata file.
base::FilePath logo_path = GetLogoPath();
base::FilePath metadata_path = GetMetadataPath();
if (!base::DeleteFile(metadata_path, false))
return;
if (encoded_image &&
base::WriteFile(logo_path, encoded_image->front_as<char>(),
static_cast<int>(encoded_image->size())) == -1) {
base::DeleteFile(logo_path, false);
return;
}
WriteMetadata();
}
void LogoCache::DeleteLogoAndMetadata() {
base::DeleteFile(GetLogoPath(), false);
base::DeleteFile(GetMetadataPath(), false);
}
bool LogoCache::EnsureCacheDirectoryExists() {
if (base::DirectoryExists(cache_directory_))
return true;
return base::CreateDirectory(cache_directory_);
}
} // namespace search_provider_logos