| // 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 <utility> |
| |
| #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<base::ObserverListThreadSafe<SettingsObserver>>& |
| 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<std::string>& keys) { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| return delegate_->GetBytesInUse(keys); |
| } |
| |
| size_t SyncableSettingsStorage::GetBytesInUse() { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| return delegate_->GetBytesInUse(); |
| } |
| |
| template <class T> |
| 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<std::string>& 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<std::string>& 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( |
| std::unique_ptr<base::DictionaryValue> sync_state, |
| std::unique_ptr<SettingsSyncProcessor> 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()); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> 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( |
| std::unique_ptr<base::DictionaryValue> 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(); |
| std::unique_ptr<base::Value> value; |
| local_state->RemoveWithoutPathExpansion(key, &value); |
| changes.push_back(ValueStoreChange(key, nullptr, std::move(value))); |
| } |
| |
| syncer::SyncError error = sync_processor_->SendChanges(changes); |
| if (error.IsSet()) |
| StopSyncing(); |
| return error; |
| } |
| |
| syncer::SyncError SyncableSettingsStorage::OverwriteLocalSettingsWithSync( |
| std::unique_ptr<base::DictionaryValue> sync_state, |
| std::unique_ptr<base::DictionaryValue> 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. |
| std::unique_ptr<SettingSyncDataList> changes(new SettingSyncDataList()); |
| |
| for (base::DictionaryValue::Iterator it(*local_state); !it.IsAtEnd(); |
| it.Advance()) { |
| std::unique_ptr<base::Value> 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(), |
| std::unique_ptr<base::Value>(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(); |
| std::unique_ptr<base::Value> 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( |
| std::unique_ptr<SettingSyncDataList> 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<syncer::SyncError> 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(); |
| std::unique_ptr<base::Value> change_value = (*it)->PassValue(); |
| |
| std::unique_ptr<base::Value> 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, std::move(change_value), &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, std::move(current_value), |
| std::move(change_value), &changes); |
| } |
| break; |
| |
| case syncer::SyncChange::ACTION_UPDATE: |
| if (current_value.get()) { |
| error = OnSyncUpdate(key, std::move(current_value), |
| std::move(change_value), &changes); |
| } else { |
| // Similarly, pretend change's an add. |
| LOG(WARNING) << "Got update from sync for nonexistent setting" << |
| extension_id_ << "/" << key; |
| error = OnSyncAdd(key, std::move(change_value), &changes); |
| } |
| break; |
| |
| case syncer::SyncChange::ACTION_DELETE: |
| if (current_value.get()) { |
| error = OnSyncDelete(key, std::move(current_value), &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, |
| std::unique_ptr<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, nullptr, std::move(new_value))); |
| return syncer::SyncError(); |
| } |
| |
| syncer::SyncError SyncableSettingsStorage::OnSyncUpdate( |
| const std::string& key, |
| std::unique_ptr<base::Value> old_value, |
| std::unique_ptr<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, std::move(old_value), std::move(new_value))); |
| return syncer::SyncError(); |
| } |
| |
| syncer::SyncError SyncableSettingsStorage::OnSyncDelete( |
| const std::string& key, |
| std::unique_ptr<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, std::move(old_value), nullptr)); |
| return syncer::SyncError(); |
| } |
| |
| } // namespace extensions |