// Copyright (c) 2012 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/extensions/settings/settings_backend.h" #include "base/compiler_specific.h" #include "base/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/extensions/settings/settings_storage_factory.h" #include "chrome/browser/extensions/settings/settings_storage_quota_enforcer.h" #include "chrome/browser/extensions/settings/settings_sync_processor.h" #include "chrome/browser/extensions/settings/settings_sync_util.h" #include "chrome/browser/value_store/failing_value_store.h" #include "chrome/common/extensions/extension.h" #include "content/public/browser/browser_thread.h" #include "sync/api/sync_error_factory.h" using content::BrowserThread; namespace extensions { SettingsBackend::SettingsBackend( const scoped_refptr& storage_factory, const FilePath& base_path, const SettingsStorageQuotaEnforcer::Limits& quota, const scoped_refptr& observers) : storage_factory_(storage_factory), base_path_(base_path), quota_(quota), observers_(observers), sync_type_(syncer::UNSPECIFIED) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); } SettingsBackend::~SettingsBackend() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); } ValueStore* SettingsBackend::GetStorage( const std::string& extension_id) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DictionaryValue empty; return GetOrCreateStorageWithSyncData(extension_id, empty); } SyncableSettingsStorage* SettingsBackend::GetOrCreateStorageWithSyncData( const std::string& extension_id, const DictionaryValue& sync_data) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); StorageObjMap::iterator maybe_storage = storage_objs_.find(extension_id); if (maybe_storage != storage_objs_.end()) { return maybe_storage->second.get(); } ValueStore* storage = storage_factory_->Create(base_path_, extension_id); if (storage) { // It's fine to create the quota enforcer underneath the sync layer, since // sync will only go ahead if each underlying storage operation succeeds. storage = new SettingsStorageQuotaEnforcer(quota_, storage); } else { storage = new FailingValueStore(); } linked_ptr syncable_storage( new SyncableSettingsStorage( observers_, extension_id, storage)); storage_objs_[extension_id] = syncable_storage; if (sync_processor_.get()) { syncer::SyncError error = syncable_storage->StartSyncing( sync_data, CreateSettingsSyncProcessor(extension_id).Pass()); if (error.IsSet()) syncable_storage.get()->StopSyncing(); } return syncable_storage.get(); } void SettingsBackend::DeleteStorage(const std::string& extension_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Clear settings when the extension is uninstalled. Leveldb implementations // will also delete the database from disk when the object is destroyed as a // result of being removed from |storage_objs_|. // // TODO(kalman): always GetStorage here (rather than only clearing if it // exists) since the storage area may have been unloaded, but we still want // to clear the data from disk. // However, this triggers http://crbug.com/111072. StorageObjMap::iterator maybe_storage = storage_objs_.find(extension_id); if (maybe_storage == storage_objs_.end()) return; maybe_storage->second->Clear(); storage_objs_.erase(extension_id); } std::set SettingsBackend::GetKnownExtensionIDs() const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::set result; // Storage areas can be in-memory as well as on disk. |storage_objs_| will // contain all that are in-memory. for (StorageObjMap::iterator it = storage_objs_.begin(); it != storage_objs_.end(); ++it) { result.insert(it->first); } // Leveldb databases are directories inside base_path_. file_util::FileEnumerator::FindInfo find_info; file_util::FileEnumerator extension_dirs( base_path_, false, file_util::FileEnumerator::DIRECTORIES); while (!extension_dirs.Next().empty()) { extension_dirs.GetFindInfo(&find_info); FilePath extension_dir(file_util::FileEnumerator::GetFilename(find_info)); DCHECK(!extension_dir.IsAbsolute()); // Extension IDs are created as std::strings so they *should* be ASCII. std::string maybe_as_ascii(extension_dir.MaybeAsASCII()); if (!maybe_as_ascii.empty()) { result.insert(maybe_as_ascii); } } return result; } static void AddAllSyncData( const std::string& extension_id, const DictionaryValue& src, syncer::ModelType type, syncer::SyncDataList* dst) { for (DictionaryValue::Iterator it(src); it.HasNext(); it.Advance()) { dst->push_back(settings_sync_util::CreateData( extension_id, it.key(), it.value(), type)); } } syncer::SyncDataList SettingsBackend::GetAllSyncData( syncer::ModelType type) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Ignore the type, it's just for sanity checking; assume that whatever base // path we're constructed with is correct for the sync type. DCHECK(type == syncer::EXTENSION_SETTINGS || type == syncer::APP_SETTINGS); // For all extensions, get all their settings. This has the effect // of bringing in the entire state of extension settings in memory; sad. syncer::SyncDataList all_sync_data; std::set known_extension_ids(GetKnownExtensionIDs()); for (std::set::const_iterator it = known_extension_ids.begin(); it != known_extension_ids.end(); ++it) { ValueStore::ReadResult maybe_settings = GetStorage(*it)->Get(); if (maybe_settings->HasError()) { LOG(WARNING) << "Failed to get settings for " << *it << ": " << maybe_settings->error(); continue; } AddAllSyncData(*it, *maybe_settings->settings().get(), type, &all_sync_data); } return all_sync_data; } syncer::SyncError SettingsBackend::MergeDataAndStartSyncing( syncer::ModelType type, const syncer::SyncDataList& initial_sync_data, scoped_ptr sync_processor, scoped_ptr sync_error_factory) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(type == syncer::EXTENSION_SETTINGS || type == syncer::APP_SETTINGS); DCHECK_EQ(sync_type_, syncer::UNSPECIFIED); DCHECK(!sync_processor_.get()); DCHECK(sync_processor.get()); DCHECK(sync_error_factory.get()); sync_type_ = type; sync_processor_ = sync_processor.Pass(); sync_error_factory_ = sync_error_factory.Pass(); // Group the initial sync data by extension id. std::map > grouped_sync_data; for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin(); it != initial_sync_data.end(); ++it) { SettingSyncData data(*it); linked_ptr sync_data = grouped_sync_data[data.extension_id()]; if (!sync_data.get()) { sync_data = linked_ptr(new DictionaryValue()); grouped_sync_data[data.extension_id()] = sync_data; } DCHECK(!sync_data->HasKey(data.key())) << "Duplicate settings for " << data.extension_id() << "/" << data.key(); sync_data->Set(data.key(), data.value().DeepCopy()); } // Start syncing all existing storage areas. Any storage areas created in // the future will start being synced as part of the creation process. for (StorageObjMap::iterator it = storage_objs_.begin(); it != storage_objs_.end(); ++it) { std::map >::iterator maybe_sync_data = grouped_sync_data.find(it->first); syncer::SyncError error; if (maybe_sync_data != grouped_sync_data.end()) { error = it->second->StartSyncing( *maybe_sync_data->second, CreateSettingsSyncProcessor(it->first).Pass()); grouped_sync_data.erase(it->first); } else { DictionaryValue empty; error = it->second->StartSyncing( empty, CreateSettingsSyncProcessor(it->first).Pass()); } if (error.IsSet()) it->second->StopSyncing(); } // Eagerly create and init the rest of the storage areas that have sync data. // Under normal circumstances (i.e. not first-time sync) this will be all of // them. for (std::map >::iterator it = grouped_sync_data.begin(); it != grouped_sync_data.end(); ++it) { GetOrCreateStorageWithSyncData(it->first, *it->second); } return syncer::SyncError(); } syncer::SyncError SettingsBackend::ProcessSyncChanges( const tracked_objects::Location& from_here, const syncer::SyncChangeList& sync_changes) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(sync_processor_.get()); // Group changes by extension, to pass all changes in a single method call. std::map grouped_sync_data; for (syncer::SyncChangeList::const_iterator it = sync_changes.begin(); it != sync_changes.end(); ++it) { SettingSyncData data(*it); grouped_sync_data[data.extension_id()].push_back(data); } // Create any storage areas that don't exist yet but have sync data. DictionaryValue empty; for (std::map::iterator it = grouped_sync_data.begin(); it != grouped_sync_data.end(); ++it) { SyncableSettingsStorage* storage = GetOrCreateStorageWithSyncData(it->first, empty); syncer::SyncError error = storage->ProcessSyncChanges(it->second); if (error.IsSet()) storage->StopSyncing(); } return syncer::SyncError(); } void SettingsBackend::StopSyncing(syncer::ModelType type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(type == syncer::EXTENSION_SETTINGS || type == syncer::APP_SETTINGS); DCHECK(sync_type_ == type || sync_type_ == syncer::UNSPECIFIED); for (StorageObjMap::iterator it = storage_objs_.begin(); it != storage_objs_.end(); ++it) { // Some storage areas may have already stopped syncing if they had areas // and syncing was disabled, but StopSyncing is safe to call multiple times. it->second->StopSyncing(); } sync_type_ = syncer::UNSPECIFIED; sync_processor_.reset(); sync_error_factory_.reset(); } scoped_ptr SettingsBackend::CreateSettingsSyncProcessor( const std::string& extension_id) const { CHECK(sync_processor_.get()); return scoped_ptr( new SettingsSyncProcessor(extension_id, sync_type_, sync_processor_.get())); } } // namespace extensions