Delay app cache check until flag file indicates that it is ready
[chromium-blink-merge.git] / chrome / browser / chromeos / extensions / external_cache.cc
blobedda312238666c44e502131a92128392e45d74f6
1 // Copyright (c) 2013 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/chromeos/extensions/external_cache.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/location.h"
12 #include "base/strings/string_util.h"
13 #include "base/values.h"
14 #include "base/version.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/crx_installer.h"
17 #include "chrome/browser/extensions/external_provider_impl.h"
18 #include "chrome/browser/extensions/updater/extension_downloader.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/notification_source.h"
26 namespace chromeos {
28 namespace {
30 // File name extension for CRX files (not case sensitive).
31 const char kCRXFileExtension[] = ".crx";
33 // Name of flag file that indicates that cache is ready (import finished).
34 const char kCacheReadyFlagFileName[] = ".initialized";
36 // Delay between checking cache ready flag file.
37 const int64_t kCacheReadyDelayMs = 1000;
39 } // namespace
41 ExternalCache::ExternalCache(const std::string& cache_dir,
42 net::URLRequestContextGetter* request_context,
43 Delegate* delegate,
44 bool always_check_updates,
45 bool wait_cache_initialization)
46 : cache_dir_(cache_dir),
47 request_context_(request_context),
48 delegate_(delegate),
49 always_check_updates_(always_check_updates),
50 wait_cache_initialization_(wait_cache_initialization),
51 cached_extensions_(new base::DictionaryValue()),
52 weak_ptr_factory_(this),
53 worker_pool_token_(
54 content::BrowserThread::GetBlockingPool()->GetSequenceToken()) {
55 notification_registrar_.Add(
56 this,
57 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
58 content::NotificationService::AllBrowserContextsAndSources());
61 ExternalCache::~ExternalCache() {
64 void ExternalCache::UpdateExtensionsList(
65 scoped_ptr<base::DictionaryValue> prefs) {
66 extensions_ = prefs.Pass();
67 if (extensions_->empty()) {
68 // Don't check cache and clear it if there are no extensions in the list.
69 // It is important case because login to supervised user shouldn't clear
70 // cache for normal users.
71 // TODO(dpolukhin): introduce reference counting to preserve cache elements
72 // when they are not needed for current user but needed for other users.
73 cached_extensions_->Clear();
74 UpdateExtensionLoader();
75 } else {
76 CheckCacheNow();
80 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
81 for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
82 !it.IsAtEnd(); it.Advance()) {
83 const base::DictionaryValue* entry = NULL;
84 if (!it.value().GetAsDictionary(&entry)) {
85 NOTREACHED() << "ExternalCache found bad entry with type "
86 << it.value().GetType();
87 continue;
90 std::string external_crx;
91 if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
92 &external_crx) &&
93 external_crx == path.value()) {
95 LOG(ERROR) << "ExternalCache extension at " << path.value()
96 << " failed to install, deleting it.";
97 cached_extensions_->Remove(it.key(), NULL);
98 UpdateExtensionLoader();
100 // The file will be downloaded again on the next restart.
101 if (base::FilePath(cache_dir_).IsParent(path)) {
102 // Don't delete files out of cache_dir_.
103 content::BrowserThread::PostTask(
104 content::BrowserThread::FILE, FROM_HERE,
105 base::Bind(base::IgnoreResult(base::DeleteFile), path, true));
108 // Don't try to DownloadMissingExtensions() from here,
109 // since it can cause a fail/retry loop.
110 return;
113 LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
116 void ExternalCache::Observe(int type,
117 const content::NotificationSource& source,
118 const content::NotificationDetails& details) {
119 switch (type) {
120 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
121 extensions::CrxInstaller* installer =
122 content::Source<extensions::CrxInstaller>(source).ptr();
123 OnDamagedFileDetected(installer->source_file());
124 break;
127 default:
128 NOTREACHED();
132 void ExternalCache::OnExtensionDownloadFailed(
133 const std::string& id,
134 extensions::ExtensionDownloaderDelegate::Error error,
135 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
136 const std::set<int>& request_ids) {
137 if (error == NO_UPDATE_AVAILABLE) {
138 if (!cached_extensions_->HasKey(id)) {
139 LOG(ERROR) << "ExternalCache extension " << id
140 << " not found on update server";
142 } else {
143 LOG(ERROR) << "ExternalCache failed to download extension " << id
144 << ", error " << error;
148 void ExternalCache::OnExtensionDownloadFinished(
149 const std::string& id,
150 const base::FilePath& path,
151 const GURL& download_url,
152 const std::string& version,
153 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
154 const std::set<int>& request_ids) {
155 // The explicit copy ctors are to make sure that Bind() binds a copy and not
156 // a reference to the arguments.
157 PostBlockingTask(FROM_HERE,
158 base::Bind(&ExternalCache::BlockingInstallCacheEntry,
159 weak_ptr_factory_.GetWeakPtr(),
160 std::string(cache_dir_),
161 std::string(id),
162 base::FilePath(path),
163 std::string(version)));
166 void ExternalCache::OnBlacklistDownloadFinished(
167 const std::string& data,
168 const std::string& package_hash,
169 const std::string& version,
170 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
171 const std::set<int>& request_ids) {
172 NOTREACHED();
175 bool ExternalCache::IsExtensionPending(const std::string& id) {
176 // Pending means that there is no installed version yet.
177 return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
180 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
181 std::string* version) {
182 DictionaryValue* extension_dictionary = NULL;
183 if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
184 return extension_dictionary->GetString(
185 extensions::ExternalProviderImpl::kExternalVersion,
186 version);
188 return false;
191 void ExternalCache::CheckCacheNow() {
192 scoped_ptr<DictionaryValue> prefs(extensions_->DeepCopy());
193 PostBlockingTask(FROM_HERE,
194 base::Bind(&ExternalCache::BlockingCheckCache,
195 weak_ptr_factory_.GetWeakPtr(),
196 worker_pool_token_,
197 std::string(cache_dir_),
198 base::Passed(&prefs),
199 wait_cache_initialization_));
202 void ExternalCache::UpdateExtensionLoader() {
203 VLOG(1) << "Notify ExternalCache delegate about cache update";
204 if (delegate_)
205 delegate_->OnExtensionListsUpdated(cached_extensions_.get());
208 // static
209 void ExternalCache::BlockingCheckCache(
210 base::WeakPtr<ExternalCache> external_cache,
211 base::SequencedWorkerPool::SequenceToken sequence_token,
212 const std::string& cache_dir,
213 scoped_ptr<base::DictionaryValue> prefs,
214 bool wait_cache_initialization) {
216 base::FilePath dir(cache_dir);
217 if (wait_cache_initialization &&
218 !base::PathExists(dir.AppendASCII(kCacheReadyFlagFileName))) {
219 content::BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
220 sequence_token,
221 FROM_HERE,
222 base::Bind(&ExternalCache::BlockingCheckCache,
223 external_cache,
224 sequence_token,
225 std::string(cache_dir),
226 base::Passed(&prefs),
227 wait_cache_initialization),
228 base::TimeDelta::FromMilliseconds(kCacheReadyDelayMs));
229 return;
232 BlockingCheckCacheInternal(cache_dir, prefs.get());
233 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
234 base::Bind(&ExternalCache::OnCacheUpdated,
235 external_cache,
236 base::Passed(&prefs)));
239 // static
240 void ExternalCache::BlockingCheckCacheInternal(const std::string& cache_dir,
241 base::DictionaryValue* prefs) {
242 // Start by verifying that the cache dir exists.
243 base::FilePath dir(cache_dir);
244 if (!base::DirectoryExists(dir)) {
245 // Create it now.
246 if (!file_util::CreateDirectory(dir)) {
247 LOG(ERROR) << "Failed to create ExternalCache directory at "
248 << dir.value();
251 // Nothing else to do. Cache won't be used.
252 return;
255 // Enumerate all the files in the cache |dir|, including directories
256 // and symlinks. Each unrecognized file will be erased.
257 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
258 base::FileEnumerator::SHOW_SYM_LINKS;
259 base::FileEnumerator enumerator(dir, false /* recursive */, types);
260 for (base::FilePath path = enumerator.Next();
261 !path.empty(); path = enumerator.Next()) {
262 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
263 std::string basename = path.BaseName().value();
265 if (info.IsDirectory() || file_util::IsLink(info.GetName())) {
266 LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename;
267 base::DeleteFile(path, true /* recursive */);
268 continue;
271 // Skip flag file that indicates that cache is ready.
272 if (basename == kCacheReadyFlagFileName)
273 continue;
275 // crx files in the cache are named <extension-id>-<version>.crx.
276 std::string id;
277 std::string version;
278 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
279 size_t n = basename.find('-');
280 if (n != std::string::npos && n + 1 < basename.size() - 4) {
281 id = basename.substr(0, n);
282 // Size of |version| = total size - "<id>" - "-" - ".crx"
283 version = basename.substr(n + 1, basename.size() - 5 - id.size());
287 base::DictionaryValue* entry = NULL;
288 if (!extensions::Extension::IdIsValid(id)) {
289 LOG(ERROR) << "Bad extension id in ExternalCache: " << id;
290 id.clear();
291 } else if (!prefs->GetDictionary(id, &entry)) {
292 LOG(WARNING) << basename << " is in the cache but is not configured by "
293 << "the ExternalCache source, and will be erased.";
294 id.clear();
297 if (!Version(version).IsValid()) {
298 LOG(ERROR) << "Bad extension version in ExternalCache: " << version;
299 version.clear();
302 if (id.empty() || version.empty()) {
303 LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename;
304 base::DeleteFile(path, true /* recursive */);
305 continue;
308 // Enforce a lower-case id.
309 id = StringToLowerASCII(id);
311 std::string update_url;
312 std::string prev_version_string;
313 std::string prev_crx;
314 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
315 &update_url)) {
316 VLOG(1) << "ExternalCache found cached version " << version
317 << " for extension id: " << id;
318 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
319 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
320 version);
321 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
322 path.value());
323 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
324 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore,
325 true);
327 } else if (
328 entry->GetString(extensions::ExternalProviderImpl::kExternalVersion,
329 &prev_version_string) &&
330 entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
331 &prev_crx)) {
332 Version prev_version(prev_version_string);
333 Version curr_version(version);
334 DCHECK(prev_version.IsValid());
335 DCHECK(curr_version.IsValid());
336 if (prev_version.CompareTo(curr_version) <= 0) {
337 VLOG(1) << "ExternalCache found old cached version "
338 << prev_version_string << " path: " << prev_crx;
339 base::FilePath prev_crx_file(prev_crx);
340 if (dir.IsParent(prev_crx_file)) {
341 // Only delete old cached files under cache_dir_ folder.
342 base::DeleteFile(base::FilePath(prev_crx), true /* recursive */);
344 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
345 version);
346 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
347 path.value());
348 } else {
349 VLOG(1) << "ExternalCache found old cached version "
350 << version << " path: " << path.value();
351 base::DeleteFile(path, true /* recursive */);
353 } else {
354 NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
355 << " file path: " << path.value();
360 void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) {
361 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
363 // If request_context_ is missing we can't download anything.
364 if (!downloader_ && request_context_) {
365 downloader_.reset(
366 new extensions::ExtensionDownloader(this, request_context_));
369 cached_extensions_->Clear();
370 for (base::DictionaryValue::Iterator it(*extensions_.get());
371 !it.IsAtEnd(); it.Advance()) {
372 const base::DictionaryValue* entry = NULL;
373 if (!it.value().GetAsDictionary(&entry)) {
374 LOG(ERROR) << "ExternalCache found bad entry with type "
375 << it.value().GetType();
376 continue;
379 // Check for updates for all extensions configured except for extensions
380 // marked as keep_if_present.
381 if (downloader_ &&
382 !entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent)) {
383 GURL update_url;
384 std::string external_update_url;
385 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
386 &external_update_url)) {
387 update_url = GURL(external_update_url);
388 } else if (always_check_updates_) {
389 update_url = extension_urls::GetWebstoreUpdateUrl();
391 if (update_url.is_valid())
392 downloader_->AddPendingExtension(it.key(), update_url, 0);
395 base::DictionaryValue* cached_entry = NULL;
396 if (prefs->GetDictionary(it.key(), &cached_entry)) {
397 std::string crx_path;
398 if (!downloader_ ||
399 cached_entry->GetString(
400 extensions::ExternalProviderImpl::kExternalCrx, &crx_path) ||
401 cached_entry->HasKey(
402 extensions::ExternalProviderImpl::kKeepIfPresent)) {
403 scoped_ptr<base::Value> value;
404 prefs->Remove(it.key(), &value);
405 cached_extensions_->Set(it.key(), value.release());
409 if (downloader_)
410 downloader_->StartAllPending();
412 VLOG(1) << "Updated ExternalCache, there are "
413 << cached_extensions_->size() << " extensions cached";
415 UpdateExtensionLoader();
418 // static
419 void ExternalCache::BlockingInstallCacheEntry(
420 base::WeakPtr<ExternalCache> external_cache,
421 const std::string& app_cache_dir,
422 const std::string& id,
423 const base::FilePath& path,
424 const std::string& version) {
425 Version version_validator(version);
426 if (!version_validator.IsValid()) {
427 LOG(ERROR) << "ExternalCache downloaded extension " << id << " but got bad "
428 << "version: " << version;
429 base::DeleteFile(path, true /* recursive */);
430 return;
433 std::string basename = id + "-" + version + kCRXFileExtension;
434 base::FilePath cache_dir(app_cache_dir);
435 base::FilePath cached_crx_path = cache_dir.Append(basename);
437 if (base::PathExists(cached_crx_path)) {
438 LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
439 << "an existing cached crx.";
440 base::DeleteFile(cached_crx_path, true /* recursive */);
443 if (!base::DirectoryExists(cache_dir)) {
444 LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
445 << cache_dir.value();
446 if (!file_util::CreateDirectory(cache_dir)) {
447 LOG(ERROR) << "Failed to create the AppPack cache dir!";
448 base::DeleteFile(path, true /* recursive */);
449 return;
453 if (!base::Move(path, cached_crx_path)) {
454 LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
455 << " to " << cached_crx_path.value();
456 base::DeleteFile(path, true /* recursive */);
457 return;
460 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
461 base::Bind(&ExternalCache::OnCacheEntryInstalled,
462 external_cache,
463 std::string(id),
464 cached_crx_path.value(),
465 std::string(version)));
468 void ExternalCache::OnCacheEntryInstalled(const std::string& id,
469 const std::string& path,
470 const std::string& version) {
471 VLOG(1) << "AppPack installed a new extension in the cache: " << path;
473 base::DictionaryValue* entry = NULL;
474 std::string update_url;
475 if (!extensions_->GetDictionary(id, &entry)) {
476 LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
477 return;
480 // Copy entry to don't modify it inside extensions_.
481 entry = entry->DeepCopy();
482 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
483 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
484 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx, path);
485 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url)))
486 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
488 cached_extensions_->Set(id, entry);
489 UpdateExtensionLoader();
492 void ExternalCache::PostBlockingTask(const tracked_objects::Location& location,
493 const base::Closure& task) {
494 content::BrowserThread::GetBlockingPool()->
495 PostSequencedWorkerTaskWithShutdownBehavior(
496 worker_pool_token_, location, task,
497 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
500 } // namespace chromeos