// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/app_mode/kiosk_app_data.h" #include #include "base/bind.h" #include "base/file_util.h" #include "base/memory/ref_counted_memory.h" #include "base/prefs/pref_service.h" #include "base/threading/sequenced_worker_pool.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_data_delegate.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" #include "chrome/browser/extensions/webstore_data_fetcher.h" #include "chrome/browser/extensions/webstore_install_helper.h" #include "chrome/browser/image_decoder.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/common/extensions/extension_constants.h" #include "content/public/browser/browser_thread.h" #include "ui/gfx/codec/png_codec.h" using content::BrowserThread; namespace chromeos { namespace { // Keys for local state data. See sample layout in KioskAppManager. const char kKeyName[] = "name"; const char kKeyIcon[] = "icon"; // Web store data keys. const char kManifestKey[] = "manifest"; const char kIconUrlKey[] = "icon_url"; const char kLocalizedNameKey[] = "localized_name"; const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; // Icon file extension. const char kIconFileExtension[] = ".png"; // Save |raw_icon| for given |app_id|. void SaveIconToLocalOnBlockingPool( const base::FilePath& icon_path, scoped_refptr raw_icon) { DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); base::FilePath dir = icon_path.DirName(); if (!file_util::PathExists(dir)) CHECK(file_util::CreateDirectory(dir)); CHECK_EQ(static_cast(raw_icon->size()), file_util::WriteFile(icon_path, raw_icon->data().c_str(), raw_icon->size())); } } // namespace //////////////////////////////////////////////////////////////////////////////// // KioskAppData::IconLoader // Loads locally stored icon data and decode it. class KioskAppData::IconLoader : public ImageDecoder::Delegate { public: enum LoadResult { SUCCESS, FAILED_TO_LOAD, FAILED_TO_DECODE, }; IconLoader(const base::WeakPtr& client, const base::FilePath& icon_path) : client_(client), icon_path_(icon_path), load_result_(SUCCESS) {} void Start() { base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken(); task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior( token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); task_runner_->PostTask(FROM_HERE, base::Bind(&IconLoader::LoadOnBlockingPool, base::Unretained(this))); } private: friend class base::RefCountedThreadSafe; virtual ~IconLoader() {} // Loads the icon from locally stored |icon_path_| on the blocking pool void LoadOnBlockingPool() { DCHECK(task_runner_->RunsTasksOnCurrentThread()); std::string data; if (!file_util::ReadFileToString(base::FilePath(icon_path_), &data)) { ReportResultOnBlockingPool(FAILED_TO_LOAD); return; } raw_icon_ = base::RefCountedString::TakeString(&data); scoped_refptr image_decoder = new ImageDecoder( this, raw_icon_->data(), ImageDecoder::DEFAULT_CODEC); image_decoder->Start(task_runner_); } void ReportResultOnBlockingPool(LoadResult result) { DCHECK(task_runner_->RunsTasksOnCurrentThread()); load_result_ = result; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&IconLoader::ReportResultOnUIThread, base::Unretained(this))); } void NotifyClient() { if (!client_) return; if (load_result_ == SUCCESS) client_->OnIconLoadSuccess(raw_icon_, icon_); else client_->OnIconLoadFailure(); } void ReportResultOnUIThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); NotifyClient(); delete this; } // ImageDecoder::Delegate overrides: virtual void OnImageDecoded(const ImageDecoder* decoder, const SkBitmap& decoded_image) OVERRIDE { icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image); icon_.MakeThreadSafe(); ReportResultOnBlockingPool(SUCCESS); } virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE { ReportResultOnBlockingPool(FAILED_TO_DECODE); } base::WeakPtr client_; base::FilePath icon_path_; LoadResult load_result_; scoped_refptr task_runner_; gfx::ImageSkia icon_; scoped_refptr raw_icon_; DISALLOW_COPY_AND_ASSIGN(IconLoader); }; //////////////////////////////////////////////////////////////////////////////// // KioskAppData::WebstoreDataParser // Use WebstoreInstallHelper to parse the manifest and decode the icon. class KioskAppData::WebstoreDataParser : public extensions::WebstoreInstallHelper::Delegate { public: explicit WebstoreDataParser(const base::WeakPtr& client) : client_(client) {} void Start(const std::string& app_id, const std::string& manifest, const GURL& icon_url, net::URLRequestContextGetter* context_getter) { scoped_refptr webstore_helper = new extensions::WebstoreInstallHelper(this, app_id, manifest, "", // No icon data. icon_url, context_getter); webstore_helper->Start(); } private: friend class base::RefCounted; virtual ~WebstoreDataParser() {} // WebstoreInstallHelper::Delegate overrides: virtual void OnWebstoreParseSuccess( const std::string& id, const SkBitmap& icon, base::DictionaryValue* parsed_manifest) OVERRIDE { // Takes ownership of |parsed_manifest|. scoped_ptr manifest(parsed_manifest); if (client_) client_->OnWebstoreParseSuccess(icon, manifest.release()); delete this; } virtual void OnWebstoreParseFailure( const std::string& id, InstallHelperResultCode result_code, const std::string& error_message) OVERRIDE { if (client_) client_->OnWebstoreParseFailure(); delete this; } base::WeakPtr client_; DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser); }; //////////////////////////////////////////////////////////////////////////////// // KioskAppData KioskAppData::KioskAppData(KioskAppDataDelegate* delegate, const std::string& app_id) : delegate_(delegate), status_(STATUS_INIT), id_(app_id) { } KioskAppData::~KioskAppData() {} void KioskAppData::Load() { SetStatus(STATUS_LOADING); if (LoadFromCache()) return; StartFetch(); } void KioskAppData::ClearCache() { PrefService* local_state = g_browser_process->local_state(); DictionaryPrefUpdate dict_update(local_state, KioskAppManager::kKioskDictionaryName); std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + id_; dict_update->Remove(app_key, NULL); if (!icon_path_.empty()) { BrowserThread::PostBlockingPoolTask( FROM_HERE, base::Bind(base::IgnoreResult(&file_util::Delete), icon_path_, false)); } } bool KioskAppData::IsLoading() const { return status_ == STATUS_LOADING; } void KioskAppData::SetStatus(Status status) { if (status_ == status) return; status_ = status; if (!delegate_) return; switch (status_) { case STATUS_INIT: break; case STATUS_LOADING: case STATUS_LOADED: delegate_->OnKioskAppDataChanged(id_); break; case STATUS_ERROR: delegate_->OnKioskAppDataLoadFailure(id_); break; }; } net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() { return g_browser_process->system_request_context(); } bool KioskAppData::LoadFromCache() { std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + id_; std::string name_key = app_key + '.' + kKeyName; std::string icon_path_key = app_key + '.' + kKeyIcon; PrefService* local_state = g_browser_process->local_state(); const base::DictionaryValue* dict = local_state->GetDictionary(KioskAppManager::kKioskDictionaryName); icon_path_.clear(); std::string icon_path_string; if (!dict->GetString(name_key, &name_) || !dict->GetString(icon_path_key, &icon_path_string)) { return false; } icon_path_ = base::FilePath(icon_path_string); // IconLoader deletes itself when done. (new IconLoader(AsWeakPtr(), icon_path_))->Start(); return true; } void KioskAppData::SetCache(const std::string& name, const base::FilePath& icon_path) { std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + id_; std::string name_key = app_key + '.' + kKeyName; std::string icon_path_key = app_key + '.' + kKeyIcon; PrefService* local_state = g_browser_process->local_state(); DictionaryPrefUpdate dict_update(local_state, KioskAppManager::kKioskDictionaryName); dict_update->SetString(name_key, name); dict_update->SetString(icon_path_key, icon_path.value()); icon_path_ = icon_path; } void KioskAppData::OnIconLoadSuccess( const scoped_refptr& raw_icon, const gfx::ImageSkia& icon) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); raw_icon_ = raw_icon; icon_ = icon; SetStatus(STATUS_LOADED); } void KioskAppData::OnIconLoadFailure() { // Re-fetch data from web store when failed to load cached data. StartFetch(); } void KioskAppData::OnWebstoreParseSuccess( const SkBitmap& icon, base::DictionaryValue* parsed_manifest) { // Takes ownership of |parsed_manifest|. scoped_ptr manifest(parsed_manifest); icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon); icon_.MakeThreadSafe(); std::vector image_data; CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data)); raw_icon_ = new base::RefCountedString; raw_icon_->data().assign(image_data.begin(), image_data.end()); base::FilePath cache_dir; if (delegate_) delegate_->GetKioskAppIconCacheDir(&cache_dir); base::FilePath icon_path = cache_dir.AppendASCII(id_).AddExtension(kIconFileExtension); BrowserThread::GetBlockingPool()->PostTask( FROM_HERE, base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon_)); SetCache(name_, icon_path); SetStatus(STATUS_LOADED); } void KioskAppData::OnWebstoreParseFailure() { SetStatus(STATUS_ERROR); } void KioskAppData::StartFetch() { webstore_fetcher_.reset(new extensions::WebstoreDataFetcher( this, GetRequestContextGetter(), GURL(), id_)); webstore_fetcher_->Start(); } void KioskAppData::OnWebstoreRequestFailure() { SetStatus(STATUS_ERROR); } void KioskAppData::OnWebstoreResponseParseSuccess( base::DictionaryValue* webstore_data) { // Takes ownership of |webstore_data|. scoped_ptr data(webstore_data); webstore_fetcher_.reset(); std::string manifest; if (!webstore_data->GetString(kManifestKey, &manifest)) { OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); return; } if (!webstore_data->GetString(kLocalizedNameKey, &name_)) { OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); return; } std::string icon_url_string; if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) { OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); return; } GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( icon_url_string); if (!icon_url.is_valid()) { OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError); return; } // WebstoreDataParser deletes itself when done. (new WebstoreDataParser(AsWeakPtr()))->Start(id_, manifest, icon_url, GetRequestContextGetter()); } void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) { webstore_fetcher_.reset(); SetStatus(STATUS_ERROR); } } // namespace chromeos