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"
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 {
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
24 const int64_t kCacheStatusPollingDelayMs = 1000;
28 const char LocalExtensionCache::kCacheReadyFlagFileName[] = ".initialized";
30 LocalExtensionCache::LocalExtensionCache(
31 const base::FilePath& cache_dir,
32 uint64 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() {
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);
58 CheckCacheContents(callback);
61 void LocalExtensionCache::Shutdown(const base::Closure& callback) {
62 DCHECK_NE(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) {
76 CacheMap::iterator it = cached_extensions_.find(id);
77 if (it == cached_extensions_.end())
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;
92 *version = it->second.version;
97 void LocalExtensionCache::PutExtension(const std::string& id,
98 const base::FilePath& file_path,
99 const std::string& version,
100 const PutExtensionCallback& callback) {
101 if (state_ != kReady) {
102 callback.Run(file_path, true);
106 Version version_validator(version);
107 if (!version_validator.IsValid()) {
108 LOG(ERROR) << "Extension " << id << " has bad version " << version;
109 callback.Run(file_path, true);
113 CacheMap::iterator it = cached_extensions_.find(id);
114 if (it != cached_extensions_.end()) {
115 Version new_version(version);
116 Version prev_version(it->second.version);
117 if (new_version.CompareTo(prev_version) <= 0) {
118 LOG(WARNING) << "Cache contains newer or the same version "
119 << prev_version.GetString() << " for extension "
120 << id << " version " << version;
121 callback.Run(file_path, true);
126 backend_task_runner_->PostTask(
128 base::Bind(&LocalExtensionCache::BackendInstallCacheEntry,
129 weak_ptr_factory_.GetWeakPtr(),
137 bool LocalExtensionCache::RemoveExtension(const std::string& id) {
138 if (state_ != kReady)
141 CacheMap::iterator it = cached_extensions_.find(id);
142 if (it == cached_extensions_.end())
145 backend_task_runner_->PostTask(
147 base::Bind(&LocalExtensionCache::BackendRemoveCacheEntry,
148 it->second.file_path));
150 cached_extensions_.erase(it);
154 bool LocalExtensionCache::GetStatistics(uint64* cache_size,
155 size_t* extensions_count) {
156 if (state_ != kReady)
160 for (CacheMap::iterator it = cached_extensions_.begin();
161 it != cached_extensions_.end(); ++it) {
162 *cache_size += it->second.size;
164 *extensions_count = cached_extensions_.size();
169 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
170 const base::TimeDelta& delay) {
171 cache_status_polling_delay_ = delay;
174 void LocalExtensionCache::CheckCacheStatus(const base::Closure& callback) {
175 if (state_ == kShutdown) {
180 backend_task_runner_->PostTask(
182 base::Bind(&LocalExtensionCache::BackendCheckCacheStatus,
183 weak_ptr_factory_.GetWeakPtr(),
189 void LocalExtensionCache::BackendCheckCacheStatus(
190 base::WeakPtr<LocalExtensionCache> local_cache,
191 const base::FilePath& cache_dir,
192 const base::Closure& callback) {
193 content::BrowserThread::PostTask(
194 content::BrowserThread::UI,
196 base::Bind(&LocalExtensionCache::OnCacheStatusChecked,
198 base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName)),
202 void LocalExtensionCache::OnCacheStatusChecked(bool ready,
203 const base::Closure& callback) {
204 if (state_ == kShutdown) {
210 CheckCacheContents(callback);
212 content::BrowserThread::PostDelayedTask(
213 content::BrowserThread::UI,
215 base::Bind(&LocalExtensionCache::CheckCacheStatus,
216 weak_ptr_factory_.GetWeakPtr(),
218 cache_status_polling_delay_);
222 void LocalExtensionCache::CheckCacheContents(const base::Closure& callback) {
223 DCHECK_EQ(state_, kWaitInitialization);
224 backend_task_runner_->PostTask(
226 base::Bind(&LocalExtensionCache::BackendCheckCacheContents,
227 weak_ptr_factory_.GetWeakPtr(),
233 void LocalExtensionCache::BackendCheckCacheContents(
234 base::WeakPtr<LocalExtensionCache> local_cache,
235 const base::FilePath& cache_dir,
236 const base::Closure& callback) {
237 scoped_ptr<CacheMap> cache_content(new CacheMap);
238 BackendCheckCacheContentsInternal(cache_dir, cache_content.get());
239 content::BrowserThread::PostTask(
240 content::BrowserThread::UI,
242 base::Bind(&LocalExtensionCache::OnCacheContentsChecked,
244 base::Passed(&cache_content),
249 void LocalExtensionCache::BackendCheckCacheContentsInternal(
250 const base::FilePath& cache_dir,
251 CacheMap* cache_content) {
252 // Start by verifying that the cache_dir exists.
253 if (!base::DirectoryExists(cache_dir)) {
255 if (!base::CreateDirectory(cache_dir)) {
256 LOG(ERROR) << "Failed to create cache directory at "
257 << cache_dir.value();
260 // Nothing else to do. Cache is empty.
264 // Enumerate all the files in the cache |cache_dir|, including directories
265 // and symlinks. Each unrecognized file will be erased.
266 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
267 base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
268 for (base::FilePath path = enumerator.Next();
269 !path.empty(); path = enumerator.Next()) {
270 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
271 std::string basename = path.BaseName().value();
273 if (info.IsDirectory() || base::IsLink(info.GetName())) {
274 LOG(ERROR) << "Erasing bad file in cache directory: " << basename;
275 base::DeleteFile(path, true /* recursive */);
279 // Skip flag file that indicates that cache is ready.
280 if (basename == kCacheReadyFlagFileName)
283 // crx files in the cache are named <extension-id>-<version>.crx.
286 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
287 size_t n = basename.find('-');
288 if (n != std::string::npos && n + 1 < basename.size() - 4) {
289 id = basename.substr(0, n);
290 // Size of |version| = total size - "<id>" - "-" - ".crx"
291 version = basename.substr(n + 1, basename.size() - 5 - id.size());
295 // Enforce a lower-case id.
296 id = StringToLowerASCII(id);
297 if (!extensions::Extension::IdIsValid(id)) {
298 LOG(ERROR) << "Bad extension id in cache: " << id;
302 if (!Version(version).IsValid()) {
303 LOG(ERROR) << "Bad extension version in cache: " << version;
307 if (id.empty() || version.empty()) {
308 LOG(ERROR) << "Invalid file in cache, erasing: " << basename;
309 base::DeleteFile(path, true /* recursive */);
313 VLOG(1) << "Found cached version " << version
314 << " for extension id " << id;
316 CacheMap::iterator it = cache_content->find(id);
317 if (it != cache_content->end()) {
318 // |cache_content| already has version for this ID. Removed older one.
319 Version curr_version(version);
320 Version prev_version(it->second.version);
321 if (prev_version.CompareTo(curr_version) <= 0) {
322 base::DeleteFile(base::FilePath(it->second.file_path),
323 true /* recursive */);
324 cache_content->erase(id);
325 VLOG(1) << "Remove older version " << it->second.version
326 << " for extension id " << id;
328 base::DeleteFile(path, true /* recursive */);
329 VLOG(1) << "Remove older version " << version
330 << " for extension id " << id;
335 cache_content->insert(std::make_pair(id, CacheItemInfo(
336 version, info.GetLastModifiedTime(), info.GetSize(), path)));
340 void LocalExtensionCache::OnCacheContentsChecked(
341 scoped_ptr<CacheMap> cache_content,
342 const base::Closure& callback) {
343 cache_content->swap(cached_extensions_);
349 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath& file_path,
350 const base::Time& time) {
351 base::TouchFile(file_path, time, time);
355 void LocalExtensionCache::BackendInstallCacheEntry(
356 base::WeakPtr<LocalExtensionCache> local_cache,
357 const base::FilePath& cache_dir,
358 const std::string& id,
359 const base::FilePath& file_path,
360 const std::string& version,
361 const PutExtensionCallback& callback) {
362 std::string basename = id + "-" + version + kCRXFileExtension;
363 base::FilePath cached_crx_path = cache_dir.AppendASCII(basename);
365 bool was_error = false;
366 if (base::PathExists(cached_crx_path)) {
367 LOG(ERROR) << "File already exists " << file_path.value();
368 cached_crx_path = file_path;
372 base::File::Info info;
374 if (!base::Move(file_path, cached_crx_path)) {
375 LOG(ERROR) << "Failed to copy from " << file_path.value()
376 << " to " << cached_crx_path.value();
377 cached_crx_path = file_path;
380 was_error = !base::GetFileInfo(cached_crx_path, &info);
381 VLOG(1) << "Cache entry installed for extension id " << id
382 << " version " << version;
386 content::BrowserThread::PostTask(
387 content::BrowserThread::UI,
389 base::Bind(&LocalExtensionCache::OnCacheEntryInstalled,
392 CacheItemInfo(version, info.last_modified,
393 info.size, cached_crx_path),
398 void LocalExtensionCache::OnCacheEntryInstalled(
399 const std::string& id,
400 const CacheItemInfo& info,
402 const PutExtensionCallback& callback) {
403 if (state_ == kShutdown || was_error) {
404 callback.Run(info.file_path, true);
408 CacheMap::iterator it = cached_extensions_.find(id);
409 if (it != cached_extensions_.end()) {
410 Version new_version(info.version);
411 Version prev_version(it->second.version);
412 if (new_version.CompareTo(prev_version) <= 0) {
413 DCHECK(0) << "Cache contains newer or the same version";
414 callback.Run(info.file_path, true);
418 it = cached_extensions_.insert(std::make_pair(id, info)).first;
419 // Time from file system can have lower precision so use precise "now".
420 it->second.last_used = base::Time::Now();
422 callback.Run(info.file_path, false);
426 void LocalExtensionCache::BackendRemoveCacheEntry(
427 const base::FilePath& file_path) {
428 base::DeleteFile(file_path, true /* recursive */);
429 VLOG(1) << "Removed cached file " << file_path.value();
433 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator& lhs,
434 const CacheMap::iterator& rhs) {
435 return lhs->second.last_used < rhs->second.last_used;
438 void LocalExtensionCache::CleanUp() {
439 DCHECK_EQ(state_, kReady);
441 std::vector<CacheMap::iterator> items;
442 items.reserve(cached_extensions_.size());
443 uint64_t total_size = 0;
444 for (CacheMap::iterator it = cached_extensions_.begin();
445 it != cached_extensions_.end(); ++it) {
447 total_size += it->second.size;
449 std::sort(items.begin(), items.end(), CompareCacheItemsAge);
451 for (std::vector<CacheMap::iterator>::iterator it = items.begin();
452 it != items.end(); ++it) {
453 if ((*it)->second.last_used < min_cache_age_ ||
454 (max_cache_size_ && total_size > max_cache_size_)) {
455 total_size -= (*it)->second.size;
456 VLOG(1) << "Clean up cached extension id " << (*it)->first;
457 RemoveExtension((*it)->first);
462 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
463 const std::string& version,
464 const base::Time& last_used,
466 const base::FilePath& file_path)
467 : version(version), last_used(last_used), size(size), file_path(file_path) {
470 } // namespace extensions