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_cache.h"
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"
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 const char kAnimatedUrlKey
[] = "animated_url";
26 bool GetTimeValue(const base::DictionaryValue
& dict
,
27 const std::string
& key
,
30 int64 internal_time_value
;
31 if (dict
.GetString(key
, &str
) &&
32 base::StringToInt64(str
, &internal_time_value
)) {
33 *time
= base::Time::FromInternalValue(internal_time_value
);
39 void SetTimeValue(base::DictionaryValue
& dict
,
40 const std::string
& key
,
41 const base::Time
& time
) {
42 int64 internal_time_value
= time
.ToInternalValue();
43 dict
.SetString(key
, base::Int64ToString(internal_time_value
));
48 namespace search_provider_logos
{
50 LogoCache::LogoCache(const base::FilePath
& cache_directory
)
51 : cache_directory_(cache_directory
),
52 metadata_is_valid_(false) {
53 // The LogoCache can be constructed on any thread, as long as it's used
54 // on a single thread after construction.
55 thread_checker_
.DetachFromThread();
58 LogoCache::~LogoCache() {
59 DCHECK(thread_checker_
.CalledOnValidThread());
62 void LogoCache::UpdateCachedLogoMetadata(const LogoMetadata
& metadata
) {
63 DCHECK(thread_checker_
.CalledOnValidThread());
65 DCHECK_EQ(metadata_
->fingerprint
, metadata
.fingerprint
);
67 UpdateMetadata(make_scoped_ptr(new LogoMetadata(metadata
)));
71 const LogoMetadata
* LogoCache::GetCachedLogoMetadata() {
72 DCHECK(thread_checker_
.CalledOnValidThread());
73 ReadMetadataIfNeeded();
74 return metadata_
.get();
77 void LogoCache::SetCachedLogo(const EncodedLogo
* logo
) {
78 DCHECK(thread_checker_
.CalledOnValidThread());
79 scoped_ptr
<LogoMetadata
> metadata
;
81 metadata
.reset(new LogoMetadata(logo
->metadata
));
82 logo_num_bytes_
= static_cast<int>(logo
->encoded_image
->size());
84 UpdateMetadata(metadata
.Pass());
85 WriteLogo(logo
? logo
->encoded_image
: NULL
);
88 scoped_ptr
<EncodedLogo
> LogoCache::GetCachedLogo() {
89 DCHECK(thread_checker_
.CalledOnValidThread());
91 ReadMetadataIfNeeded();
93 return scoped_ptr
<EncodedLogo
>();
95 scoped_refptr
<base::RefCountedString
> encoded_image
=
96 new base::RefCountedString();
97 if (!base::ReadFileToString(GetLogoPath(), &encoded_image
->data())) {
98 UpdateMetadata(scoped_ptr
<LogoMetadata
>());
99 return scoped_ptr
<EncodedLogo
>();
102 if (encoded_image
->size() != static_cast<size_t>(logo_num_bytes_
)) {
103 // Delete corrupt metadata and logo.
104 DeleteLogoAndMetadata();
105 UpdateMetadata(scoped_ptr
<LogoMetadata
>());
106 return scoped_ptr
<EncodedLogo
>();
109 scoped_ptr
<EncodedLogo
> logo(new EncodedLogo());
110 logo
->encoded_image
= encoded_image
;
111 logo
->metadata
= *metadata_
;
116 scoped_ptr
<LogoMetadata
> LogoCache::LogoMetadataFromString(
117 const std::string
& str
, int* logo_num_bytes
) {
118 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(str
);
119 base::DictionaryValue
* dict
;
120 if (!value
|| !value
->GetAsDictionary(&dict
))
121 return scoped_ptr
<LogoMetadata
>();
123 scoped_ptr
<LogoMetadata
> metadata(new LogoMetadata());
124 if (!dict
->GetString(kSourceUrlKey
, &metadata
->source_url
) ||
125 !dict
->GetString(kFingerprintKey
, &metadata
->fingerprint
) ||
126 !dict
->GetString(kOnClickURLKey
, &metadata
->on_click_url
) ||
127 !dict
->GetString(kAltTextKey
, &metadata
->alt_text
) ||
128 !dict
->GetString(kAnimatedUrlKey
, &metadata
->animated_url
) ||
129 !dict
->GetString(kMimeTypeKey
, &metadata
->mime_type
) ||
130 !dict
->GetBoolean(kCanShowAfterExpirationKey
,
131 &metadata
->can_show_after_expiration
) ||
132 !dict
->GetInteger(kNumBytesKey
, logo_num_bytes
) ||
133 !GetTimeValue(*dict
, kExpirationTimeKey
, &metadata
->expiration_time
)) {
134 return scoped_ptr
<LogoMetadata
>();
137 return metadata
.Pass();
141 void LogoCache::LogoMetadataToString(const LogoMetadata
& metadata
,
144 base::DictionaryValue dict
;
145 dict
.SetString(kSourceUrlKey
, metadata
.source_url
);
146 dict
.SetString(kFingerprintKey
, metadata
.fingerprint
);
147 dict
.SetString(kOnClickURLKey
, metadata
.on_click_url
);
148 dict
.SetString(kAltTextKey
, metadata
.alt_text
);
149 dict
.SetString(kAnimatedUrlKey
, metadata
.animated_url
);
150 dict
.SetString(kMimeTypeKey
, metadata
.mime_type
);
151 dict
.SetBoolean(kCanShowAfterExpirationKey
,
152 metadata
.can_show_after_expiration
);
153 dict
.SetInteger(kNumBytesKey
, num_bytes
);
154 SetTimeValue(dict
, kExpirationTimeKey
, metadata
.expiration_time
);
155 base::JSONWriter::Write(dict
, str
);
158 base::FilePath
LogoCache::GetLogoPath() {
159 return cache_directory_
.Append(FILE_PATH_LITERAL("logo"));
162 base::FilePath
LogoCache::GetMetadataPath() {
163 return cache_directory_
.Append(FILE_PATH_LITERAL("metadata"));
166 void LogoCache::UpdateMetadata(scoped_ptr
<LogoMetadata
> metadata
) {
167 metadata_
= metadata
.Pass();
168 metadata_is_valid_
= true;
171 void LogoCache::ReadMetadataIfNeeded() {
172 if (metadata_is_valid_
)
175 scoped_ptr
<LogoMetadata
> metadata
;
176 base::FilePath metadata_path
= GetMetadataPath();
178 if (base::ReadFileToString(metadata_path
, &str
)) {
179 metadata
= LogoMetadataFromString(str
, &logo_num_bytes_
);
181 // Delete corrupt metadata and logo.
182 DeleteLogoAndMetadata();
186 UpdateMetadata(metadata
.Pass());
189 void LogoCache::WriteMetadata() {
190 if (!EnsureCacheDirectoryExists())
194 LogoMetadataToString(*metadata_
, logo_num_bytes_
, &str
);
195 base::WriteFile(GetMetadataPath(), str
.data(), static_cast<int>(str
.size()));
198 void LogoCache::WriteLogo(scoped_refptr
<base::RefCountedMemory
> encoded_image
) {
199 if (!EnsureCacheDirectoryExists())
202 if (!metadata_
|| !encoded_image
.get()) {
203 DeleteLogoAndMetadata();
207 // To minimize the chances of ending up in an undetectably broken state:
208 // First, delete the metadata file, then update the logo file, then update the
210 base::FilePath logo_path
= GetLogoPath();
211 base::FilePath metadata_path
= GetMetadataPath();
213 if (!base::DeleteFile(metadata_path
, false))
218 encoded_image
->front_as
<char>(),
219 static_cast<int>(encoded_image
->size())) == -1) {
220 base::DeleteFile(logo_path
, false);
227 void LogoCache::DeleteLogoAndMetadata() {
228 base::DeleteFile(GetLogoPath(), false);
229 base::DeleteFile(GetMetadataPath(), false);
232 bool LogoCache::EnsureCacheDirectoryExists() {
233 if (base::DirectoryExists(cache_directory_
))
235 return base::CreateDirectory(cache_directory_
);
238 } // namespace search_provider_logos