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