Home | History | Annotate | Download | only in search_provider_logos
      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.
      4 
      5 #include "components/search_provider_logos/logo_cache.h"
      6 
      7 #include "base/files/file_util.h"
      8 #include "base/json/json_reader.h"
      9 #include "base/json/json_writer.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/values.h"
     12 
     13 namespace {
     14 
     15 // The cached logo metadata is persisted as JSON using these keys.
     16 const char kSourceUrlKey[] = "url";
     17 const char kExpirationTimeKey[] = "expiration_time";
     18 const char kCanShowAfterExpirationKey[] = "can_show_after_expiration";
     19 const char kFingerprintKey[] = "fingerprint";
     20 const char kOnClickURLKey[] = "on_click_url";
     21 const char kAltTextKey[] = "alt_text";
     22 const char kMimeTypeKey[] = "mime_type";
     23 const char kNumBytesKey[] = "num_bytes";
     24 
     25 bool GetTimeValue(const base::DictionaryValue& dict,
     26                   const std::string& key,
     27                   base::Time* time) {
     28   std::string str;
     29   int64 internal_time_value;
     30   if (dict.GetString(key, &str) &&
     31       base::StringToInt64(str, &internal_time_value)) {
     32     *time = base::Time::FromInternalValue(internal_time_value);
     33     return true;
     34   }
     35   return false;
     36 }
     37 
     38 void SetTimeValue(base::DictionaryValue& dict,
     39                   const std::string& key,
     40                   const base::Time& time) {
     41   int64 internal_time_value = time.ToInternalValue();
     42   dict.SetString(key, base::Int64ToString(internal_time_value));
     43 }
     44 
     45 }  // namespace
     46 
     47 namespace search_provider_logos {
     48 
     49 LogoCache::LogoCache(const base::FilePath& cache_directory)
     50     : cache_directory_(cache_directory),
     51       metadata_is_valid_(false) {
     52   // The LogoCache can be constructed on any thread, as long as it's used
     53   // on a single thread after construction.
     54   thread_checker_.DetachFromThread();
     55 }
     56 
     57 LogoCache::~LogoCache() {
     58   DCHECK(thread_checker_.CalledOnValidThread());
     59 }
     60 
     61 void LogoCache::UpdateCachedLogoMetadata(const LogoMetadata& metadata) {
     62   DCHECK(thread_checker_.CalledOnValidThread());
     63   DCHECK(metadata_);
     64   DCHECK_EQ(metadata_->fingerprint, metadata.fingerprint);
     65 
     66   UpdateMetadata(make_scoped_ptr(new LogoMetadata(metadata)));
     67   WriteMetadata();
     68 }
     69 
     70 const LogoMetadata* LogoCache::GetCachedLogoMetadata() {
     71   DCHECK(thread_checker_.CalledOnValidThread());
     72   ReadMetadataIfNeeded();
     73   return metadata_.get();
     74 }
     75 
     76 void LogoCache::SetCachedLogo(const EncodedLogo* logo) {
     77   DCHECK(thread_checker_.CalledOnValidThread());
     78   scoped_ptr<LogoMetadata> metadata;
     79   if (logo) {
     80     metadata.reset(new LogoMetadata(logo->metadata));
     81     logo_num_bytes_ = static_cast<int>(logo->encoded_image->size());
     82   }
     83   UpdateMetadata(metadata.Pass());
     84   WriteLogo(logo ? logo->encoded_image : NULL);
     85 }
     86 
     87 scoped_ptr<EncodedLogo> LogoCache::GetCachedLogo() {
     88   DCHECK(thread_checker_.CalledOnValidThread());
     89 
     90   ReadMetadataIfNeeded();
     91   if (!metadata_)
     92     return scoped_ptr<EncodedLogo>();
     93 
     94   scoped_refptr<base::RefCountedString> encoded_image =
     95       new base::RefCountedString();
     96   if (!base::ReadFileToString(GetLogoPath(), &encoded_image->data())) {
     97     UpdateMetadata(scoped_ptr<LogoMetadata>());
     98     return scoped_ptr<EncodedLogo>();
     99   }
    100 
    101   if (encoded_image->size() != static_cast<size_t>(logo_num_bytes_)) {
    102     // Delete corrupt metadata and logo.
    103     DeleteLogoAndMetadata();
    104     UpdateMetadata(scoped_ptr<LogoMetadata>());
    105     return scoped_ptr<EncodedLogo>();
    106   }
    107 
    108   scoped_ptr<EncodedLogo> logo(new EncodedLogo());
    109   logo->encoded_image = encoded_image;
    110   logo->metadata = *metadata_;
    111   return logo.Pass();
    112 }
    113 
    114 // static
    115 scoped_ptr<LogoMetadata> LogoCache::LogoMetadataFromString(
    116     const std::string& str, int* logo_num_bytes) {
    117   scoped_ptr<base::Value> value(base::JSONReader::Read(str));
    118   base::DictionaryValue* dict;
    119   if (!value || !value->GetAsDictionary(&dict))
    120     return scoped_ptr<LogoMetadata>();
    121 
    122   scoped_ptr<LogoMetadata> metadata(new LogoMetadata());
    123   if (!dict->GetString(kSourceUrlKey, &metadata->source_url) ||
    124       !dict->GetString(kFingerprintKey, &metadata->fingerprint) ||
    125       !dict->GetString(kOnClickURLKey, &metadata->on_click_url) ||
    126       !dict->GetString(kAltTextKey, &metadata->alt_text) ||
    127       !dict->GetString(kMimeTypeKey, &metadata->mime_type) ||
    128       !dict->GetBoolean(kCanShowAfterExpirationKey,
    129                         &metadata->can_show_after_expiration) ||
    130       !dict->GetInteger(kNumBytesKey, logo_num_bytes) ||
    131       !GetTimeValue(*dict, kExpirationTimeKey, &metadata->expiration_time)) {
    132     return scoped_ptr<LogoMetadata>();
    133   }
    134 
    135   return metadata.Pass();
    136 }
    137 
    138 // static
    139 void LogoCache::LogoMetadataToString(const LogoMetadata& metadata,
    140                                      int num_bytes,
    141                                      std::string* str) {
    142   base::DictionaryValue dict;
    143   dict.SetString(kSourceUrlKey, metadata.source_url);
    144   dict.SetString(kFingerprintKey, metadata.fingerprint);
    145   dict.SetString(kOnClickURLKey, metadata.on_click_url);
    146   dict.SetString(kAltTextKey, metadata.alt_text);
    147   dict.SetString(kMimeTypeKey, metadata.mime_type);
    148   dict.SetBoolean(kCanShowAfterExpirationKey,
    149                   metadata.can_show_after_expiration);
    150   dict.SetInteger(kNumBytesKey, num_bytes);
    151   SetTimeValue(dict, kExpirationTimeKey, metadata.expiration_time);
    152   base::JSONWriter::Write(&dict, str);
    153 }
    154 
    155 base::FilePath LogoCache::GetLogoPath() {
    156   return cache_directory_.Append(FILE_PATH_LITERAL("logo"));
    157 }
    158 
    159 base::FilePath LogoCache::GetMetadataPath() {
    160   return cache_directory_.Append(FILE_PATH_LITERAL("metadata"));
    161 }
    162 
    163 void LogoCache::UpdateMetadata(scoped_ptr<LogoMetadata> metadata) {
    164   metadata_ = metadata.Pass();
    165   metadata_is_valid_ = true;
    166 }
    167 
    168 void LogoCache::ReadMetadataIfNeeded() {
    169   if (metadata_is_valid_)
    170     return;
    171 
    172   scoped_ptr<LogoMetadata> metadata;
    173   base::FilePath metadata_path = GetMetadataPath();
    174   std::string str;
    175   if (base::ReadFileToString(metadata_path, &str)) {
    176     metadata = LogoMetadataFromString(str, &logo_num_bytes_);
    177     if (!metadata) {
    178       // Delete corrupt metadata and logo.
    179       DeleteLogoAndMetadata();
    180     }
    181   }
    182 
    183   UpdateMetadata(metadata.Pass());
    184 }
    185 
    186 void LogoCache::WriteMetadata() {
    187   if (!EnsureCacheDirectoryExists())
    188     return;
    189 
    190   std::string str;
    191   LogoMetadataToString(*metadata_, logo_num_bytes_, &str);
    192   base::WriteFile(GetMetadataPath(), str.data(), static_cast<int>(str.size()));
    193 }
    194 
    195 void LogoCache::WriteLogo(scoped_refptr<base::RefCountedMemory> encoded_image) {
    196   if (!EnsureCacheDirectoryExists())
    197     return;
    198 
    199   if (!metadata_ || !encoded_image.get()) {
    200     DeleteLogoAndMetadata();
    201     return;
    202   }
    203 
    204   // To minimize the chances of ending up in an undetectably broken state:
    205   // First, delete the metadata file, then update the logo file, then update the
    206   // metadata file.
    207   base::FilePath logo_path = GetLogoPath();
    208   base::FilePath metadata_path = GetMetadataPath();
    209 
    210   if (!base::DeleteFile(metadata_path, false))
    211     return;
    212 
    213   if (base::WriteFile(
    214           logo_path,
    215           encoded_image->front_as<char>(),
    216           static_cast<int>(encoded_image->size())) == -1) {
    217     base::DeleteFile(logo_path, false);
    218     return;
    219   }
    220 
    221   WriteMetadata();
    222 }
    223 
    224 void LogoCache::DeleteLogoAndMetadata() {
    225   base::DeleteFile(GetLogoPath(), false);
    226   base::DeleteFile(GetMetadataPath(), false);
    227 }
    228 
    229 bool LogoCache::EnsureCacheDirectoryExists() {
    230   if (base::DirectoryExists(cache_directory_))
    231     return true;
    232   return base::CreateDirectory(cache_directory_);
    233 }
    234 
    235 }  // namespace search_provider_logos
    236