blob: fbfa0786a0ef4d963305a9d0d52107f6af70e224 [file] [log] [blame]
// 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/api/storage/syncable_settings_storage.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/extensions/api/storage/settings_sync_processor.h"
#include "chrome/browser/extensions/api/storage/settings_sync_util.h"
#include "content/public/browser/browser_thread.h"
#include "sync/api/sync_data.h"
#include "sync/protocol/extension_setting_specifics.pb.h"
namespace extensions {
using content::BrowserThread;
SyncableSettingsStorage::SyncableSettingsStorage(
const scoped_refptr<ObserverListThreadSafe<SettingsObserver> >&
observers,
const std::string& extension_id,
ValueStore* delegate)
: observers_(observers),
extension_id_(extension_id),
delegate_(delegate) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
}
SyncableSettingsStorage::~SyncableSettingsStorage() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
}
size_t SyncableSettingsStorage::GetBytesInUse(const std::string& key) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->GetBytesInUse(key);
}
size_t SyncableSettingsStorage::GetBytesInUse(
const std::vector<std::string>& keys) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->GetBytesInUse(keys);
}
size_t SyncableSettingsStorage::GetBytesInUse() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->GetBytesInUse();
}
ValueStore::ReadResult SyncableSettingsStorage::Get(
const std::string& key) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->Get(key);
}
ValueStore::ReadResult SyncableSettingsStorage::Get(
const std::vector<std::string>& keys) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->Get(keys);
}
ValueStore::ReadResult SyncableSettingsStorage::Get() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return delegate_->Get();
}
ValueStore::WriteResult SyncableSettingsStorage::Set(
WriteOptions options, const std::string& key, const Value& value) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
WriteResult result = delegate_->Set(options, key, value);
if (result->HasError()) {
return result.Pass();
}
SyncResultIfEnabled(result);
return result.Pass();
}
ValueStore::WriteResult SyncableSettingsStorage::Set(
WriteOptions options, const base::DictionaryValue& values) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
WriteResult result = delegate_->Set(options, values);
if (result->HasError()) {
return result.Pass();
}
SyncResultIfEnabled(result);
return result.Pass();
}
ValueStore::WriteResult SyncableSettingsStorage::Remove(
const std::string& key) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
WriteResult result = delegate_->Remove(key);
if (result->HasError()) {
return result.Pass();
}
SyncResultIfEnabled(result);
return result.Pass();
}
ValueStore::WriteResult SyncableSettingsStorage::Remove(
const std::vector<std::string>& keys) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
WriteResult result = delegate_->Remove(keys);
if (result->HasError()) {
return result.Pass();
}
SyncResultIfEnabled(result);
return result.Pass();
}
ValueStore::WriteResult SyncableSettingsStorage::Clear() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
WriteResult result = delegate_->Clear();
if (result->HasError()) {
return result.Pass();
}
SyncResultIfEnabled(result);
return result.Pass();
}
void SyncableSettingsStorage::SyncResultIfEnabled(
const ValueStore::WriteResult& result) {
if (sync_processor_.get() && !result->changes().empty()) {
syncer::SyncError error = sync_processor_->SendChanges(result->changes());
if (error.IsSet())
StopSyncing();
}
}
// Sync-related methods.
syncer::SyncError SyncableSettingsStorage::StartSyncing(
const base::DictionaryValue& sync_state,
scoped_ptr<SettingsSyncProcessor> sync_processor) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!sync_processor_.get());
sync_processor_ = sync_processor.Pass();
sync_processor_->Init(sync_state);
ReadResult maybe_settings = delegate_->Get();
if (maybe_settings->HasError()) {
return syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
base::StringPrintf("Failed to get settings: %s",
maybe_settings->error().message.c_str()),
sync_processor_->type());
}
const base::DictionaryValue& settings = maybe_settings->settings();
return sync_state.empty() ?
SendLocalSettingsToSync(settings) :
OverwriteLocalSettingsWithSync(sync_state, settings);
}
syncer::SyncError SyncableSettingsStorage::SendLocalSettingsToSync(
const base::DictionaryValue& settings) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
ValueStoreChangeList changes;
for (base::DictionaryValue::Iterator i(settings); !i.IsAtEnd(); i.Advance()) {
changes.push_back(ValueStoreChange(i.key(), NULL, i.value().DeepCopy()));
}
if (changes.empty())
return syncer::SyncError();
syncer::SyncError error = sync_processor_->SendChanges(changes);
if (error.IsSet())
StopSyncing();
return error;
}
syncer::SyncError SyncableSettingsStorage::OverwriteLocalSettingsWithSync(
const base::DictionaryValue& sync_state,
const base::DictionaryValue& settings) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// Treat this as a list of changes to sync and use ProcessSyncChanges.
// This gives notifications etc for free.
scoped_ptr<base::DictionaryValue> new_sync_state(sync_state.DeepCopy());
SettingSyncDataList changes;
for (base::DictionaryValue::Iterator it(settings); !it.IsAtEnd(); it.Advance()) {
scoped_ptr<Value> sync_value;
if (new_sync_state->RemoveWithoutPathExpansion(it.key(), &sync_value)) {
if (sync_value->Equals(&it.value())) {
// Sync and local values are the same, no changes to send.
} else {
// Sync value is different, update local setting with new value.
changes.push_back(
SettingSyncData(
syncer::SyncChange::ACTION_UPDATE,
extension_id_,
it.key(),
sync_value.Pass()));
}
} else {
// Not synced, delete local setting.
changes.push_back(
SettingSyncData(
syncer::SyncChange::ACTION_DELETE,
extension_id_,
it.key(),
scoped_ptr<Value>(new base::DictionaryValue())));
}
}
// Add all new settings to local settings.
while (!new_sync_state->empty()) {
base::DictionaryValue::Iterator first_entry(*new_sync_state);
std::string key = first_entry.key();
scoped_ptr<Value> value;
CHECK(new_sync_state->RemoveWithoutPathExpansion(key, &value));
changes.push_back(
SettingSyncData(
syncer::SyncChange::ACTION_ADD,
extension_id_,
key,
value.Pass()));
}
if (changes.empty())
return syncer::SyncError();
return ProcessSyncChanges(changes);
}
void SyncableSettingsStorage::StopSyncing() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
sync_processor_.reset();
}
syncer::SyncError SyncableSettingsStorage::ProcessSyncChanges(
const SettingSyncDataList& sync_changes) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!sync_changes.empty()) << "No sync changes for " << extension_id_;
if (!sync_processor_.get()) {
return syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
std::string("Sync is inactive for ") + extension_id_,
syncer::UNSPECIFIED);
}
std::vector<syncer::SyncError> errors;
ValueStoreChangeList changes;
for (SettingSyncDataList::const_iterator it = sync_changes.begin();
it != sync_changes.end(); ++it) {
DCHECK_EQ(extension_id_, it->extension_id());
const std::string& key = it->key();
const Value& value = it->value();
scoped_ptr<Value> current_value;
{
ReadResult maybe_settings = Get(it->key());
if (maybe_settings->HasError()) {
errors.push_back(syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
base::StringPrintf("Error getting current sync state for %s/%s: %s",
extension_id_.c_str(), key.c_str(),
maybe_settings->error().message.c_str()),
sync_processor_->type()));
continue;
}
maybe_settings->settings().RemoveWithoutPathExpansion(key,
&current_value);
}
syncer::SyncError error;
switch (it->change_type()) {
case syncer::SyncChange::ACTION_ADD:
if (!current_value.get()) {
error = OnSyncAdd(key, value.DeepCopy(), &changes);
} else {
// Already a value; hopefully a local change has beaten sync in a
// race and it's not a bug, so pretend it's an update.
LOG(WARNING) << "Got add from sync for existing setting " <<
extension_id_ << "/" << key;
error = OnSyncUpdate(
key, current_value.release(), value.DeepCopy(), &changes);
}
break;
case syncer::SyncChange::ACTION_UPDATE:
if (current_value.get()) {
error = OnSyncUpdate(
key, current_value.release(), value.DeepCopy(), &changes);
} else {
// Similarly, pretend it's an add.
LOG(WARNING) << "Got update from sync for nonexistent setting" <<
extension_id_ << "/" << key;
error = OnSyncAdd(key, value.DeepCopy(), &changes);
}
break;
case syncer::SyncChange::ACTION_DELETE:
if (current_value.get()) {
error = OnSyncDelete(key, current_value.release(), &changes);
} else {
// Similarly, ignore it.
LOG(WARNING) << "Got delete from sync for nonexistent setting " <<
extension_id_ << "/" << key;
}
break;
default:
NOTREACHED();
}
if (error.IsSet()) {
errors.push_back(error);
}
}
sync_processor_->NotifyChanges(changes);
observers_->Notify(
&SettingsObserver::OnSettingsChanged,
extension_id_,
settings_namespace::SYNC,
ValueStoreChange::ToJson(changes));
// TODO(kalman): Something sensible with multiple errors.
return errors.empty() ? syncer::SyncError() : errors[0];
}
syncer::SyncError SyncableSettingsStorage::OnSyncAdd(
const std::string& key,
Value* new_value,
ValueStoreChangeList* changes) {
DCHECK(new_value);
WriteResult result = delegate_->Set(IGNORE_QUOTA, key, *new_value);
if (result->HasError()) {
return syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
base::StringPrintf("Error pushing sync add to local settings: %s",
result->error().message.c_str()),
sync_processor_->type());
}
changes->push_back(ValueStoreChange(key, NULL, new_value));
return syncer::SyncError();
}
syncer::SyncError SyncableSettingsStorage::OnSyncUpdate(
const std::string& key,
Value* old_value,
Value* new_value,
ValueStoreChangeList* changes) {
DCHECK(old_value);
DCHECK(new_value);
WriteResult result = delegate_->Set(IGNORE_QUOTA, key, *new_value);
if (result->HasError()) {
return syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
base::StringPrintf("Error pushing sync update to local settings: %s",
result->error().message.c_str()),
sync_processor_->type());
}
changes->push_back(ValueStoreChange(key, old_value, new_value));
return syncer::SyncError();
}
syncer::SyncError SyncableSettingsStorage::OnSyncDelete(
const std::string& key,
Value* old_value,
ValueStoreChangeList* changes) {
DCHECK(old_value);
WriteResult result = delegate_->Remove(key);
if (result->HasError()) {
return syncer::SyncError(
FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
base::StringPrintf("Error pushing sync remove to local settings: %s",
result->error().message.c_str()),
sync_processor_->type());
}
changes->push_back(ValueStoreChange(key, old_value, NULL));
return syncer::SyncError();
}
} // namespace extensions