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";
25 bool GetTimeValue(const base::DictionaryValue
& dict
,
26 const std::string
& key
,
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
);
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
));
47 namespace search_provider_logos
{
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();
57 LogoCache::~LogoCache() {
58 DCHECK(thread_checker_
.CalledOnValidThread());
61 void LogoCache::UpdateCachedLogoMetadata(const LogoMetadata
& metadata
) {
62 DCHECK(thread_checker_
.CalledOnValidThread());
64 DCHECK_EQ(metadata_
->fingerprint
, metadata
.fingerprint
);
66 UpdateMetadata(make_scoped_ptr(new LogoMetadata(metadata
)));
70 const LogoMetadata
* LogoCache::GetCachedLogoMetadata() {
71 DCHECK(thread_checker_
.CalledOnValidThread());
72 ReadMetadataIfNeeded();
73 return metadata_
.get();
76 void LogoCache::SetCachedLogo(const EncodedLogo
* logo
) {
77 DCHECK(thread_checker_
.CalledOnValidThread());
78 scoped_ptr
<LogoMetadata
> metadata
;
80 metadata
.reset(new LogoMetadata(logo
->metadata
));
81 logo_num_bytes_
= static_cast<int>(logo
->encoded_image
->size());
83 UpdateMetadata(metadata
.Pass());
84 WriteLogo(logo
? logo
->encoded_image
: NULL
);
87 scoped_ptr
<EncodedLogo
> LogoCache::GetCachedLogo() {
88 DCHECK(thread_checker_
.CalledOnValidThread());
90 ReadMetadataIfNeeded();
92 return scoped_ptr
<EncodedLogo
>();
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
>();
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
>();
108 scoped_ptr
<EncodedLogo
> logo(new EncodedLogo());
109 logo
->encoded_image
= encoded_image
;
110 logo
->metadata
= *metadata_
;
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
>();
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
>();
135 return metadata
.Pass();
139 void LogoCache::LogoMetadataToString(const LogoMetadata
& metadata
,
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
);
155 base::FilePath
LogoCache::GetLogoPath() {
156 return cache_directory_
.Append(FILE_PATH_LITERAL("logo"));
159 base::FilePath
LogoCache::GetMetadataPath() {
160 return cache_directory_
.Append(FILE_PATH_LITERAL("metadata"));
163 void LogoCache::UpdateMetadata(scoped_ptr
<LogoMetadata
> metadata
) {
164 metadata_
= metadata
.Pass();
165 metadata_is_valid_
= true;
168 void LogoCache::ReadMetadataIfNeeded() {
169 if (metadata_is_valid_
)
172 scoped_ptr
<LogoMetadata
> metadata
;
173 base::FilePath metadata_path
= GetMetadataPath();
175 if (base::ReadFileToString(metadata_path
, &str
)) {
176 metadata
= LogoMetadataFromString(str
, &logo_num_bytes_
);
178 // Delete corrupt metadata and logo.
179 DeleteLogoAndMetadata();
183 UpdateMetadata(metadata
.Pass());
186 void LogoCache::WriteMetadata() {
187 if (!EnsureCacheDirectoryExists())
191 LogoMetadataToString(*metadata_
, logo_num_bytes_
, &str
);
192 base::WriteFile(GetMetadataPath(), str
.data(), static_cast<int>(str
.size()));
195 void LogoCache::WriteLogo(scoped_refptr
<base::RefCountedMemory
> encoded_image
) {
196 if (!EnsureCacheDirectoryExists())
199 if (!metadata_
|| !encoded_image
.get()) {
200 DeleteLogoAndMetadata();
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
207 base::FilePath logo_path
= GetLogoPath();
208 base::FilePath metadata_path
= GetMetadataPath();
210 if (!base::DeleteFile(metadata_path
, false))
215 encoded_image
->front_as
<char>(),
216 static_cast<int>(encoded_image
->size())) == -1) {
217 base::DeleteFile(logo_path
, false);
224 void LogoCache::DeleteLogoAndMetadata() {
225 base::DeleteFile(GetLogoPath(), false);
226 base::DeleteFile(GetMetadataPath(), false);
229 bool LogoCache::EnsureCacheDirectoryExists() {
230 if (base::DirectoryExists(cache_directory_
))
232 return base::CreateDirectory(cache_directory_
);
235 } // namespace search_provider_logos