// 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 #include "base/strings/stringprintf.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 "extensions/browser/api/storage/settings_namespace.h" #include "sync/api/sync_data.h" #include "sync/protocol/extension_setting_specifics.pb.h" using content::BrowserThread; namespace extensions { SyncableSettingsStorage::SyncableSettingsStorage( const scoped_refptr>& observers, const std::string& extension_id, ValueStore* delegate, syncer::ModelType sync_type, const syncer::SyncableService::StartSyncFlare& flare) : observers_(observers), extension_id_(extension_id), delegate_(delegate), sync_type_(sync_type), flare_(flare) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); } SyncableSettingsStorage::~SyncableSettingsStorage() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); } size_t SyncableSettingsStorage::GetBytesInUse(const std::string& key) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return delegate_->GetBytesInUse(key); } size_t SyncableSettingsStorage::GetBytesInUse( const std::vector& keys) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return delegate_->GetBytesInUse(keys); } size_t SyncableSettingsStorage::GetBytesInUse() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return delegate_->GetBytesInUse(); } template T SyncableSettingsStorage::HandleResult(T result) { if (result->status().restore_status != RESTORE_NONE) { // If we're syncing, stop - we don't want to push the deletion of any data. // At next startup, when we start up the sync service, we'll get back any // data which was stored intact on Sync. // TODO(devlin): Investigate if there's a way we can trigger // MergeDataAndStartSyncing() to immediately get back any data we can, and // continue syncing. StopSyncing(); } return result; } ValueStore::ReadResult SyncableSettingsStorage::Get( const std::string& key) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return HandleResult(delegate_->Get(key)); } ValueStore::ReadResult SyncableSettingsStorage::Get( const std::vector& keys) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return HandleResult(delegate_->Get(keys)); } ValueStore::ReadResult SyncableSettingsStorage::Get() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return HandleResult(delegate_->Get()); } ValueStore::WriteResult SyncableSettingsStorage::Set( WriteOptions options, const std::string& key, const base::Value& value) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); WriteResult result = HandleResult(delegate_->Set(options, key, value)); if (!result->status().ok()) return result; SyncResultIfEnabled(result); return result; } ValueStore::WriteResult SyncableSettingsStorage::Set( WriteOptions options, const base::DictionaryValue& values) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); WriteResult result = HandleResult(delegate_->Set(options, values)); if (!result->status().ok()) return result; SyncResultIfEnabled(result); return result; } ValueStore::WriteResult SyncableSettingsStorage::Remove( const std::string& key) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); WriteResult result = HandleResult(delegate_->Remove(key)); if (!result->status().ok()) return result; SyncResultIfEnabled(result); return result; } ValueStore::WriteResult SyncableSettingsStorage::Remove( const std::vector& keys) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); WriteResult result = HandleResult(delegate_->Remove(keys)); if (!result->status().ok()) return result; SyncResultIfEnabled(result); return result; } ValueStore::WriteResult SyncableSettingsStorage::Clear() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); WriteResult result = HandleResult(delegate_->Clear()); if (!result->status().ok()) return result; SyncResultIfEnabled(result); return result; } void SyncableSettingsStorage::SyncResultIfEnabled( const ValueStore::WriteResult& result) { if (result->changes().empty()) return; if (sync_processor_.get()) { syncer::SyncError error = sync_processor_->SendChanges(result->changes()); if (error.IsSet()) StopSyncing(); } else { // Tell sync to try and start soon, because syncable changes to sync_type_ // have started happening. This will cause sync to call us back // asynchronously via StartSyncing(...) as soon as possible. flare_.Run(sync_type_); } } // Sync-related methods. syncer::SyncError SyncableSettingsStorage::StartSyncing( scoped_ptr sync_state, scoped_ptr sync_processor) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(sync_state); DCHECK(!sync_processor_.get()); sync_processor_ = std::move(sync_processor); sync_processor_->Init(*sync_state); ReadResult maybe_settings = delegate_->Get(); if (!maybe_settings->status().ok()) { return syncer::SyncError( FROM_HERE, syncer::SyncError::DATATYPE_ERROR, base::StringPrintf("Failed to get settings: %s", maybe_settings->status().message.c_str()), sync_processor_->type()); } scoped_ptr current_settings = maybe_settings->PassSettings(); return sync_state->empty() ? SendLocalSettingsToSync(std::move(current_settings)) : OverwriteLocalSettingsWithSync(std::move(sync_state), std::move(current_settings)); } syncer::SyncError SyncableSettingsStorage::SendLocalSettingsToSync( scoped_ptr local_state) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (local_state->empty()) return syncer::SyncError(); // Transform the current settings into a list of sync changes. ValueStoreChangeList changes; while (!local_state->empty()) { // It's not possible to iterate over a DictionaryValue and modify it at the // same time, so hack around that restriction. std::string key = base::DictionaryValue::Iterator(*local_state).key(); scoped_ptr value; local_state->RemoveWithoutPathExpansion(key, &value); changes.push_back(ValueStoreChange(key, nullptr, value.release())); } syncer::SyncError error = sync_processor_->SendChanges(changes); if (error.IsSet()) StopSyncing(); return error; } syncer::SyncError SyncableSettingsStorage::OverwriteLocalSettingsWithSync( scoped_ptr sync_state, scoped_ptr local_state) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); // This is implemented by building up a list of sync changes then sending // those to ProcessSyncChanges. This generates events like onStorageChanged. scoped_ptr changes(new SettingSyncDataList()); for (base::DictionaryValue::Iterator it(*local_state); !it.IsAtEnd(); it.Advance()) { scoped_ptr sync_value; if (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(new SettingSyncData( syncer::SyncChange::ACTION_UPDATE, extension_id_, it.key(), std::move(sync_value))); } } else { // Not synced, delete local setting. changes->push_back(new SettingSyncData( syncer::SyncChange::ACTION_DELETE, extension_id_, it.key(), scoped_ptr(new base::DictionaryValue()))); } } // Add all new settings to local settings. while (!sync_state->empty()) { // It's not possible to iterate over a DictionaryValue and modify it at the // same time, so hack around that restriction. std::string key = base::DictionaryValue::Iterator(*sync_state).key(); scoped_ptr value; CHECK(sync_state->RemoveWithoutPathExpansion(key, &value)); changes->push_back(new SettingSyncData( syncer::SyncChange::ACTION_ADD, extension_id_, key, std::move(value))); } if (changes->empty()) return syncer::SyncError(); return ProcessSyncChanges(std::move(changes)); } void SyncableSettingsStorage::StopSyncing() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); sync_processor_.reset(); } syncer::SyncError SyncableSettingsStorage::ProcessSyncChanges( scoped_ptr sync_changes) { DCHECK_CURRENTLY_ON(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 errors; ValueStoreChangeList changes; for (SettingSyncDataList::iterator it = sync_changes->begin(); it != sync_changes->end(); ++it) { DCHECK_EQ(extension_id_, (*it)->extension_id()); const std::string& key = (*it)->key(); scoped_ptr change_value = (*it)->PassValue(); scoped_ptr current_value; { ReadResult maybe_settings = Get(key); if (!maybe_settings->status().ok()) { 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->status().message.c_str()), sync_processor_->type())); continue; } maybe_settings->settings().RemoveWithoutPathExpansion(key, ¤t_value); } syncer::SyncError error; switch ((*it)->change_type()) { case syncer::SyncChange::ACTION_ADD: if (!current_value.get()) { error = OnSyncAdd(key, change_value.release(), &changes); } else { // Already a value; hopefully a local change has beaten sync in a // race and change's not a bug, so pretend change's an update. LOG(WARNING) << "Got add from sync for existing setting " << extension_id_ << "/" << key; error = OnSyncUpdate(key, current_value.release(), change_value.release(), &changes); } break; case syncer::SyncChange::ACTION_UPDATE: if (current_value.get()) { error = OnSyncUpdate(key, current_value.release(), change_value.release(), &changes); } else { // Similarly, pretend change's an add. LOG(WARNING) << "Got update from sync for nonexistent setting" << extension_id_ << "/" << key; error = OnSyncAdd(key, change_value.release(), &changes); } break; case syncer::SyncChange::ACTION_DELETE: if (current_value.get()) { error = OnSyncDelete(key, current_value.release(), &changes); } else { // Similarly, ignore change. 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(FROM_HERE, &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, base::Value* new_value, ValueStoreChangeList* changes) { DCHECK(new_value); WriteResult result = HandleResult(delegate_->Set(IGNORE_QUOTA, key, *new_value)); if (!result->status().ok()) { return syncer::SyncError( FROM_HERE, syncer::SyncError::DATATYPE_ERROR, base::StringPrintf("Error pushing sync add to local settings: %s", result->status().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, base::Value* old_value, base::Value* new_value, ValueStoreChangeList* changes) { DCHECK(old_value); DCHECK(new_value); WriteResult result = HandleResult(delegate_->Set(IGNORE_QUOTA, key, *new_value)); if (!result->status().ok()) { return syncer::SyncError( FROM_HERE, syncer::SyncError::DATATYPE_ERROR, base::StringPrintf("Error pushing sync update to local settings: %s", result->status().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, base::Value* old_value, ValueStoreChangeList* changes) { DCHECK(old_value); WriteResult result = HandleResult(delegate_->Remove(key)); if (!result->status().ok()) { return syncer::SyncError( FROM_HERE, syncer::SyncError::DATATYPE_ERROR, base::StringPrintf("Error pushing sync remove to local settings: %s", result->status().message.c_str()), sync_processor_->type()); } changes->push_back(ValueStoreChange(key, old_value, NULL)); return syncer::SyncError(); } } // namespace extensions