| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 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 "chrome/browser/sync/engine/process_commit_response_command.h" | |
| 6 | |
| 7 #include <cstddef> | |
| 8 #include <set> | |
| 9 #include <string> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/basictypes.h" | |
| 13 #include "base/location.h" | |
| 14 #include "chrome/browser/sync/engine/syncer_proto_util.h" | |
| 15 #include "chrome/browser/sync/engine/syncer_util.h" | |
| 16 #include "chrome/browser/sync/engine/syncproto.h" | |
| 17 #include "chrome/browser/sync/sessions/sync_session.h" | |
| 18 #include "chrome/browser/sync/syncable/syncable.h" | |
| 19 #include "chrome/browser/sync/util/time.h" | |
| 20 | |
| 21 using syncable::WriteTransaction; | |
| 22 using syncable::MutableEntry; | |
| 23 using syncable::Entry; | |
| 24 | |
| 25 using std::set; | |
| 26 using std::string; | |
| 27 using std::vector; | |
| 28 | |
| 29 using syncable::BASE_VERSION; | |
| 30 using syncable::GET_BY_ID; | |
| 31 using syncable::ID; | |
| 32 using syncable::IS_DEL; | |
| 33 using syncable::IS_DIR; | |
| 34 using syncable::IS_UNAPPLIED_UPDATE; | |
| 35 using syncable::IS_UNSYNCED; | |
| 36 using syncable::PARENT_ID; | |
| 37 using syncable::SERVER_IS_DEL; | |
| 38 using syncable::SERVER_PARENT_ID; | |
| 39 using syncable::SERVER_POSITION_IN_PARENT; | |
| 40 using syncable::SERVER_VERSION; | |
| 41 using syncable::SYNCER; | |
| 42 using syncable::SYNCING; | |
| 43 | |
| 44 namespace browser_sync { | |
| 45 | |
| 46 using sessions::OrderedCommitSet; | |
| 47 using sessions::StatusController; | |
| 48 using sessions::SyncSession; | |
| 49 using sessions::ConflictProgress; | |
| 50 | |
| 51 ProcessCommitResponseCommand::ProcessCommitResponseCommand() {} | |
| 52 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {} | |
| 53 | |
| 54 std::set<ModelSafeGroup> ProcessCommitResponseCommand::GetGroupsToChange( | |
| 55 const sessions::SyncSession& session) const { | |
| 56 std::set<ModelSafeGroup> groups_with_commits; | |
| 57 | |
| 58 syncable::Directory* dir = session.context()->directory(); | |
| 59 syncable::ReadTransaction trans(FROM_HERE, dir); | |
| 60 const StatusController& status = session.status_controller(); | |
| 61 for (size_t i = 0; i < status.commit_ids().size(); ++i) { | |
| 62 groups_with_commits.insert( | |
| 63 GetGroupForModelType(status.GetUnrestrictedCommitModelTypeAt(i), | |
| 64 session.routing_info())); | |
| 65 } | |
| 66 | |
| 67 return groups_with_commits; | |
| 68 } | |
| 69 | |
| 70 SyncerError ProcessCommitResponseCommand::ModelNeutralExecuteImpl( | |
| 71 sessions::SyncSession* session) { | |
| 72 const StatusController& status = session->status_controller(); | |
| 73 const ClientToServerResponse& response(status.commit_response()); | |
| 74 const vector<syncable::Id>& commit_ids(status.commit_ids()); | |
| 75 | |
| 76 if (!response.has_commit()) { | |
| 77 // TODO(sync): What if we didn't try to commit anything? | |
| 78 LOG(WARNING) << "Commit response has no commit body!"; | |
| 79 return SERVER_RESPONSE_VALIDATION_FAILED; | |
| 80 } | |
| 81 | |
| 82 const CommitResponse& cr = response.commit(); | |
| 83 int commit_count = commit_ids.size(); | |
| 84 if (cr.entryresponse_size() != commit_count) { | |
| 85 LOG(ERROR) << "Commit response has wrong number of entries! Expected:" << | |
| 86 commit_count << " Got:" << cr.entryresponse_size(); | |
| 87 for (int i = 0 ; i < cr.entryresponse_size() ; i++) { | |
| 88 LOG(ERROR) << "Response #" << i << " Value: " << | |
| 89 cr.entryresponse(i).response_type(); | |
| 90 if (cr.entryresponse(i).has_error_message()) | |
| 91 LOG(ERROR) << " " << cr.entryresponse(i).error_message(); | |
| 92 } | |
| 93 return SERVER_RESPONSE_VALIDATION_FAILED; | |
| 94 } | |
| 95 return SYNCER_OK; | |
| 96 } | |
| 97 | |
| 98 SyncerError ProcessCommitResponseCommand::ModelChangingExecuteImpl( | |
| 99 SyncSession* session) { | |
| 100 SyncerError result = ProcessCommitResponse(session); | |
| 101 ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor(); | |
| 102 if (session->status_controller().HasBookmarkCommitActivity() && | |
| 103 session->status_controller().syncer_status() | |
| 104 .num_successful_bookmark_commits == 0) { | |
| 105 monitor->PutRecords(session->extensions_activity()); | |
| 106 session->mutable_extensions_activity()->clear(); | |
| 107 } | |
| 108 return result; | |
| 109 } | |
| 110 | |
| 111 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse( | |
| 112 SyncSession* session) { | |
| 113 syncable::Directory* dir = session->context()->directory(); | |
| 114 | |
| 115 StatusController* status = session->mutable_status_controller(); | |
| 116 const ClientToServerResponse& response(status->commit_response()); | |
| 117 const CommitResponse& cr = response.commit(); | |
| 118 const sync_pb::CommitMessage& commit_message = | |
| 119 status->commit_message().commit(); | |
| 120 | |
| 121 // If we try to commit a parent and child together and the parent conflicts | |
| 122 // the child will have a bad parent causing an error. As this is not a | |
| 123 // critical error, we trap it and don't LOG(ERROR). To enable this we keep | |
| 124 // a map of conflicting new folders. | |
| 125 int transient_error_commits = 0; | |
| 126 int conflicting_commits = 0; | |
| 127 int error_commits = 0; | |
| 128 int successes = 0; | |
| 129 set<syncable::Id> conflicting_new_folder_ids; | |
| 130 set<syncable::Id> deleted_folders; | |
| 131 ConflictProgress* conflict_progress = status->mutable_conflict_progress(); | |
| 132 OrderedCommitSet::Projection proj = status->commit_id_projection(); | |
| 133 if (!proj.empty()) { // Scope for WriteTransaction. | |
| 134 WriteTransaction trans(FROM_HERE, SYNCER, dir); | |
| 135 for (size_t i = 0; i < proj.size(); i++) { | |
| 136 CommitResponse::ResponseType response_type = | |
| 137 ProcessSingleCommitResponse(&trans, cr.entryresponse(proj[i]), | |
| 138 commit_message.entries(proj[i]), | |
| 139 status->GetCommitIdAt(proj[i]), | |
| 140 &conflicting_new_folder_ids, | |
| 141 &deleted_folders); | |
| 142 switch (response_type) { | |
| 143 case CommitResponse::INVALID_MESSAGE: | |
| 144 ++error_commits; | |
| 145 break; | |
| 146 case CommitResponse::CONFLICT: | |
| 147 ++conflicting_commits; | |
| 148 // Only server CONFLICT responses will activate conflict resolution. | |
| 149 conflict_progress->AddServerConflictingItemById( | |
| 150 status->GetCommitIdAt(proj[i])); | |
| 151 break; | |
| 152 case CommitResponse::SUCCESS: | |
| 153 // TODO(sync): worry about sync_rate_ rate calc? | |
| 154 ++successes; | |
| 155 if (status->GetCommitModelTypeAt(proj[i]) == syncable::BOOKMARKS) | |
| 156 status->increment_num_successful_bookmark_commits(); | |
| 157 status->increment_num_successful_commits(); | |
| 158 break; | |
| 159 case CommitResponse::OVER_QUOTA: | |
| 160 // We handle over quota like a retry, which is same as transient. | |
| 161 case CommitResponse::RETRY: | |
| 162 case CommitResponse::TRANSIENT_ERROR: | |
| 163 ++transient_error_commits; | |
| 164 break; | |
| 165 default: | |
| 166 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse"; | |
| 167 } | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders); | |
| 172 | |
| 173 int commit_count = static_cast<int>(proj.size()); | |
| 174 if (commit_count == (successes + conflicting_commits)) { | |
| 175 // We consider conflicting commits as a success because things will work out | |
| 176 // on their own when we receive them. Flags will be set so that | |
| 177 // HasMoreToSync() will cause SyncScheduler to enter another sync cycle to | |
| 178 // handle this condition. | |
| 179 return SYNCER_OK; | |
| 180 } else if (error_commits > 0) { | |
| 181 return SERVER_RETURN_UNKNOWN_ERROR; | |
| 182 } else if (transient_error_commits > 0) { | |
| 183 return SERVER_RETURN_TRANSIENT_ERROR; | |
| 184 } else { | |
| 185 LOG(FATAL) << "Inconsistent counts when processing commit response"; | |
| 186 return SYNCER_OK; | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 void LogServerError(const CommitResponse_EntryResponse& res) { | |
| 191 if (res.has_error_message()) | |
| 192 LOG(WARNING) << " " << res.error_message(); | |
| 193 else | |
| 194 LOG(WARNING) << " No detailed error message returned from server"; | |
| 195 } | |
| 196 | |
| 197 CommitResponse::ResponseType | |
| 198 ProcessCommitResponseCommand::ProcessSingleCommitResponse( | |
| 199 syncable::WriteTransaction* trans, | |
| 200 const sync_pb::CommitResponse_EntryResponse& pb_server_entry, | |
| 201 const sync_pb::SyncEntity& commit_request_entry, | |
| 202 const syncable::Id& pre_commit_id, | |
| 203 std::set<syncable::Id>* conflicting_new_folder_ids, | |
| 204 set<syncable::Id>* deleted_folders) { | |
| 205 | |
| 206 const CommitResponse_EntryResponse& server_entry = | |
| 207 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry); | |
| 208 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id); | |
| 209 CHECK(local_entry.good()); | |
| 210 bool syncing_was_set = local_entry.Get(SYNCING); | |
| 211 local_entry.Put(SYNCING, false); | |
| 212 | |
| 213 CommitResponse::ResponseType response = (CommitResponse::ResponseType) | |
| 214 server_entry.response_type(); | |
| 215 if (!CommitResponse::ResponseType_IsValid(response)) { | |
| 216 LOG(ERROR) << "Commit response has unknown response type! Possibly out " | |
| 217 "of date client?"; | |
| 218 return CommitResponse::INVALID_MESSAGE; | |
| 219 } | |
| 220 if (CommitResponse::TRANSIENT_ERROR == response) { | |
| 221 DVLOG(1) << "Transient Error Committing: " << local_entry; | |
| 222 LogServerError(server_entry); | |
| 223 return CommitResponse::TRANSIENT_ERROR; | |
| 224 } | |
| 225 if (CommitResponse::INVALID_MESSAGE == response) { | |
| 226 LOG(ERROR) << "Error Commiting: " << local_entry; | |
| 227 LogServerError(server_entry); | |
| 228 return response; | |
| 229 } | |
| 230 if (CommitResponse::CONFLICT == response) { | |
| 231 DVLOG(1) << "Conflict Committing: " << local_entry; | |
| 232 // TODO(nick): conflicting_new_folder_ids is a purposeless anachronism. | |
| 233 if (!pre_commit_id.ServerKnows() && local_entry.Get(IS_DIR)) { | |
| 234 conflicting_new_folder_ids->insert(pre_commit_id); | |
| 235 } | |
| 236 return response; | |
| 237 } | |
| 238 if (CommitResponse::RETRY == response) { | |
| 239 DVLOG(1) << "Retry Committing: " << local_entry; | |
| 240 return response; | |
| 241 } | |
| 242 if (CommitResponse::OVER_QUOTA == response) { | |
| 243 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; | |
| 244 return response; | |
| 245 } | |
| 246 if (!server_entry.has_id_string()) { | |
| 247 LOG(ERROR) << "Commit response has no id"; | |
| 248 return CommitResponse::INVALID_MESSAGE; | |
| 249 } | |
| 250 | |
| 251 // Implied by the IsValid call above, but here for clarity. | |
| 252 DCHECK_EQ(CommitResponse::SUCCESS, response) << response; | |
| 253 // Check to see if we've been given the ID of an existing entry. If so treat | |
| 254 // it as an error response and retry later. | |
| 255 if (pre_commit_id != server_entry.id()) { | |
| 256 Entry e(trans, GET_BY_ID, server_entry.id()); | |
| 257 if (e.good()) { | |
| 258 LOG(ERROR) << "Got duplicate id when commiting id: " << pre_commit_id << | |
| 259 ". Treating as an error return"; | |
| 260 return CommitResponse::INVALID_MESSAGE; | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 if (server_entry.version() == 0) { | |
| 265 LOG(WARNING) << "Server returned a zero version on a commit response."; | |
| 266 } | |
| 267 | |
| 268 ProcessSuccessfulCommitResponse(commit_request_entry, server_entry, | |
| 269 pre_commit_id, &local_entry, syncing_was_set, deleted_folders); | |
| 270 return response; | |
| 271 } | |
| 272 | |
| 273 const string& ProcessCommitResponseCommand::GetResultingPostCommitName( | |
| 274 const sync_pb::SyncEntity& committed_entry, | |
| 275 const CommitResponse_EntryResponse& entry_response) { | |
| 276 const string& response_name = | |
| 277 SyncerProtoUtil::NameFromCommitEntryResponse(entry_response); | |
| 278 if (!response_name.empty()) | |
| 279 return response_name; | |
| 280 return SyncerProtoUtil::NameFromSyncEntity(committed_entry); | |
| 281 } | |
| 282 | |
| 283 bool ProcessCommitResponseCommand::UpdateVersionAfterCommit( | |
| 284 const sync_pb::SyncEntity& committed_entry, | |
| 285 const CommitResponse_EntryResponse& entry_response, | |
| 286 const syncable::Id& pre_commit_id, | |
| 287 syncable::MutableEntry* local_entry) { | |
| 288 int64 old_version = local_entry->Get(BASE_VERSION); | |
| 289 int64 new_version = entry_response.version(); | |
| 290 bool bad_commit_version = false; | |
| 291 if (committed_entry.deleted() && | |
| 292 !local_entry->Get(syncable::UNIQUE_CLIENT_TAG).empty()) { | |
| 293 // If the item was deleted, and it's undeletable (uses the client tag), | |
| 294 // change the version back to zero. We must set the version to zero so | |
| 295 // that the server knows to re-create the item if it gets committed | |
| 296 // later for undeletion. | |
| 297 new_version = 0; | |
| 298 } else if (!pre_commit_id.ServerKnows()) { | |
| 299 bad_commit_version = 0 == new_version; | |
| 300 } else { | |
| 301 bad_commit_version = old_version > new_version; | |
| 302 } | |
| 303 if (bad_commit_version) { | |
| 304 LOG(ERROR) << "Bad version in commit return for " << *local_entry | |
| 305 << " new_id:" << entry_response.id() << " new_version:" | |
| 306 << entry_response.version(); | |
| 307 return false; | |
| 308 } | |
| 309 | |
| 310 // Update the base version and server version. The base version must change | |
| 311 // here, even if syncing_was_set is false; that's because local changes were | |
| 312 // on top of the successfully committed version. | |
| 313 local_entry->Put(BASE_VERSION, new_version); | |
| 314 DVLOG(1) << "Commit is changing base version of " << local_entry->Get(ID) | |
| 315 << " to: " << new_version; | |
| 316 local_entry->Put(SERVER_VERSION, new_version); | |
| 317 return true; | |
| 318 } | |
| 319 | |
| 320 bool ProcessCommitResponseCommand::ChangeIdAfterCommit( | |
| 321 const CommitResponse_EntryResponse& entry_response, | |
| 322 const syncable::Id& pre_commit_id, | |
| 323 syncable::MutableEntry* local_entry) { | |
| 324 syncable::WriteTransaction* trans = local_entry->write_transaction(); | |
| 325 if (entry_response.id() != pre_commit_id) { | |
| 326 if (pre_commit_id.ServerKnows()) { | |
| 327 // The server can sometimes generate a new ID on commit; for example, | |
| 328 // when committing an undeletion. | |
| 329 DVLOG(1) << " ID changed while committing an old entry. " | |
| 330 << pre_commit_id << " became " << entry_response.id() << "."; | |
| 331 } | |
| 332 MutableEntry same_id(trans, GET_BY_ID, entry_response.id()); | |
| 333 // We should trap this before this function. | |
| 334 if (same_id.good()) { | |
| 335 LOG(ERROR) << "ID clash with id " << entry_response.id() | |
| 336 << " during commit " << same_id; | |
| 337 return false; | |
| 338 } | |
| 339 SyncerUtil::ChangeEntryIDAndUpdateChildren( | |
| 340 trans, local_entry, entry_response.id()); | |
| 341 DVLOG(1) << "Changing ID to " << entry_response.id(); | |
| 342 } | |
| 343 return true; | |
| 344 } | |
| 345 | |
| 346 void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit( | |
| 347 const sync_pb::SyncEntity& committed_entry, | |
| 348 const CommitResponse_EntryResponse& entry_response, | |
| 349 syncable::MutableEntry* local_entry) { | |
| 350 | |
| 351 // We just committed an entry successfully, and now we want to make our view | |
| 352 // of the server state consistent with the server state. We must be careful; | |
| 353 // |entry_response| and |committed_entry| have some identically named | |
| 354 // fields. We only want to consider fields from |committed_entry| when there | |
| 355 // is not an overriding field in the |entry_response|. We do not want to | |
| 356 // update the server data from the local data in the entry -- it's possible | |
| 357 // that the local data changed during the commit, and even if not, the server | |
| 358 // has the last word on the values of several properties. | |
| 359 | |
| 360 local_entry->Put(SERVER_IS_DEL, committed_entry.deleted()); | |
| 361 if (committed_entry.deleted()) { | |
| 362 // Don't clobber any other fields of deleted objects. | |
| 363 return; | |
| 364 } | |
| 365 | |
| 366 local_entry->Put(syncable::SERVER_IS_DIR, | |
| 367 (committed_entry.folder() || | |
| 368 committed_entry.bookmarkdata().bookmark_folder())); | |
| 369 local_entry->Put(syncable::SERVER_SPECIFICS, | |
| 370 committed_entry.specifics()); | |
| 371 local_entry->Put(syncable::SERVER_MTIME, | |
| 372 ProtoTimeToTime(committed_entry.mtime())); | |
| 373 local_entry->Put(syncable::SERVER_CTIME, | |
| 374 ProtoTimeToTime(committed_entry.ctime())); | |
| 375 local_entry->Put(syncable::SERVER_POSITION_IN_PARENT, | |
| 376 entry_response.position_in_parent()); | |
| 377 // TODO(nick): The server doesn't set entry_response.server_parent_id in | |
| 378 // practice; to update SERVER_PARENT_ID appropriately here we'd need to | |
| 379 // get the post-commit ID of the parent indicated by | |
| 380 // committed_entry.parent_id_string(). That should be inferrable from the | |
| 381 // information we have, but it's a bit convoluted to pull it out directly. | |
| 382 // Getting this right is important: SERVER_PARENT_ID gets fed back into | |
| 383 // old_parent_id during the next commit. | |
| 384 local_entry->Put(syncable::SERVER_PARENT_ID, | |
| 385 local_entry->Get(syncable::PARENT_ID)); | |
| 386 local_entry->Put(syncable::SERVER_NON_UNIQUE_NAME, | |
| 387 GetResultingPostCommitName(committed_entry, entry_response)); | |
| 388 | |
| 389 if (local_entry->Get(IS_UNAPPLIED_UPDATE)) { | |
| 390 // This shouldn't happen; an unapplied update shouldn't be committed, and | |
| 391 // if it were, the commit should have failed. But if it does happen: we've | |
| 392 // just overwritten the update info, so clear the flag. | |
| 393 local_entry->Put(IS_UNAPPLIED_UPDATE, false); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 void ProcessCommitResponseCommand::OverrideClientFieldsAfterCommit( | |
| 398 const sync_pb::SyncEntity& committed_entry, | |
| 399 const CommitResponse_EntryResponse& entry_response, | |
| 400 syncable::MutableEntry* local_entry) { | |
| 401 if (committed_entry.deleted()) { | |
| 402 // If an entry's been deleted, nothing else matters. | |
| 403 DCHECK(local_entry->Get(IS_DEL)); | |
| 404 return; | |
| 405 } | |
| 406 | |
| 407 // Update the name. | |
| 408 const string& server_name = | |
| 409 GetResultingPostCommitName(committed_entry, entry_response); | |
| 410 const string& old_name = | |
| 411 local_entry->Get(syncable::NON_UNIQUE_NAME); | |
| 412 | |
| 413 if (!server_name.empty() && old_name != server_name) { | |
| 414 DVLOG(1) << "During commit, server changed name: " << old_name | |
| 415 << " to new name: " << server_name; | |
| 416 local_entry->Put(syncable::NON_UNIQUE_NAME, server_name); | |
| 417 } | |
| 418 | |
| 419 // The server has the final say on positioning, so apply the absolute | |
| 420 // position that it returns. | |
| 421 if (entry_response.has_position_in_parent()) { | |
| 422 // The SERVER_ field should already have been written. | |
| 423 DCHECK_EQ(entry_response.position_in_parent(), | |
| 424 local_entry->Get(SERVER_POSITION_IN_PARENT)); | |
| 425 | |
| 426 // We just committed successfully, so we assume that the position | |
| 427 // value we got applies to the PARENT_ID we submitted. | |
| 428 syncable::Id new_prev = local_entry->ComputePrevIdFromServerPosition( | |
| 429 local_entry->Get(PARENT_ID)); | |
| 430 if (!local_entry->PutPredecessor(new_prev)) { | |
| 431 // TODO(lipalani) : Propagate the error to caller. crbug.com/100444. | |
| 432 NOTREACHED(); | |
| 433 } | |
| 434 } | |
| 435 } | |
| 436 | |
| 437 void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse( | |
| 438 const sync_pb::SyncEntity& committed_entry, | |
| 439 const CommitResponse_EntryResponse& entry_response, | |
| 440 const syncable::Id& pre_commit_id, syncable::MutableEntry* local_entry, | |
| 441 bool syncing_was_set, set<syncable::Id>* deleted_folders) { | |
| 442 DCHECK(local_entry->Get(IS_UNSYNCED)); | |
| 443 | |
| 444 // Update SERVER_VERSION and BASE_VERSION. | |
| 445 if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id, | |
| 446 local_entry)) { | |
| 447 LOG(ERROR) << "Bad version in commit return for " << *local_entry | |
| 448 << " new_id:" << entry_response.id() << " new_version:" | |
| 449 << entry_response.version(); | |
| 450 return; | |
| 451 } | |
| 452 | |
| 453 // If the server gave us a new ID, apply it. | |
| 454 if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) { | |
| 455 return; | |
| 456 } | |
| 457 | |
| 458 // Update our stored copy of the server state. | |
| 459 UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry); | |
| 460 | |
| 461 // If the item doesn't need to be committed again (an item might need to be | |
| 462 // committed again if it changed locally during the commit), we can remove | |
| 463 // it from the unsynced list. Also, we should change the locally- | |
| 464 // visible properties to apply any canonicalizations or fixups | |
| 465 // that the server introduced during the commit. | |
| 466 if (syncing_was_set) { | |
| 467 OverrideClientFieldsAfterCommit(committed_entry, entry_response, | |
| 468 local_entry); | |
| 469 local_entry->Put(IS_UNSYNCED, false); | |
| 470 } | |
| 471 | |
| 472 // Make a note of any deleted folders, whose children would have | |
| 473 // been recursively deleted. | |
| 474 // TODO(nick): Here, commit_message.deleted() would be more correct than | |
| 475 // local_entry->Get(IS_DEL). For example, an item could be renamed, and then | |
| 476 // deleted during the commit of the rename. Unit test & fix. | |
| 477 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) { | |
| 478 deleted_folders->insert(local_entry->Get(ID)); | |
| 479 } | |
| 480 } | |
| 481 | |
| 482 } // namespace browser_sync | |
| OLD | NEW |