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