|  | // 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 "components/sync_driver/generic_change_processor.h" | 
|  |  | 
|  | #include "base/location.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "components/sync_driver/sync_api_component_factory.h" | 
|  | #include "sync/api/sync_change.h" | 
|  | #include "sync/api/sync_error.h" | 
|  | #include "sync/api/syncable_service.h" | 
|  | #include "sync/internal_api/public/base_node.h" | 
|  | #include "sync/internal_api/public/change_record.h" | 
|  | #include "sync/internal_api/public/read_node.h" | 
|  | #include "sync/internal_api/public/read_transaction.h" | 
|  | #include "sync/internal_api/public/util/unrecoverable_error_handler.h" | 
|  | #include "sync/internal_api/public/write_node.h" | 
|  | #include "sync/internal_api/public/write_transaction.h" | 
|  | #include "sync/syncable/entry.h"  // TODO(tim): Bug 123674. | 
|  |  | 
|  | namespace sync_driver { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const int kContextSizeLimit = 1024;  // Datatype context size limit. | 
|  |  | 
|  | void SetNodeSpecifics(const sync_pb::EntitySpecifics& entity_specifics, | 
|  | syncer::WriteNode* write_node) { | 
|  | if (syncer::GetModelTypeFromSpecifics(entity_specifics) == | 
|  | syncer::PASSWORDS) { | 
|  | write_node->SetPasswordSpecifics( | 
|  | entity_specifics.password().client_only_encrypted_data()); | 
|  | } else { | 
|  | write_node->SetEntitySpecifics(entity_specifics); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Helper function to convert AttachmentId to AttachmentMetadataRecord. | 
|  | sync_pb::AttachmentMetadataRecord AttachmentIdToRecord( | 
|  | const syncer::AttachmentId& attachment_id) { | 
|  | sync_pb::AttachmentMetadataRecord record; | 
|  | *record.mutable_id() = attachment_id.GetProto(); | 
|  | return record; | 
|  | } | 
|  |  | 
|  | // Replace |write_nodes|'s attachment ids with |attachment_ids|. | 
|  | void SetAttachmentMetadata(const syncer::AttachmentIdList& attachment_ids, | 
|  | syncer::WriteNode* write_node) { | 
|  | DCHECK(write_node); | 
|  | sync_pb::AttachmentMetadata attachment_metadata; | 
|  | std::transform( | 
|  | attachment_ids.begin(), | 
|  | attachment_ids.end(), | 
|  | RepeatedFieldBackInserter(attachment_metadata.mutable_record()), | 
|  | AttachmentIdToRecord); | 
|  | write_node->SetAttachmentMetadata(attachment_metadata); | 
|  | } | 
|  |  | 
|  | syncer::SyncData BuildRemoteSyncData( | 
|  | int64 sync_id, | 
|  | const syncer::BaseNode& read_node, | 
|  | const syncer::AttachmentServiceProxy& attachment_service_proxy) { | 
|  | const syncer::AttachmentIdList& attachment_ids = read_node.GetAttachmentIds(); | 
|  | // Use the specifics of non-password datatypes directly (encryption has | 
|  | // already been handled). | 
|  | if (read_node.GetModelType() != syncer::PASSWORDS) { | 
|  | return syncer::SyncData::CreateRemoteData(sync_id, | 
|  | read_node.GetEntitySpecifics(), | 
|  | read_node.GetModificationTime(), | 
|  | attachment_ids, | 
|  | attachment_service_proxy); | 
|  | } | 
|  |  | 
|  | // Passwords must be accessed differently, to account for their encryption, | 
|  | // and stored into a temporary EntitySpecifics. | 
|  | sync_pb::EntitySpecifics password_holder; | 
|  | password_holder.mutable_password()->mutable_client_only_encrypted_data()-> | 
|  | CopyFrom(read_node.GetPasswordSpecifics()); | 
|  | return syncer::SyncData::CreateRemoteData(sync_id, | 
|  | password_holder, | 
|  | read_node.GetModificationTime(), | 
|  | attachment_ids, | 
|  | attachment_service_proxy); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | GenericChangeProcessor::GenericChangeProcessor( | 
|  | DataTypeErrorHandler* error_handler, | 
|  | const base::WeakPtr<syncer::SyncableService>& local_service, | 
|  | const base::WeakPtr<syncer::SyncMergeResult>& merge_result, | 
|  | syncer::UserShare* user_share, | 
|  | SyncApiComponentFactory* sync_factory) | 
|  | : ChangeProcessor(error_handler), | 
|  | local_service_(local_service), | 
|  | merge_result_(merge_result), | 
|  | share_handle_(user_share), | 
|  | attachment_service_( | 
|  | sync_factory->CreateAttachmentService(*user_share, this)), | 
|  | attachment_service_weak_ptr_factory_(attachment_service_.get()), | 
|  | attachment_service_proxy_( | 
|  | base::MessageLoopProxy::current(), | 
|  | attachment_service_weak_ptr_factory_.GetWeakPtr()) { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | DCHECK(attachment_service_); | 
|  | } | 
|  |  | 
|  | GenericChangeProcessor::~GenericChangeProcessor() { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | } | 
|  |  | 
|  | void GenericChangeProcessor::ApplyChangesFromSyncModel( | 
|  | const syncer::BaseTransaction* trans, | 
|  | int64 model_version, | 
|  | const syncer::ImmutableChangeRecordList& changes) { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | DCHECK(syncer_changes_.empty()); | 
|  | for (syncer::ChangeRecordList::const_iterator it = | 
|  | changes.Get().begin(); it != changes.Get().end(); ++it) { | 
|  | if (it->action == syncer::ChangeRecord::ACTION_DELETE) { | 
|  | scoped_ptr<sync_pb::EntitySpecifics> specifics; | 
|  | if (it->specifics.has_password()) { | 
|  | DCHECK(it->extra.get()); | 
|  | specifics.reset(new sync_pb::EntitySpecifics(it->specifics)); | 
|  | specifics->mutable_password()->mutable_client_only_encrypted_data()-> | 
|  | CopyFrom(it->extra->unencrypted()); | 
|  | } | 
|  | const syncer::AttachmentIdList empty_list_of_attachment_ids; | 
|  | syncer_changes_.push_back( | 
|  | syncer::SyncChange(FROM_HERE, | 
|  | syncer::SyncChange::ACTION_DELETE, | 
|  | syncer::SyncData::CreateRemoteData( | 
|  | it->id, | 
|  | specifics ? *specifics : it->specifics, | 
|  | base::Time(), | 
|  | empty_list_of_attachment_ids, | 
|  | attachment_service_proxy_))); | 
|  | } else { | 
|  | syncer::SyncChange::SyncChangeType action = | 
|  | (it->action == syncer::ChangeRecord::ACTION_ADD) ? | 
|  | syncer::SyncChange::ACTION_ADD : syncer::SyncChange::ACTION_UPDATE; | 
|  | // Need to load specifics from node. | 
|  | syncer::ReadNode read_node(trans); | 
|  | if (read_node.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) { | 
|  | syncer::SyncError error( | 
|  | FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Failed to look up data for received change with id " + | 
|  | base::Int64ToString(it->id), | 
|  | syncer::GetModelTypeFromSpecifics(it->specifics)); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | return; | 
|  | } | 
|  | syncer_changes_.push_back(syncer::SyncChange( | 
|  | FROM_HERE, | 
|  | action, | 
|  | BuildRemoteSyncData(it->id, read_node, attachment_service_proxy_))); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void GenericChangeProcessor::CommitChangesFromSyncModel() { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | if (syncer_changes_.empty()) | 
|  | return; | 
|  | if (!local_service_.get()) { | 
|  | syncer::ModelType type = syncer_changes_[0].sync_data().GetDataType(); | 
|  | syncer::SyncError error(FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Local service destroyed.", | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | return; | 
|  | } | 
|  | syncer::SyncError error = local_service_->ProcessSyncChanges(FROM_HERE, | 
|  | syncer_changes_); | 
|  | syncer_changes_.clear(); | 
|  | if (error.IsSet()) | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | } | 
|  |  | 
|  | syncer::SyncDataList GenericChangeProcessor::GetAllSyncData( | 
|  | syncer::ModelType type) const { | 
|  | // This is slow / memory intensive.  Should be used sparingly by datatypes. | 
|  | syncer::SyncDataList data; | 
|  | GetAllSyncDataReturnError(type, &data); | 
|  | return data; | 
|  | } | 
|  |  | 
|  | syncer::SyncError GenericChangeProcessor::UpdateDataTypeContext( | 
|  | syncer::ModelType type, | 
|  | syncer::SyncChangeProcessor::ContextRefreshStatus refresh_status, | 
|  | const std::string& context) { | 
|  | DCHECK(syncer::ProtocolTypes().Has(type)); | 
|  |  | 
|  | if (context.size() > static_cast<size_t>(kContextSizeLimit)) { | 
|  | return syncer::SyncError(FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Context size limit exceeded.", | 
|  | type); | 
|  | } | 
|  |  | 
|  | syncer::WriteTransaction trans(FROM_HERE, share_handle()); | 
|  | trans.SetDataTypeContext(type, refresh_status, context); | 
|  |  | 
|  | // TODO(zea): plumb a pointer to the PSS or SyncManagerImpl here so we can | 
|  | // trigger a datatype nudge if |refresh_status == REFRESH_NEEDED|. | 
|  |  | 
|  | return syncer::SyncError(); | 
|  | } | 
|  |  | 
|  | void GenericChangeProcessor::OnAttachmentUploaded( | 
|  | const syncer::AttachmentId& attachment_id) { | 
|  | syncer::WriteTransaction trans(FROM_HERE, share_handle()); | 
|  | trans.UpdateEntriesMarkAttachmentAsOnServer(attachment_id); | 
|  | } | 
|  |  | 
|  | syncer::SyncError GenericChangeProcessor::GetAllSyncDataReturnError( | 
|  | syncer::ModelType type, | 
|  | syncer::SyncDataList* current_sync_data) const { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | std::string type_name = syncer::ModelTypeToString(type); | 
|  | syncer::ReadTransaction trans(FROM_HERE, share_handle()); | 
|  | syncer::ReadNode root(&trans); | 
|  | if (root.InitTypeRoot(type) != syncer::BaseNode::INIT_OK) { | 
|  | syncer::SyncError error(FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Server did not create the top-level " + type_name + | 
|  | " node. We might be running against an out-of-" | 
|  | "date server.", | 
|  | type); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | // TODO(akalin): We'll have to do a tree traversal for bookmarks. | 
|  | DCHECK_NE(type, syncer::BOOKMARKS); | 
|  |  | 
|  | std::vector<int64> child_ids; | 
|  | root.GetChildIds(&child_ids); | 
|  |  | 
|  | for (std::vector<int64>::iterator it = child_ids.begin(); | 
|  | it != child_ids.end(); ++it) { | 
|  | syncer::ReadNode sync_child_node(&trans); | 
|  | if (sync_child_node.InitByIdLookup(*it) != | 
|  | syncer::BaseNode::INIT_OK) { | 
|  | syncer::SyncError error(FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Failed to fetch child node for type " + | 
|  | type_name + ".", | 
|  | type); | 
|  | return error; | 
|  | } | 
|  | current_sync_data->push_back(BuildRemoteSyncData( | 
|  | sync_child_node.GetId(), sync_child_node, attachment_service_proxy_)); | 
|  | } | 
|  | return syncer::SyncError(); | 
|  | } | 
|  |  | 
|  | bool GenericChangeProcessor::GetDataTypeContext(syncer::ModelType type, | 
|  | std::string* context) const { | 
|  | syncer::ReadTransaction trans(FROM_HERE, share_handle()); | 
|  | sync_pb::DataTypeContext context_proto; | 
|  | trans.GetDataTypeContext(type, &context_proto); | 
|  | if (!context_proto.has_context()) | 
|  | return false; | 
|  |  | 
|  | DCHECK_EQ(type, | 
|  | syncer::GetModelTypeFromSpecificsFieldNumber( | 
|  | context_proto.data_type_id())); | 
|  | *context = context_proto.context(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int GenericChangeProcessor::GetSyncCountForType(syncer::ModelType type) { | 
|  | syncer::ReadTransaction trans(FROM_HERE, share_handle()); | 
|  | syncer::ReadNode root(&trans); | 
|  | if (root.InitTypeRoot(type) != syncer::BaseNode::INIT_OK) | 
|  | return 0; | 
|  |  | 
|  | // Subtract one to account for type's root node. | 
|  | return root.GetTotalNodeCount() - 1; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // WARNING: this code is sensitive to compiler optimizations. Be careful | 
|  | // modifying any code around an OnSingleDataTypeUnrecoverableError call, else | 
|  | // the compiler attempts to merge it with other calls, losing useful information | 
|  | // in breakpad uploads. | 
|  | syncer::SyncError LogLookupFailure( | 
|  | syncer::BaseNode::InitByLookupResult lookup_result, | 
|  | const tracked_objects::Location& from_here, | 
|  | const std::string& error_prefix, | 
|  | syncer::ModelType type, | 
|  | DataTypeErrorHandler* error_handler) { | 
|  | switch (lookup_result) { | 
|  | case syncer::BaseNode::INIT_FAILED_ENTRY_NOT_GOOD: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(from_here, | 
|  | error_prefix + | 
|  | "could not find entry matching the lookup criteria.", | 
|  | type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Delete: Bad entry."; | 
|  | return error; | 
|  | } | 
|  | case syncer::BaseNode::INIT_FAILED_ENTRY_IS_DEL: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(from_here, error_prefix + "entry is already deleted.", type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Delete: Deleted entry."; | 
|  | return error; | 
|  | } | 
|  | case syncer::BaseNode::INIT_FAILED_DECRYPT_IF_NECESSARY: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(from_here, error_prefix + "unable to decrypt", type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Delete: Undecryptable entry."; | 
|  | return error; | 
|  | } | 
|  | case syncer::BaseNode::INIT_FAILED_PRECONDITION: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(from_here, | 
|  | error_prefix + "a precondition was not met for calling init.", | 
|  | type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Delete: Failed precondition."; | 
|  | return error; | 
|  | } | 
|  | default: { | 
|  | syncer::SyncError error; | 
|  | // Should have listed all the possible error cases above. | 
|  | error.Reset(from_here, error_prefix + "unknown error", type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Delete: Unknown error."; | 
|  | return error; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | syncer::SyncError AttemptDelete(const syncer::SyncChange& change, | 
|  | syncer::ModelType type, | 
|  | const std::string& type_str, | 
|  | syncer::WriteNode* node, | 
|  | DataTypeErrorHandler* error_handler) { | 
|  | DCHECK_EQ(change.change_type(), syncer::SyncChange::ACTION_DELETE); | 
|  | if (change.sync_data().IsLocal()) { | 
|  | const std::string& tag = syncer::SyncDataLocal(change.sync_data()).GetTag(); | 
|  | if (tag.empty()) { | 
|  | syncer::SyncError error( | 
|  | FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Failed to delete " + type_str + " node. Local data, empty tag. " + | 
|  | change.location().ToString(), | 
|  | type); | 
|  | error_handler->OnSingleDataTypeUnrecoverableError(error); | 
|  | NOTREACHED(); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | syncer::BaseNode::InitByLookupResult result = | 
|  | node->InitByClientTagLookup(change.sync_data().GetDataType(), tag); | 
|  | if (result != syncer::BaseNode::INIT_OK) { | 
|  | return LogLookupFailure( | 
|  | result, FROM_HERE, | 
|  | "Failed to delete " + type_str + " node. Local data. " + | 
|  | change.location().ToString(), | 
|  | type, error_handler); | 
|  | } | 
|  | } else { | 
|  | syncer::BaseNode::InitByLookupResult result = node->InitByIdLookup( | 
|  | syncer::SyncDataRemote(change.sync_data()).GetId()); | 
|  | if (result != syncer::BaseNode::INIT_OK) { | 
|  | return LogLookupFailure( | 
|  | result, FROM_HERE, | 
|  | "Failed to delete " + type_str + " node. Non-local data. " + | 
|  | change.location().ToString(), | 
|  | type, error_handler); | 
|  | } | 
|  | } | 
|  | if (IsActOnceDataType(type)) | 
|  | node->Drop(); | 
|  | else | 
|  | node->Tombstone(); | 
|  | return syncer::SyncError(); | 
|  | } | 
|  |  | 
|  | // A callback invoked on completion of AttachmentService::StoreAttachment. | 
|  | void IgnoreStoreResult(const syncer::AttachmentService::StoreResult&) { | 
|  | // TODO(maniscalco): Here is where we're going to update the in-directory | 
|  | // entry to indicate that the attachments have been successfully stored on | 
|  | // disk.  Why do we care?  Because we might crash after persisting the | 
|  | // directory to disk, but before we have persisted its attachments, leaving us | 
|  | // with danging attachment ids.  Having a flag that indicates we've stored the | 
|  | // entry will allow us to detect and filter entries with dangling attachment | 
|  | // ids (bug 368353). | 
|  | } | 
|  |  | 
|  | void StoreAttachments(syncer::AttachmentService* attachment_service, | 
|  | const syncer::AttachmentList& attachments) { | 
|  | DCHECK(attachment_service); | 
|  | syncer::AttachmentService::StoreCallback ignore_store_result = | 
|  | base::Bind(&IgnoreStoreResult); | 
|  | attachment_service->StoreAttachments(attachments, ignore_store_result); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | syncer::SyncError GenericChangeProcessor::ProcessSyncChanges( | 
|  | const tracked_objects::Location& from_here, | 
|  | const syncer::SyncChangeList& list_of_changes) { | 
|  | DCHECK(CalledOnValidThread()); | 
|  |  | 
|  | // Keep track of brand new attachments so we can persist them on this device | 
|  | // and upload them to the server. | 
|  | syncer::AttachmentList new_attachments; | 
|  |  | 
|  | syncer::WriteTransaction trans(from_here, share_handle()); | 
|  |  | 
|  | for (syncer::SyncChangeList::const_iterator iter = list_of_changes.begin(); | 
|  | iter != list_of_changes.end(); | 
|  | ++iter) { | 
|  | const syncer::SyncChange& change = *iter; | 
|  | DCHECK_NE(change.sync_data().GetDataType(), syncer::UNSPECIFIED); | 
|  | syncer::ModelType type = change.sync_data().GetDataType(); | 
|  | std::string type_str = syncer::ModelTypeToString(type); | 
|  | syncer::WriteNode sync_node(&trans); | 
|  | if (change.change_type() == syncer::SyncChange::ACTION_DELETE) { | 
|  | syncer::SyncError error = | 
|  | AttemptDelete(change, type, type_str, &sync_node, error_handler()); | 
|  | if (error.IsSet()) { | 
|  | NOTREACHED(); | 
|  | return error; | 
|  | } | 
|  | if (merge_result_.get()) { | 
|  | merge_result_->set_num_items_deleted( | 
|  | merge_result_->num_items_deleted() + 1); | 
|  | } | 
|  | } else if (change.change_type() == syncer::SyncChange::ACTION_ADD) { | 
|  | syncer::SyncError error = HandleActionAdd( | 
|  | change, type_str, type, trans, &sync_node, &new_attachments); | 
|  | if (error.IsSet()) { | 
|  | return error; | 
|  | } | 
|  | } else if (change.change_type() == syncer::SyncChange::ACTION_UPDATE) { | 
|  | syncer::SyncError error = HandleActionUpdate( | 
|  | change, type_str, type, trans, &sync_node, &new_attachments); | 
|  | if (error.IsSet()) { | 
|  | return error; | 
|  | } | 
|  | } else { | 
|  | syncer::SyncError error( | 
|  | FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Received unset SyncChange in the change processor, " + | 
|  | change.location().ToString(), | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | NOTREACHED(); | 
|  | LOG(ERROR) << "Unset sync change."; | 
|  | return error; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!new_attachments.empty()) { | 
|  | StoreAttachments(attachment_service_.get(), new_attachments); | 
|  | } | 
|  |  | 
|  | return syncer::SyncError(); | 
|  | } | 
|  |  | 
|  | // WARNING: this code is sensitive to compiler optimizations. Be careful | 
|  | // modifying any code around an OnSingleDataTypeUnrecoverableError call, else | 
|  | // the compiler attempts to merge it with other calls, losing useful information | 
|  | // in breakpad uploads. | 
|  | syncer::SyncError GenericChangeProcessor::HandleActionAdd( | 
|  | const syncer::SyncChange& change, | 
|  | const std::string& type_str, | 
|  | const syncer::ModelType& type, | 
|  | const syncer::WriteTransaction& trans, | 
|  | syncer::WriteNode* sync_node, | 
|  | syncer::AttachmentList* new_attachments) { | 
|  | // TODO(sync): Handle other types of creation (custom parents, folders, | 
|  | // etc.). | 
|  | syncer::ReadNode root_node(&trans); | 
|  | const syncer::SyncDataLocal sync_data_local(change.sync_data()); | 
|  | if (root_node.InitTypeRoot(sync_data_local.GetDataType()) != | 
|  | syncer::BaseNode::INIT_OK) { | 
|  | syncer::SyncError error(FROM_HERE, | 
|  | syncer::SyncError::DATATYPE_ERROR, | 
|  | "Failed to look up root node for type " + type_str, | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | NOTREACHED(); | 
|  | LOG(ERROR) << "Create: no root node."; | 
|  | return error; | 
|  | } | 
|  | syncer::WriteNode::InitUniqueByCreationResult result = | 
|  | sync_node->InitUniqueByCreation( | 
|  | sync_data_local.GetDataType(), root_node, sync_data_local.GetTag()); | 
|  | if (result != syncer::WriteNode::INIT_SUCCESS) { | 
|  | std::string error_prefix = "Failed to create " + type_str + " node: " + | 
|  | change.location().ToString() + ", "; | 
|  | switch (result) { | 
|  | case syncer::WriteNode::INIT_FAILED_EMPTY_TAG: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "empty tag", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Create: Empty tag."; | 
|  | return error; | 
|  | } | 
|  | case syncer::WriteNode::INIT_FAILED_ENTRY_ALREADY_EXISTS: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "entry already exists", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Create: Entry exists."; | 
|  | return error; | 
|  | } | 
|  | case syncer::WriteNode::INIT_FAILED_COULD_NOT_CREATE_ENTRY: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "failed to create entry", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Create: Could not create entry."; | 
|  | return error; | 
|  | } | 
|  | case syncer::WriteNode::INIT_FAILED_SET_PREDECESSOR: { | 
|  | syncer::SyncError error; | 
|  | error.Reset( | 
|  | FROM_HERE, error_prefix + "failed to set predecessor", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Create: Bad predecessor."; | 
|  | return error; | 
|  | } | 
|  | default: { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "unknown error", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Create: Unknown error."; | 
|  | return error; | 
|  | } | 
|  | } | 
|  | } | 
|  | sync_node->SetTitle(change.sync_data().GetTitle()); | 
|  | SetNodeSpecifics(sync_data_local.GetSpecifics(), sync_node); | 
|  |  | 
|  | syncer::AttachmentIdList attachment_ids = sync_data_local.GetAttachmentIds(); | 
|  | SetAttachmentMetadata(attachment_ids, sync_node); | 
|  |  | 
|  | // Return any newly added attachments. | 
|  | const syncer::AttachmentList& local_attachments_for_upload = | 
|  | sync_data_local.GetLocalAttachmentsForUpload(); | 
|  | new_attachments->insert(new_attachments->end(), | 
|  | local_attachments_for_upload.begin(), | 
|  | local_attachments_for_upload.end()); | 
|  |  | 
|  | if (merge_result_.get()) { | 
|  | merge_result_->set_num_items_added(merge_result_->num_items_added() + 1); | 
|  | } | 
|  | return syncer::SyncError(); | 
|  | } | 
|  | // WARNING: this code is sensitive to compiler optimizations. Be careful | 
|  | // modifying any code around an OnSingleDataTypeUnrecoverableError call, else | 
|  | // the compiler attempts to merge it with other calls, losing useful information | 
|  | // in breakpad uploads. | 
|  | syncer::SyncError GenericChangeProcessor::HandleActionUpdate( | 
|  | const syncer::SyncChange& change, | 
|  | const std::string& type_str, | 
|  | const syncer::ModelType& type, | 
|  | const syncer::WriteTransaction& trans, | 
|  | syncer::WriteNode* sync_node, | 
|  | syncer::AttachmentList* new_attachments) { | 
|  | // TODO(zea): consider having this logic for all possible changes? | 
|  |  | 
|  | const syncer::SyncDataLocal sync_data_local(change.sync_data()); | 
|  | syncer::BaseNode::InitByLookupResult result = | 
|  | sync_node->InitByClientTagLookup(sync_data_local.GetDataType(), | 
|  | sync_data_local.GetTag()); | 
|  | if (result != syncer::BaseNode::INIT_OK) { | 
|  | std::string error_prefix = "Failed to load " + type_str + " node. " + | 
|  | change.location().ToString() + ", "; | 
|  | if (result == syncer::BaseNode::INIT_FAILED_PRECONDITION) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "empty tag", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: Empty tag."; | 
|  | return error; | 
|  | } else if (result == syncer::BaseNode::INIT_FAILED_ENTRY_NOT_GOOD) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "bad entry", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: bad entry."; | 
|  | return error; | 
|  | } else if (result == syncer::BaseNode::INIT_FAILED_ENTRY_IS_DEL) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, error_prefix + "deleted entry", type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: deleted entry."; | 
|  | return error; | 
|  | } else { | 
|  | syncer::Cryptographer* crypto = trans.GetCryptographer(); | 
|  | syncer::ModelTypeSet encrypted_types(trans.GetEncryptedTypes()); | 
|  | const sync_pb::EntitySpecifics& specifics = | 
|  | sync_node->GetEntry()->GetSpecifics(); | 
|  | CHECK(specifics.has_encrypted()); | 
|  | const bool can_decrypt = crypto->CanDecrypt(specifics.encrypted()); | 
|  | const bool agreement = encrypted_types.Has(type); | 
|  | if (!agreement && !can_decrypt) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, | 
|  | "Failed to load encrypted entry, missing key and " | 
|  | "nigori mismatch for " + | 
|  | type_str + ".", | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: encr case 1."; | 
|  | return error; | 
|  | } else if (agreement && can_decrypt) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, | 
|  | "Failed to load encrypted entry, we have the key " | 
|  | "and the nigori matches (?!) for " + | 
|  | type_str + ".", | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: encr case 2."; | 
|  | return error; | 
|  | } else if (agreement) { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, | 
|  | "Failed to load encrypted entry, missing key and " | 
|  | "the nigori matches for " + | 
|  | type_str + ".", | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: encr case 3."; | 
|  | return error; | 
|  | } else { | 
|  | syncer::SyncError error; | 
|  | error.Reset(FROM_HERE, | 
|  | "Failed to load encrypted entry, we have the key" | 
|  | "(?!) and nigori mismatch for " + | 
|  | type_str + ".", | 
|  | type); | 
|  | error_handler()->OnSingleDataTypeUnrecoverableError(error); | 
|  | LOG(ERROR) << "Update: encr case 4."; | 
|  | return error; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | sync_node->SetTitle(change.sync_data().GetTitle()); | 
|  | SetNodeSpecifics(sync_data_local.GetSpecifics(), sync_node); | 
|  | SetAttachmentMetadata(sync_data_local.GetAttachmentIds(), sync_node); | 
|  |  | 
|  | // Return any newly added attachments. | 
|  | const syncer::AttachmentList& local_attachments_for_upload = | 
|  | sync_data_local.GetLocalAttachmentsForUpload(); | 
|  | new_attachments->insert(new_attachments->end(), | 
|  | local_attachments_for_upload.begin(), | 
|  | local_attachments_for_upload.end()); | 
|  |  | 
|  | if (merge_result_.get()) { | 
|  | merge_result_->set_num_items_modified(merge_result_->num_items_modified() + | 
|  | 1); | 
|  | } | 
|  | // TODO(sync): Support updating other parts of the sync node (title, | 
|  | // successor, parent, etc.). | 
|  | return syncer::SyncError(); | 
|  | } | 
|  |  | 
|  | bool GenericChangeProcessor::SyncModelHasUserCreatedNodes( | 
|  | syncer::ModelType type, | 
|  | bool* has_nodes) { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | DCHECK(has_nodes); | 
|  | DCHECK_NE(type, syncer::UNSPECIFIED); | 
|  | std::string type_name = syncer::ModelTypeToString(type); | 
|  | std::string err_str = "Server did not create the top-level " + type_name + | 
|  | " node. We might be running against an out-of-date server."; | 
|  | *has_nodes = false; | 
|  | syncer::ReadTransaction trans(FROM_HERE, share_handle()); | 
|  | syncer::ReadNode type_root_node(&trans); | 
|  | if (type_root_node.InitTypeRoot(type) != syncer::BaseNode::INIT_OK) { | 
|  | LOG(ERROR) << err_str; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // The sync model has user created nodes if the type's root node has any | 
|  | // children. | 
|  | *has_nodes = type_root_node.HasChildren(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool GenericChangeProcessor::CryptoReadyIfNecessary(syncer::ModelType type) { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | DCHECK_NE(type, syncer::UNSPECIFIED); | 
|  | // We only access the cryptographer while holding a transaction. | 
|  | syncer::ReadTransaction trans(FROM_HERE, share_handle()); | 
|  | const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes(); | 
|  | return !encrypted_types.Has(type) || | 
|  | trans.GetCryptographer()->is_ready(); | 
|  | } | 
|  |  | 
|  | void GenericChangeProcessor::StartImpl() { | 
|  | } | 
|  |  | 
|  | syncer::UserShare* GenericChangeProcessor::share_handle() const { | 
|  | DCHECK(CalledOnValidThread()); | 
|  | return share_handle_; | 
|  | } | 
|  |  | 
|  | }  // namespace sync_driver |