1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sync/engine/build_commit_command.h" 6 7 #include <limits> 8 #include <set> 9 #include <string> 10 #include <vector> 11 12 #include "base/strings/string_util.h" 13 #include "sync/engine/syncer_proto_util.h" 14 #include "sync/internal_api/public/base/unique_position.h" 15 #include "sync/protocol/bookmark_specifics.pb.h" 16 #include "sync/protocol/sync.pb.h" 17 #include "sync/sessions/ordered_commit_set.h" 18 #include "sync/sessions/sync_session.h" 19 #include "sync/syncable/directory.h" 20 #include "sync/syncable/entry.h" 21 #include "sync/syncable/syncable_base_transaction.h" 22 #include "sync/syncable/syncable_changes_version.h" 23 #include "sync/syncable/syncable_proto_util.h" 24 #include "sync/util/time.h" 25 26 using std::set; 27 using std::string; 28 using std::vector; 29 30 namespace syncer { 31 32 using sessions::SyncSession; 33 using syncable::Entry; 34 using syncable::IS_DEL; 35 using syncable::IS_UNAPPLIED_UPDATE; 36 using syncable::IS_UNSYNCED; 37 using syncable::Id; 38 using syncable::SPECIFICS; 39 using syncable::UNIQUE_POSITION; 40 41 BuildCommitCommand::BuildCommitCommand( 42 syncable::BaseTransaction* trans, 43 const sessions::OrderedCommitSet& batch_commit_set, 44 sync_pb::ClientToServerMessage* commit_message, 45 ExtensionsActivity::Records* extensions_activity_buffer) 46 : trans_(trans), 47 batch_commit_set_(batch_commit_set), 48 commit_message_(commit_message), 49 extensions_activity_buffer_(extensions_activity_buffer) { 50 } 51 52 BuildCommitCommand::~BuildCommitCommand() {} 53 54 void BuildCommitCommand::AddExtensionsActivityToMessage( 55 SyncSession* session, sync_pb::CommitMessage* message) { 56 // We only send ExtensionsActivity to the server if bookmarks are being 57 // committed. 58 ExtensionsActivity* activity = session->context()->extensions_activity(); 59 if (batch_commit_set_.HasBookmarkCommitId()) { 60 // This isn't perfect, since the set of extensions activity may not 61 // correlate exactly with the items being committed. That's OK as 62 // long as we're looking for a rough estimate of extensions activity, 63 // not an precise mapping of which commits were triggered by which 64 // extension. 65 // 66 // We will push this list of extensions activity back into the 67 // ExtensionsActivityMonitor if this commit fails. That's why we must keep 68 // a copy of these records in the session. 69 activity->GetAndClearRecords(extensions_activity_buffer_); 70 71 const ExtensionsActivity::Records& records = 72 *extensions_activity_buffer_; 73 for (ExtensionsActivity::Records::const_iterator it = 74 records.begin(); 75 it != records.end(); ++it) { 76 sync_pb::ChromiumExtensionsActivity* activity_message = 77 message->add_extensions_activity(); 78 activity_message->set_extension_id(it->second.extension_id); 79 activity_message->set_bookmark_writes_since_last_commit( 80 it->second.bookmark_write_count); 81 } 82 } 83 } 84 85 void BuildCommitCommand::AddClientConfigParamsToMessage( 86 SyncSession* session, sync_pb::CommitMessage* message) { 87 const ModelSafeRoutingInfo& routing_info = session->context()->routing_info(); 88 sync_pb::ClientConfigParams* config_params = message->mutable_config_params(); 89 for (std::map<ModelType, ModelSafeGroup>::const_iterator iter = 90 routing_info.begin(); iter != routing_info.end(); ++iter) { 91 if (ProxyTypes().Has(iter->first)) 92 continue; 93 int field_number = GetSpecificsFieldNumberFromModelType(iter->first); 94 config_params->mutable_enabled_type_ids()->Add(field_number); 95 } 96 config_params->set_tabs_datatype_enabled( 97 routing_info.count(syncer::PROXY_TABS) > 0); 98 } 99 100 namespace { 101 void SetEntrySpecifics(Entry* meta_entry, 102 sync_pb::SyncEntity* sync_entry) { 103 // Add the new style extension and the folder bit. 104 sync_entry->mutable_specifics()->CopyFrom(meta_entry->Get(SPECIFICS)); 105 sync_entry->set_folder(meta_entry->Get(syncable::IS_DIR)); 106 107 CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data()); 108 DCHECK_EQ(meta_entry->GetModelType(), GetModelType(*sync_entry)); 109 } 110 } // namespace 111 112 SyncerError BuildCommitCommand::ExecuteImpl(SyncSession* session) { 113 commit_message_->set_share(session->context()->account_name()); 114 commit_message_->set_message_contents(sync_pb::ClientToServerMessage::COMMIT); 115 116 sync_pb::CommitMessage* commit_message = commit_message_->mutable_commit(); 117 commit_message->set_cache_guid(trans_->directory()->cache_guid()); 118 AddExtensionsActivityToMessage(session, commit_message); 119 AddClientConfigParamsToMessage(session, commit_message); 120 121 // Cache previously computed position values. Because |commit_ids| 122 // is already in sibling order, we should always hit this map after 123 // the first sibling in a consecutive run of commit items. The 124 // entries in this map are (low, high) values describing the 125 // space of positions that are immediate successors of the item 126 // whose ID is the map's key. 127 std::map<Id, std::pair<int64, int64> > position_map; 128 129 for (size_t i = 0; i < batch_commit_set_.Size(); i++) { 130 Id id = batch_commit_set_.GetCommitIdAt(i); 131 sync_pb::SyncEntity* sync_entry = commit_message->add_entries(); 132 sync_entry->set_id_string(SyncableIdToProto(id)); 133 Entry meta_entry(trans_, syncable::GET_BY_ID, id); 134 CHECK(meta_entry.good()); 135 136 DCHECK_NE(0UL, 137 session->context()->routing_info().count( 138 meta_entry.GetModelType())) 139 << "Committing change to datatype that's not actively enabled."; 140 141 string name = meta_entry.Get(syncable::NON_UNIQUE_NAME); 142 CHECK(!name.empty()); // Make sure this isn't an update. 143 // Note: Truncation is also performed in WriteNode::SetTitle(..). But this 144 // call is still necessary to handle any title changes that might originate 145 // elsewhere, or already be persisted in the directory. 146 TruncateUTF8ToByteSize(name, 255, &name); 147 sync_entry->set_name(name); 148 149 // Set the non_unique_name. If we do, the server ignores 150 // the |name| value (using |non_unique_name| instead), and will return 151 // in the CommitResponse a unique name if one is generated. 152 // We send both because it may aid in logging. 153 sync_entry->set_non_unique_name(name); 154 155 if (!meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { 156 sync_entry->set_client_defined_unique_tag( 157 meta_entry.Get(syncable::UNIQUE_CLIENT_TAG)); 158 } 159 160 // Deleted items with server-unknown parent ids can be a problem so we set 161 // the parent to 0. (TODO(sync): Still true in protocol?). 162 Id new_parent_id; 163 if (meta_entry.Get(syncable::IS_DEL) && 164 !meta_entry.Get(syncable::PARENT_ID).ServerKnows()) { 165 new_parent_id = trans_->root_id(); 166 } else { 167 new_parent_id = meta_entry.Get(syncable::PARENT_ID); 168 } 169 sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id)); 170 171 // If our parent has changed, send up the old one so the server 172 // can correctly deal with multiple parents. 173 // TODO(nick): With the server keeping track of the primary sync parent, 174 // it should not be necessary to provide the old_parent_id: the version 175 // number should suffice. 176 if (new_parent_id != meta_entry.Get(syncable::SERVER_PARENT_ID) && 177 0 != meta_entry.Get(syncable::BASE_VERSION) && 178 syncable::CHANGES_VERSION != meta_entry.Get(syncable::BASE_VERSION)) { 179 sync_entry->set_old_parent_id( 180 SyncableIdToProto(meta_entry.Get(syncable::SERVER_PARENT_ID))); 181 } 182 183 int64 version = meta_entry.Get(syncable::BASE_VERSION); 184 if (syncable::CHANGES_VERSION == version || 0 == version) { 185 // Undeletions are only supported for items that have a client tag. 186 DCHECK(!id.ServerKnows() || 187 !meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) 188 << meta_entry; 189 190 // Version 0 means to create or undelete an object. 191 sync_entry->set_version(0); 192 } else { 193 DCHECK(id.ServerKnows()) << meta_entry; 194 sync_entry->set_version(meta_entry.Get(syncable::BASE_VERSION)); 195 } 196 sync_entry->set_ctime(TimeToProtoTime(meta_entry.Get(syncable::CTIME))); 197 sync_entry->set_mtime(TimeToProtoTime(meta_entry.Get(syncable::MTIME))); 198 199 // Deletion is final on the server, let's move things and then delete them. 200 if (meta_entry.Get(IS_DEL)) { 201 sync_entry->set_deleted(true); 202 } else { 203 if (meta_entry.Get(SPECIFICS).has_bookmark()) { 204 // Both insert_after_item_id and position_in_parent fields are set only 205 // for legacy reasons. See comments in sync.proto for more information. 206 const Id& prev_id = meta_entry.GetPredecessorId(); 207 string prev_id_string = 208 prev_id.IsRoot() ? string() : prev_id.GetServerId(); 209 sync_entry->set_insert_after_item_id(prev_id_string); 210 sync_entry->set_position_in_parent( 211 meta_entry.Get(UNIQUE_POSITION).ToInt64()); 212 meta_entry.Get(UNIQUE_POSITION).ToProto( 213 sync_entry->mutable_unique_position()); 214 } 215 SetEntrySpecifics(&meta_entry, sync_entry); 216 } 217 } 218 219 return SYNCER_OK; 220 } 221 222 } // namespace syncer 223