| // 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<SettingsStorageFactory>& storage_factory, |
| const FilePath& base_path, |
| const SettingsStorageQuotaEnforcer::Limits& quota, |
| const scoped_refptr<SettingsObserverList>& 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<SyncableSettingsStorage> 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<std::string> SettingsBackend::GetKnownExtensionIDs() const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| std::set<std::string> 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<std::string> known_extension_ids(GetKnownExtensionIDs()); |
| |
| for (std::set<std::string>::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<syncer::SyncChangeProcessor> sync_processor, |
| scoped_ptr<syncer::SyncErrorFactory> 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<std::string, linked_ptr<DictionaryValue> > grouped_sync_data; |
| for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin(); |
| it != initial_sync_data.end(); ++it) { |
| SettingSyncData data(*it); |
| linked_ptr<DictionaryValue> sync_data = |
| grouped_sync_data[data.extension_id()]; |
| if (!sync_data.get()) { |
| sync_data = linked_ptr<DictionaryValue>(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->SetWithoutPathExpansion(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<std::string, linked_ptr<DictionaryValue> >::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<std::string, linked_ptr<DictionaryValue> >::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<std::string, SettingSyncDataList> 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<std::string, SettingSyncDataList>::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<SettingsSyncProcessor> SettingsBackend::CreateSettingsSyncProcessor( |
| const std::string& extension_id) const { |
| CHECK(sync_processor_.get()); |
| return scoped_ptr<SettingsSyncProcessor>( |
| new SettingsSyncProcessor(extension_id, |
| sync_type_, |
| sync_processor_.get())); |
| } |
| |
| } // namespace extensions |