1 // Copyright 2013 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/commit_util.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/sync_session.h" 18 #include "sync/syncable/directory.h" 19 #include "sync/syncable/entry.h" 20 #include "sync/syncable/model_neutral_mutable_entry.h" 21 #include "sync/syncable/syncable_base_transaction.h" 22 #include "sync/syncable/syncable_base_write_transaction.h" 23 #include "sync/syncable/syncable_changes_version.h" 24 #include "sync/syncable/syncable_proto_util.h" 25 #include "sync/syncable/syncable_util.h" 26 #include "sync/util/time.h" 27 28 using std::set; 29 using std::string; 30 using std::vector; 31 32 namespace syncer { 33 34 using sessions::SyncSession; 35 using syncable::Entry; 36 using syncable::IS_DEL; 37 using syncable::IS_UNAPPLIED_UPDATE; 38 using syncable::IS_UNSYNCED; 39 using syncable::Id; 40 using syncable::SPECIFICS; 41 using syncable::UNIQUE_POSITION; 42 43 namespace commit_util { 44 45 void AddExtensionsActivityToMessage( 46 ExtensionsActivity* activity, 47 ExtensionsActivity::Records* extensions_activity_buffer, 48 sync_pb::CommitMessage* message) { 49 // This isn't perfect, since the set of extensions activity may not correlate 50 // exactly with the items being committed. That's OK as long as we're looking 51 // for a rough estimate of extensions activity, not an precise mapping of 52 // which commits were triggered by which extension. 53 // 54 // We will push this list of extensions activity back into the 55 // ExtensionsActivityMonitor if this commit fails. That's why we must keep a 56 // copy of these records in the session. 57 activity->GetAndClearRecords(extensions_activity_buffer); 58 59 const ExtensionsActivity::Records& records = *extensions_activity_buffer; 60 for (ExtensionsActivity::Records::const_iterator it = 61 records.begin(); 62 it != records.end(); ++it) { 63 sync_pb::ChromiumExtensionsActivity* activity_message = 64 message->add_extensions_activity(); 65 activity_message->set_extension_id(it->second.extension_id); 66 activity_message->set_bookmark_writes_since_last_commit( 67 it->second.bookmark_write_count); 68 } 69 } 70 71 void AddClientConfigParamsToMessage( 72 ModelTypeSet enabled_types, 73 sync_pb::CommitMessage* message) { 74 sync_pb::ClientConfigParams* config_params = message->mutable_config_params(); 75 for (ModelTypeSet::Iterator it = enabled_types.First(); it.Good(); it.Inc()) { 76 if (ProxyTypes().Has(it.Get())) 77 continue; 78 int field_number = GetSpecificsFieldNumberFromModelType(it.Get()); 79 config_params->mutable_enabled_type_ids()->Add(field_number); 80 } 81 config_params->set_tabs_datatype_enabled( 82 enabled_types.Has(syncer::PROXY_TABS)); 83 } 84 85 namespace { 86 void SetEntrySpecifics(const Entry& meta_entry, 87 sync_pb::SyncEntity* sync_entry) { 88 // Add the new style extension and the folder bit. 89 sync_entry->mutable_specifics()->CopyFrom(meta_entry.GetSpecifics()); 90 sync_entry->set_folder(meta_entry.GetIsDir()); 91 92 CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data()); 93 DCHECK_EQ(meta_entry.GetModelType(), GetModelType(*sync_entry)); 94 } 95 } // namespace 96 97 void BuildCommitItem( 98 const syncable::Entry& meta_entry, 99 sync_pb::SyncEntity* sync_entry) { 100 syncable::Id id = meta_entry.GetId(); 101 sync_entry->set_id_string(SyncableIdToProto(id)); 102 103 string name = meta_entry.GetNonUniqueName(); 104 CHECK(!name.empty()); // Make sure this isn't an update. 105 // Note: Truncation is also performed in WriteNode::SetTitle(..). But this 106 // call is still necessary to handle any title changes that might originate 107 // elsewhere, or already be persisted in the directory. 108 base::TruncateUTF8ToByteSize(name, 255, &name); 109 sync_entry->set_name(name); 110 111 // Set the non_unique_name. If we do, the server ignores 112 // the |name| value (using |non_unique_name| instead), and will return 113 // in the CommitResponse a unique name if one is generated. 114 // We send both because it may aid in logging. 115 sync_entry->set_non_unique_name(name); 116 117 if (!meta_entry.GetUniqueClientTag().empty()) { 118 sync_entry->set_client_defined_unique_tag( 119 meta_entry.GetUniqueClientTag()); 120 } 121 122 // Deleted items with server-unknown parent ids can be a problem so we set 123 // the parent to 0. (TODO(sync): Still true in protocol?). 124 Id new_parent_id; 125 if (meta_entry.GetIsDel() && 126 !meta_entry.GetParentId().ServerKnows()) { 127 new_parent_id = syncable::BaseTransaction::root_id(); 128 } else { 129 new_parent_id = meta_entry.GetParentId(); 130 } 131 sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id)); 132 133 // If our parent has changed, send up the old one so the server 134 // can correctly deal with multiple parents. 135 // TODO(nick): With the server keeping track of the primary sync parent, 136 // it should not be necessary to provide the old_parent_id: the version 137 // number should suffice. 138 if (new_parent_id != meta_entry.GetServerParentId() && 139 0 != meta_entry.GetBaseVersion() && 140 syncable::CHANGES_VERSION != meta_entry.GetBaseVersion()) { 141 sync_entry->set_old_parent_id( 142 SyncableIdToProto(meta_entry.GetServerParentId())); 143 } 144 145 int64 version = meta_entry.GetBaseVersion(); 146 if (syncable::CHANGES_VERSION == version || 0 == version) { 147 // Undeletions are only supported for items that have a client tag. 148 DCHECK(!id.ServerKnows() || 149 !meta_entry.GetUniqueClientTag().empty()) 150 << meta_entry; 151 152 // Version 0 means to create or undelete an object. 153 sync_entry->set_version(0); 154 } else { 155 DCHECK(id.ServerKnows()) << meta_entry; 156 sync_entry->set_version(meta_entry.GetBaseVersion()); 157 } 158 sync_entry->set_ctime(TimeToProtoTime(meta_entry.GetCtime())); 159 sync_entry->set_mtime(TimeToProtoTime(meta_entry.GetMtime())); 160 161 // Deletion is final on the server, let's move things and then delete them. 162 if (meta_entry.GetIsDel()) { 163 sync_entry->set_deleted(true); 164 } else { 165 if (meta_entry.GetSpecifics().has_bookmark()) { 166 // Both insert_after_item_id and position_in_parent fields are set only 167 // for legacy reasons. See comments in sync.proto for more information. 168 const Id& prev_id = meta_entry.GetPredecessorId(); 169 string prev_id_string = 170 prev_id.IsRoot() ? string() : prev_id.GetServerId(); 171 sync_entry->set_insert_after_item_id(prev_id_string); 172 sync_entry->set_position_in_parent( 173 meta_entry.GetUniquePosition().ToInt64()); 174 meta_entry.GetUniquePosition().ToProto( 175 sync_entry->mutable_unique_position()); 176 } 177 SetEntrySpecifics(meta_entry, sync_entry); 178 } 179 } 180 181 182 // Helpers for ProcessSingleCommitResponse. 183 namespace { 184 185 void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) { 186 if (res.has_error_message()) 187 LOG(WARNING) << " " << res.error_message(); 188 else 189 LOG(WARNING) << " No detailed error message returned from server"; 190 } 191 192 const string& GetResultingPostCommitName( 193 const sync_pb::SyncEntity& committed_entry, 194 const sync_pb::CommitResponse_EntryResponse& entry_response) { 195 const string& response_name = 196 SyncerProtoUtil::NameFromCommitEntryResponse(entry_response); 197 if (!response_name.empty()) 198 return response_name; 199 return SyncerProtoUtil::NameFromSyncEntity(committed_entry); 200 } 201 202 bool UpdateVersionAfterCommit( 203 const sync_pb::SyncEntity& committed_entry, 204 const sync_pb::CommitResponse_EntryResponse& entry_response, 205 const syncable::Id& pre_commit_id, 206 syncable::ModelNeutralMutableEntry* local_entry) { 207 int64 old_version = local_entry->GetBaseVersion(); 208 int64 new_version = entry_response.version(); 209 bool bad_commit_version = false; 210 if (committed_entry.deleted() && 211 !local_entry->GetUniqueClientTag().empty()) { 212 // If the item was deleted, and it's undeletable (uses the client tag), 213 // change the version back to zero. We must set the version to zero so 214 // that the server knows to re-create the item if it gets committed 215 // later for undeletion. 216 new_version = 0; 217 } else if (!pre_commit_id.ServerKnows()) { 218 bad_commit_version = 0 == new_version; 219 } else { 220 bad_commit_version = old_version > new_version; 221 } 222 if (bad_commit_version) { 223 LOG(ERROR) << "Bad version in commit return for " << *local_entry 224 << " new_id:" << SyncableIdFromProto(entry_response.id_string()) 225 << " new_version:" << entry_response.version(); 226 return false; 227 } 228 229 // Update the base version and server version. The base version must change 230 // here, even if syncing_was_set is false; that's because local changes were 231 // on top of the successfully committed version. 232 local_entry->PutBaseVersion(new_version); 233 DVLOG(1) << "Commit is changing base version of " << local_entry->GetId() 234 << " to: " << new_version; 235 local_entry->PutServerVersion(new_version); 236 return true; 237 } 238 239 bool ChangeIdAfterCommit( 240 const sync_pb::CommitResponse_EntryResponse& entry_response, 241 const syncable::Id& pre_commit_id, 242 syncable::ModelNeutralMutableEntry* local_entry) { 243 syncable::BaseWriteTransaction* trans = local_entry->base_write_transaction(); 244 const syncable::Id& entry_response_id = 245 SyncableIdFromProto(entry_response.id_string()); 246 if (entry_response_id != pre_commit_id) { 247 if (pre_commit_id.ServerKnows()) { 248 // The server can sometimes generate a new ID on commit; for example, 249 // when committing an undeletion. 250 DVLOG(1) << " ID changed while committing an old entry. " 251 << pre_commit_id << " became " << entry_response_id << "."; 252 } 253 syncable::ModelNeutralMutableEntry same_id( 254 trans, 255 syncable::GET_BY_ID, 256 entry_response_id); 257 // We should trap this before this function. 258 if (same_id.good()) { 259 LOG(ERROR) << "ID clash with id " << entry_response_id 260 << " during commit " << same_id; 261 return false; 262 } 263 ChangeEntryIDAndUpdateChildren(trans, local_entry, entry_response_id); 264 DVLOG(1) << "Changing ID to " << entry_response_id; 265 } 266 return true; 267 } 268 269 void UpdateServerFieldsAfterCommit( 270 const sync_pb::SyncEntity& committed_entry, 271 const sync_pb::CommitResponse_EntryResponse& entry_response, 272 syncable::ModelNeutralMutableEntry* local_entry) { 273 274 // We just committed an entry successfully, and now we want to make our view 275 // of the server state consistent with the server state. We must be careful; 276 // |entry_response| and |committed_entry| have some identically named 277 // fields. We only want to consider fields from |committed_entry| when there 278 // is not an overriding field in the |entry_response|. We do not want to 279 // update the server data from the local data in the entry -- it's possible 280 // that the local data changed during the commit, and even if not, the server 281 // has the last word on the values of several properties. 282 283 local_entry->PutServerIsDel(committed_entry.deleted()); 284 if (committed_entry.deleted()) { 285 // Don't clobber any other fields of deleted objects. 286 return; 287 } 288 289 local_entry->PutServerIsDir( 290 (committed_entry.folder() || 291 committed_entry.bookmarkdata().bookmark_folder())); 292 local_entry->PutServerSpecifics(committed_entry.specifics()); 293 local_entry->PutServerMtime(ProtoTimeToTime(committed_entry.mtime())); 294 local_entry->PutServerCtime(ProtoTimeToTime(committed_entry.ctime())); 295 if (committed_entry.has_unique_position()) { 296 local_entry->PutServerUniquePosition( 297 UniquePosition::FromProto( 298 committed_entry.unique_position())); 299 } 300 301 // TODO(nick): The server doesn't set entry_response.server_parent_id in 302 // practice; to update SERVER_PARENT_ID appropriately here we'd need to 303 // get the post-commit ID of the parent indicated by 304 // committed_entry.parent_id_string(). That should be inferrable from the 305 // information we have, but it's a bit convoluted to pull it out directly. 306 // Getting this right is important: SERVER_PARENT_ID gets fed back into 307 // old_parent_id during the next commit. 308 local_entry->PutServerParentId(local_entry->GetParentId()); 309 local_entry->PutServerNonUniqueName( 310 GetResultingPostCommitName(committed_entry, entry_response)); 311 312 if (local_entry->GetIsUnappliedUpdate()) { 313 // This shouldn't happen; an unapplied update shouldn't be committed, and 314 // if it were, the commit should have failed. But if it does happen: we've 315 // just overwritten the update info, so clear the flag. 316 local_entry->PutIsUnappliedUpdate(false); 317 } 318 } 319 320 void ProcessSuccessfulCommitResponse( 321 const sync_pb::SyncEntity& committed_entry, 322 const sync_pb::CommitResponse_EntryResponse& entry_response, 323 const syncable::Id& pre_commit_id, 324 syncable::ModelNeutralMutableEntry* local_entry, 325 bool syncing_was_set, set<syncable::Id>* deleted_folders) { 326 DCHECK(local_entry->GetIsUnsynced()); 327 328 // Update SERVER_VERSION and BASE_VERSION. 329 if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id, 330 local_entry)) { 331 LOG(ERROR) << "Bad version in commit return for " << *local_entry 332 << " new_id:" << SyncableIdFromProto(entry_response.id_string()) 333 << " new_version:" << entry_response.version(); 334 return; 335 } 336 337 // If the server gave us a new ID, apply it. 338 if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) { 339 return; 340 } 341 342 // Update our stored copy of the server state. 343 UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry); 344 345 // If the item doesn't need to be committed again (an item might need to be 346 // committed again if it changed locally during the commit), we can remove 347 // it from the unsynced list. 348 if (syncing_was_set) { 349 local_entry->PutIsUnsynced(false); 350 } 351 352 // Make a note of any deleted folders, whose children would have 353 // been recursively deleted. 354 // TODO(nick): Here, commit_message.deleted() would be more correct than 355 // local_entry->GetIsDel(). For example, an item could be renamed, and then 356 // deleted during the commit of the rename. Unit test & fix. 357 if (local_entry->GetIsDir() && local_entry->GetIsDel()) { 358 deleted_folders->insert(local_entry->GetId()); 359 } 360 } 361 362 } // namespace 363 364 sync_pb::CommitResponse::ResponseType 365 ProcessSingleCommitResponse( 366 syncable::BaseWriteTransaction* trans, 367 const sync_pb::CommitResponse_EntryResponse& server_entry, 368 const sync_pb::SyncEntity& commit_request_entry, 369 int64 metahandle, 370 set<syncable::Id>* deleted_folders) { 371 syncable::ModelNeutralMutableEntry local_entry( 372 trans, 373 syncable::GET_BY_HANDLE, 374 metahandle); 375 CHECK(local_entry.good()); 376 bool syncing_was_set = local_entry.GetSyncing(); 377 local_entry.PutSyncing(false); 378 379 sync_pb::CommitResponse::ResponseType response = server_entry.response_type(); 380 if (!sync_pb::CommitResponse::ResponseType_IsValid(response)) { 381 LOG(ERROR) << "Commit response has unknown response type! Possibly out " 382 "of date client?"; 383 return sync_pb::CommitResponse::INVALID_MESSAGE; 384 } 385 if (sync_pb::CommitResponse::TRANSIENT_ERROR == response) { 386 DVLOG(1) << "Transient Error Committing: " << local_entry; 387 LogServerError(server_entry); 388 return sync_pb::CommitResponse::TRANSIENT_ERROR; 389 } 390 if (sync_pb::CommitResponse::INVALID_MESSAGE == response) { 391 LOG(ERROR) << "Error Commiting: " << local_entry; 392 LogServerError(server_entry); 393 return response; 394 } 395 if (sync_pb::CommitResponse::CONFLICT == response) { 396 DVLOG(1) << "Conflict Committing: " << local_entry; 397 return response; 398 } 399 if (sync_pb::CommitResponse::RETRY == response) { 400 DVLOG(1) << "Retry Committing: " << local_entry; 401 return response; 402 } 403 if (sync_pb::CommitResponse::OVER_QUOTA == response) { 404 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; 405 return response; 406 } 407 if (!server_entry.has_id_string()) { 408 LOG(ERROR) << "Commit response has no id"; 409 return sync_pb::CommitResponse::INVALID_MESSAGE; 410 } 411 412 // Implied by the IsValid call above, but here for clarity. 413 DCHECK_EQ(sync_pb::CommitResponse::SUCCESS, response) << response; 414 // Check to see if we've been given the ID of an existing entry. If so treat 415 // it as an error response and retry later. 416 const syncable::Id& server_entry_id = 417 SyncableIdFromProto(server_entry.id_string()); 418 if (local_entry.GetId() != server_entry_id) { 419 Entry e(trans, syncable::GET_BY_ID, server_entry_id); 420 if (e.good()) { 421 LOG(ERROR) 422 << "Got duplicate id when commiting id: " 423 << local_entry.GetId() 424 << ". Treating as an error return"; 425 return sync_pb::CommitResponse::INVALID_MESSAGE; 426 } 427 } 428 429 if (server_entry.version() == 0) { 430 LOG(WARNING) << "Server returned a zero version on a commit response."; 431 } 432 433 ProcessSuccessfulCommitResponse(commit_request_entry, server_entry, 434 local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders); 435 return response; 436 } 437 438 } // namespace commit_util 439 440 } // namespace syncer 441