Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / extensions / updater / local_extension_cache.cc
blob13eebb35a6ea2a5d3906c0960df0df2e4844f7a4
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 "chrome/browser/extensions/updater/local_extension_cache.h"
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/strings/string_util.h"
12 #include "base/version.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "extensions/common/extension.h"
16 namespace extensions {
17 namespace {
19 // File name extension for CRX files (not case sensitive).
20 const char kCRXFileExtension[] = ".crx";
22 // Delay between checks for flag file presence when waiting for the cache to
23 // become ready.
24 const int64_t kCacheStatusPollingDelayMs = 1000;
26 } // namespace
28 const char LocalExtensionCache::kCacheReadyFlagFileName[] = ".initialized";
30 LocalExtensionCache::LocalExtensionCache(
31 const base::FilePath& cache_dir,
32 size_t max_cache_size,
33 const base::TimeDelta& max_cache_age,
34 const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner)
35 : cache_dir_(cache_dir),
36 max_cache_size_(max_cache_size),
37 min_cache_age_(base::Time::Now() - max_cache_age),
38 backend_task_runner_(backend_task_runner),
39 state_(kUninitialized),
40 weak_ptr_factory_(this),
41 cache_status_polling_delay_(
42 base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs)) {
45 LocalExtensionCache::~LocalExtensionCache() {
46 if (state_ == kReady)
47 CleanUp();
50 void LocalExtensionCache::Init(bool wait_for_cache_initialization,
51 const base::Closure& callback) {
52 DCHECK_EQ(state_, kUninitialized);
54 state_ = kWaitInitialization;
55 if (wait_for_cache_initialization)
56 CheckCacheStatus(callback);
57 else
58 CheckCacheContents(callback);
61 void LocalExtensionCache::Shutdown(const base::Closure& callback) {
62 DCHECK_NE(state_, kShutdown);
63 if (state_ == kReady)
64 CleanUp();
65 state_ = kShutdown;
66 backend_task_runner_->PostTaskAndReply(FROM_HERE,
67 base::Bind(&base::DoNothing), callback);
70 bool LocalExtensionCache::GetExtension(const std::string& id,
71 base::FilePath* file_path,
72 std::string* version) {
73 if (state_ != kReady)
74 return false;
76 CacheMap::iterator it = cached_extensions_.find(id);
77 if (it == cached_extensions_.end())
78 return false;
80 if (file_path) {
81 *file_path = it->second.file_path;
83 // If caller is not interesting in file_path, extension is not used.
84 base::Time now = base::Time::Now();
85 backend_task_runner_->PostTask(FROM_HERE,
86 base::Bind(&LocalExtensionCache::BackendMarkFileUsed,
87 it->second.file_path, now));
88 it->second.last_used = now;
91 if (version)
92 *version = it->second.version;
94 return true;
97 void LocalExtensionCache::PutExtension(const std::string& id,
98 const base::FilePath& file_path,
99 const std::string& version,
100 const base::Closure& callback) {
101 if (state_ != kReady)
102 return;
104 Version version_validator(version);
105 if (!version_validator.IsValid()) {
106 LOG(ERROR) << "Extension " << id << " has bad version " << version;
107 return;
110 CacheMap::iterator it = cached_extensions_.find(id);
111 if (it != cached_extensions_.end()) {
112 Version new_version(version);
113 Version prev_version(it->second.version);
114 if (new_version.CompareTo(prev_version) <= 0) {
115 LOG(WARNING) << "Cache contains newer or the same version "
116 << prev_version.GetString() << " for extension "
117 << id << " version " << version;
118 return;
122 backend_task_runner_->PostTask(
123 FROM_HERE,
124 base::Bind(&LocalExtensionCache::BackendInstallCacheEntry,
125 weak_ptr_factory_.GetWeakPtr(),
126 cache_dir_,
128 file_path,
129 version,
130 callback));
133 bool LocalExtensionCache::RemoveExtension(const std::string& id) {
134 if (state_ != kReady)
135 return false;
137 CacheMap::iterator it = cached_extensions_.find(id);
138 if (it == cached_extensions_.end())
139 return false;
141 backend_task_runner_->PostTask(
142 FROM_HERE,
143 base::Bind(&LocalExtensionCache::BackendRemoveCacheEntry,
144 it->second.file_path));
146 cached_extensions_.erase(it);
147 return true;
150 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
151 const base::TimeDelta& delay) {
152 cache_status_polling_delay_ = delay;
155 void LocalExtensionCache::CheckCacheStatus(const base::Closure& callback) {
156 if (state_ == kShutdown) {
157 callback.Run();
158 return;
161 backend_task_runner_->PostTask(
162 FROM_HERE,
163 base::Bind(&LocalExtensionCache::BackendCheckCacheStatus,
164 weak_ptr_factory_.GetWeakPtr(),
165 cache_dir_,
166 callback));
169 // static
170 void LocalExtensionCache::BackendCheckCacheStatus(
171 base::WeakPtr<LocalExtensionCache> local_cache,
172 const base::FilePath& cache_dir,
173 const base::Closure& callback) {
174 content::BrowserThread::PostTask(
175 content::BrowserThread::UI,
176 FROM_HERE,
177 base::Bind(&LocalExtensionCache::OnCacheStatusChecked,
178 local_cache,
179 base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName)),
180 callback));
183 void LocalExtensionCache::OnCacheStatusChecked(bool ready,
184 const base::Closure& callback) {
185 if (state_ == kShutdown) {
186 callback.Run();
187 return;
190 if (ready) {
191 CheckCacheContents(callback);
192 } else {
193 content::BrowserThread::PostDelayedTask(
194 content::BrowserThread::UI,
195 FROM_HERE,
196 base::Bind(&LocalExtensionCache::CheckCacheStatus,
197 weak_ptr_factory_.GetWeakPtr(),
198 callback),
199 cache_status_polling_delay_);
203 void LocalExtensionCache::CheckCacheContents(const base::Closure& callback) {
204 DCHECK_EQ(state_, kWaitInitialization);
205 backend_task_runner_->PostTask(
206 FROM_HERE,
207 base::Bind(&LocalExtensionCache::BackendCheckCacheContents,
208 weak_ptr_factory_.GetWeakPtr(),
209 cache_dir_,
210 callback));
213 // static
214 void LocalExtensionCache::BackendCheckCacheContents(
215 base::WeakPtr<LocalExtensionCache> local_cache,
216 const base::FilePath& cache_dir,
217 const base::Closure& callback) {
218 scoped_ptr<CacheMap> cache_content(new CacheMap);
219 BackendCheckCacheContentsInternal(cache_dir, cache_content.get());
220 content::BrowserThread::PostTask(
221 content::BrowserThread::UI,
222 FROM_HERE,
223 base::Bind(&LocalExtensionCache::OnCacheContentsChecked,
224 local_cache,
225 base::Passed(&cache_content),
226 callback));
229 // static
230 void LocalExtensionCache::BackendCheckCacheContentsInternal(
231 const base::FilePath& cache_dir,
232 CacheMap* cache_content) {
233 // Start by verifying that the cache_dir exists.
234 if (!base::DirectoryExists(cache_dir)) {
235 // Create it now.
236 if (!base::CreateDirectory(cache_dir)) {
237 LOG(ERROR) << "Failed to create cache directory at "
238 << cache_dir.value();
241 // Nothing else to do. Cache is empty.
242 return;
245 // Enumerate all the files in the cache |cache_dir|, including directories
246 // and symlinks. Each unrecognized file will be erased.
247 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
248 base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
249 for (base::FilePath path = enumerator.Next();
250 !path.empty(); path = enumerator.Next()) {
251 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
252 std::string basename = path.BaseName().value();
254 if (info.IsDirectory() || base::IsLink(info.GetName())) {
255 LOG(ERROR) << "Erasing bad file in cache directory: " << basename;
256 base::DeleteFile(path, true /* recursive */);
257 continue;
260 // Skip flag file that indicates that cache is ready.
261 if (basename == kCacheReadyFlagFileName)
262 continue;
264 // crx files in the cache are named <extension-id>-<version>.crx.
265 std::string id;
266 std::string version;
267 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
268 size_t n = basename.find('-');
269 if (n != std::string::npos && n + 1 < basename.size() - 4) {
270 id = basename.substr(0, n);
271 // Size of |version| = total size - "<id>" - "-" - ".crx"
272 version = basename.substr(n + 1, basename.size() - 5 - id.size());
276 // Enforce a lower-case id.
277 id = StringToLowerASCII(id);
278 if (!extensions::Extension::IdIsValid(id)) {
279 LOG(ERROR) << "Bad extension id in cache: " << id;
280 id.clear();
283 if (!Version(version).IsValid()) {
284 LOG(ERROR) << "Bad extension version in cache: " << version;
285 version.clear();
288 if (id.empty() || version.empty()) {
289 LOG(ERROR) << "Invalid file in cache, erasing: " << basename;
290 base::DeleteFile(path, true /* recursive */);
291 continue;
294 VLOG(1) << "Found cached version " << version
295 << " for extension id " << id;
297 CacheMap::iterator it = cache_content->find(id);
298 if (it != cache_content->end()) {
299 // |cache_content| already has version for this ID. Removed older one.
300 Version curr_version(version);
301 Version prev_version(it->second.version);
302 if (prev_version.CompareTo(curr_version) <= 0) {
303 base::DeleteFile(base::FilePath(it->second.file_path),
304 true /* recursive */);
305 cache_content->erase(id);
306 VLOG(1) << "Remove older version " << it->second.version
307 << " for extension id " << id;
308 } else {
309 base::DeleteFile(path, true /* recursive */);
310 VLOG(1) << "Remove older version " << version
311 << " for extension id " << id;
312 continue;
316 cache_content->insert(std::make_pair(id, CacheItemInfo(
317 version, info.GetLastModifiedTime(), info.GetSize(), path)));
321 void LocalExtensionCache::OnCacheContentsChecked(
322 scoped_ptr<CacheMap> cache_content,
323 const base::Closure& callback) {
324 cache_content->swap(cached_extensions_);
325 state_ = kReady;
326 callback.Run();
329 // static
330 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath& file_path,
331 const base::Time& time) {
332 base::TouchFile(file_path, time, time);
335 // static
336 void LocalExtensionCache::BackendInstallCacheEntry(
337 base::WeakPtr<LocalExtensionCache> local_cache,
338 const base::FilePath& cache_dir,
339 const std::string& id,
340 const base::FilePath& file_path,
341 const std::string& version,
342 const base::Closure& callback) {
343 std::string basename = id + "-" + version + kCRXFileExtension;
344 base::FilePath cached_crx_path = cache_dir.AppendASCII(basename);
346 if (base::PathExists(cached_crx_path)) {
347 LOG(ERROR) << "File already exists " << file_path.value();
348 return;
351 if (!base::CopyFile(file_path, cached_crx_path)) {
352 LOG(ERROR) << "Failed to copy from " << file_path.value()
353 << " to " << cached_crx_path.value();
354 return;
357 base::File::Info info;
358 if (!base::GetFileInfo(cached_crx_path, &info)) {
359 LOG(ERROR) << "Failed to stat file " << cached_crx_path.value();
360 return;
363 content::BrowserThread::PostTask(
364 content::BrowserThread::UI,
365 FROM_HERE,
366 base::Bind(&LocalExtensionCache::OnCacheEntryInstalled,
367 local_cache,
369 CacheItemInfo(version, info.last_modified,
370 info.size, cached_crx_path),
371 callback));
374 void LocalExtensionCache::OnCacheEntryInstalled(const std::string& id,
375 const CacheItemInfo& info,
376 const base::Closure& callback) {
377 if (state_ == kShutdown)
378 return;
380 CacheMap::iterator it = cached_extensions_.find(id);
381 if (it != cached_extensions_.end()) {
382 Version new_version(info.version);
383 Version prev_version(it->second.version);
384 if (new_version.CompareTo(prev_version) <= 0) {
385 // Cache contains newer or the same version.
386 return;
389 cached_extensions_.insert(std::make_pair(id, info));
390 callback.Run();
393 // static
394 void LocalExtensionCache::BackendRemoveCacheEntry(
395 const base::FilePath& file_path) {
396 base::DeleteFile(file_path, true /* recursive */);
397 VLOG(1) << "Removed cached file " << file_path.value();
400 // static
401 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator& lhs,
402 const CacheMap::iterator& rhs) {
403 return lhs->second.last_used < rhs->second.last_used;
406 void LocalExtensionCache::CleanUp() {
407 DCHECK_EQ(state_, kReady);
409 std::vector<CacheMap::iterator> items;
410 items.reserve(cached_extensions_.size());
411 size_t total_size = 0;
412 for (CacheMap::iterator it = cached_extensions_.begin();
413 it != cached_extensions_.end(); ++it) {
414 items.push_back(it);
415 total_size += it->second.size;
417 std::sort(items.begin(), items.end(), CompareCacheItemsAge);
419 for (std::vector<CacheMap::iterator>::iterator it = items.begin();
420 it != items.end(); ++it) {
421 if ((*it)->second.last_used < min_cache_age_ ||
422 (max_cache_size_ && total_size > max_cache_size_)) {
423 total_size -= (*it)->second.size;
424 RemoveExtension((*it)->first);
429 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
430 const std::string& version,
431 const base::Time& last_used,
432 const size_t size,
433 const base::FilePath& file_path)
434 : version(version), last_used(last_used), size(size), file_path(file_path) {
437 } // namespace extensions