blob: 18d503505783427808e6ab3babcb9ae702f4c9bd [file] [log] [blame]
// Copyright 2014 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/api/storage/sync_storage_backend.h"
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/extensions/api/storage/settings_sync_processor.h"
#include "chrome/browser/extensions/api/storage/settings_sync_util.h"
#include "chrome/browser/extensions/api/storage/syncable_settings_storage.h"
#include "components/sync/model/sync_error_factory.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace extensions {
namespace {
void AddAllSyncData(const std::string& extension_id,
const base::DictionaryValue& src,
syncer::ModelType type,
syncer::SyncDataList* dst) {
for (base::DictionaryValue::Iterator it(src); !it.IsAtEnd(); it.Advance()) {
dst->push_back(settings_sync_util::CreateData(
extension_id, it.key(), it.value(), type));
}
}
std::unique_ptr<base::DictionaryValue> EmptyDictionaryValue() {
return base::MakeUnique<base::DictionaryValue>();
}
ValueStoreFactory::ModelType ToFactoryModelType(syncer::ModelType sync_type) {
switch (sync_type) {
case syncer::APP_SETTINGS:
return ValueStoreFactory::ModelType::APP;
case syncer::EXTENSION_SETTINGS:
return ValueStoreFactory::ModelType::EXTENSION;
default:
NOTREACHED();
}
return ValueStoreFactory::ModelType::EXTENSION;
}
} // namespace
SyncStorageBackend::SyncStorageBackend(
const scoped_refptr<ValueStoreFactory>& storage_factory,
const SettingsStorageQuotaEnforcer::Limits& quota,
const scoped_refptr<SettingsObserverList>& observers,
syncer::ModelType sync_type,
const syncer::SyncableService::StartSyncFlare& flare)
: storage_factory_(storage_factory),
quota_(quota),
observers_(observers),
sync_type_(sync_type),
flare_(flare) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK(sync_type_ == syncer::EXTENSION_SETTINGS ||
sync_type_ == syncer::APP_SETTINGS);
}
SyncStorageBackend::~SyncStorageBackend() {}
ValueStore* SyncStorageBackend::GetStorage(const std::string& extension_id) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
return GetOrCreateStorageWithSyncData(extension_id, EmptyDictionaryValue());
}
SyncableSettingsStorage* SyncStorageBackend::GetOrCreateStorageWithSyncData(
const std::string& extension_id,
std::unique_ptr<base::DictionaryValue> sync_data) const {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
StorageObjMap::iterator maybe_storage = storage_objs_.find(extension_id);
if (maybe_storage != storage_objs_.end()) {
return maybe_storage->second.get();
}
std::unique_ptr<SettingsStorageQuotaEnforcer> settings_storage(
new SettingsStorageQuotaEnforcer(
quota_, storage_factory_->CreateSettingsStore(
settings_namespace::SYNC, ToFactoryModelType(sync_type_),
extension_id)));
// It's fine to create the quota enforcer underneath the sync layer, since
// sync will only go ahead if each underlying storage operation succeeds.
std::unique_ptr<SyncableSettingsStorage> syncable_storage(
new SyncableSettingsStorage(observers_, extension_id,
settings_storage.release(), sync_type_,
flare_));
SyncableSettingsStorage* raw_syncable_storage = syncable_storage.get();
storage_objs_[extension_id] = std::move(syncable_storage);
if (sync_processor_.get()) {
syncer::SyncError error = raw_syncable_storage->StartSyncing(
std::move(sync_data), CreateSettingsSyncProcessor(extension_id));
if (error.IsSet())
raw_syncable_storage->StopSyncing();
}
return raw_syncable_storage;
}
void SyncStorageBackend::DeleteStorage(const std::string& extension_id) {
DCHECK_CURRENTLY_ON(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> SyncStorageBackend::GetKnownExtensionIDs(
ValueStoreFactory::ModelType model_type) const {
DCHECK_CURRENTLY_ON(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 (const auto& storage_obj : storage_objs_) {
result.insert(storage_obj.first);
}
std::set<std::string> disk_ids = storage_factory_->GetKnownExtensionIDs(
settings_namespace::SYNC, model_type);
result.insert(disk_ids.begin(), disk_ids.end());
return result;
}
syncer::SyncDataList SyncStorageBackend::GetAllSyncData(syncer::ModelType type)
const {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
// 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(ToFactoryModelType(type)));
for (std::set<std::string>::const_iterator it = known_extension_ids.begin();
it != known_extension_ids.end();
++it) {
ValueStore::ReadResult maybe_settings =
GetOrCreateStorageWithSyncData(*it, EmptyDictionaryValue())->Get();
if (!maybe_settings->status().ok()) {
LOG(WARNING) << "Failed to get settings for " << *it << ": "
<< maybe_settings->status().message;
continue;
}
AddAllSyncData(*it, maybe_settings->settings(), type, &all_sync_data);
}
return all_sync_data;
}
syncer::SyncMergeResult SyncStorageBackend::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK_EQ(sync_type_, type);
DCHECK(!sync_processor_.get());
DCHECK(sync_processor.get());
DCHECK(sync_error_factory.get());
sync_processor_ = std::move(sync_processor);
sync_error_factory_ = std::move(sync_error_factory);
// Group the initial sync data by extension id.
// The raw pointers are safe because ownership of each item is passed to
// storage->StartSyncing or GetOrCreateStorageWithSyncData.
std::map<std::string, base::DictionaryValue*> grouped_sync_data;
for (const syncer::SyncData& sync_data : initial_sync_data) {
SettingSyncData data(sync_data);
// Yes this really is a reference to a pointer.
base::DictionaryValue*& settings = grouped_sync_data[data.extension_id()];
if (!settings)
settings = new base::DictionaryValue();
DCHECK(!settings->HasKey(data.key())) << "Duplicate settings for "
<< data.extension_id() << "/"
<< data.key();
settings->SetWithoutPathExpansion(data.key(), data.PassValue());
}
// Start syncing all existing storage areas. Any storage areas created in
// the future will start being synced as part of the creation process.
for (const auto& storage_obj : storage_objs_) {
const std::string& extension_id = storage_obj.first;
SyncableSettingsStorage* storage = storage_obj.second.get();
auto group = grouped_sync_data.find(extension_id);
syncer::SyncError error;
if (group != grouped_sync_data.end()) {
error = storage->StartSyncing(base::WrapUnique(group->second),
CreateSettingsSyncProcessor(extension_id));
grouped_sync_data.erase(group);
} else {
error = storage->StartSyncing(EmptyDictionaryValue(),
CreateSettingsSyncProcessor(extension_id));
}
if (error.IsSet())
storage->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 (const auto& group : grouped_sync_data) {
GetOrCreateStorageWithSyncData(group.first, base::WrapUnique(group.second));
}
return syncer::SyncMergeResult(type);
}
syncer::SyncError SyncStorageBackend::ProcessSyncChanges(
const tracked_objects::Location& from_here,
const syncer::SyncChangeList& sync_changes) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK(sync_processor_.get());
// Group changes by extension, to pass all changes in a single method call.
// The raw pointers are safe because ownership of each item is passed to
// storage->ProcessSyncChanges.
std::map<std::string, SettingSyncDataList*> grouped_sync_data;
for (const syncer::SyncChange& change : sync_changes) {
std::unique_ptr<SettingSyncData> data(new SettingSyncData(change));
SettingSyncDataList*& group = grouped_sync_data[data->extension_id()];
if (!group)
group = new SettingSyncDataList();
group->push_back(std::move(data));
}
// Create any storage areas that don't exist yet but have sync data.
for (const auto& group : grouped_sync_data) {
SyncableSettingsStorage* storage =
GetOrCreateStorageWithSyncData(group.first, EmptyDictionaryValue());
syncer::SyncError error =
storage->ProcessSyncChanges(base::WrapUnique(group.second));
if (error.IsSet())
storage->StopSyncing();
}
return syncer::SyncError();
}
void SyncStorageBackend::StopSyncing(syncer::ModelType type) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK(type == syncer::EXTENSION_SETTINGS || type == syncer::APP_SETTINGS);
DCHECK_EQ(sync_type_, type);
for (const auto& storage_obj : storage_objs_) {
// Some storage areas may have already stopped syncing if they had areas
// and syncing was disabled, but StopSyncing is safe to call multiple times.
storage_obj.second->StopSyncing();
}
sync_processor_.reset();
sync_error_factory_.reset();
}
std::unique_ptr<SettingsSyncProcessor>
SyncStorageBackend::CreateSettingsSyncProcessor(
const std::string& extension_id) const {
CHECK(sync_processor_.get());
return std::unique_ptr<SettingsSyncProcessor>(new SettingsSyncProcessor(
extension_id, sync_type_, sync_processor_.get()));
}
} // namespace extensions