Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / sync / engine / commit_util.cc
blob45bc2bbf1e219f71fb5ea4b4ce5f07486fed9357
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"
7 #include <limits>
8 #include <set>
9 #include <string>
10 #include <vector>
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"
28 using std::set;
29 using std::string;
30 using std::vector;
32 namespace syncer {
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;
43 namespace commit_util {
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.
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);
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);
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);
81 config_params->set_tabs_datatype_enabled(
82 enabled_types.Has(syncer::PROXY_TABS));
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());
92 CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data());
93 DCHECK_EQ(meta_entry.GetModelType(), GetModelType(*sync_entry));
95 } // namespace
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));
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);
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);
117 if (!meta_entry.GetUniqueClientTag().empty()) {
118 sync_entry->set_client_defined_unique_tag(
119 meta_entry.GetUniqueClientTag());
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();
131 sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id));
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()));
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;
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());
158 sync_entry->set_ctime(TimeToProtoTime(meta_entry.GetCtime()));
159 sync_entry->set_mtime(TimeToProtoTime(meta_entry.GetMtime()));
161 // Handle bookmarks separately.
162 if (meta_entry.GetSpecifics().has_bookmark()) {
163 if (meta_entry.GetIsDel()) {
164 sync_entry->set_deleted(true);
165 } else {
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());
177 // Always send specifics for bookmarks.
178 SetEntrySpecifics(meta_entry, sync_entry);
179 return;
182 // Deletion is final on the server, let's move things and then delete them.
183 if (meta_entry.GetIsDel()) {
184 sync_entry->set_deleted(true);
185 } else {
186 SetEntrySpecifics(meta_entry, sync_entry);
190 // Helpers for ProcessSingleCommitResponse.
191 namespace {
193 void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
194 if (res.has_error_message())
195 LOG(WARNING) << " " << res.error_message();
196 else
197 LOG(WARNING) << " No detailed error message returned from server";
200 const string& GetResultingPostCommitName(
201 const sync_pb::SyncEntity& committed_entry,
202 const sync_pb::CommitResponse_EntryResponse& entry_response) {
203 const string& response_name =
204 SyncerProtoUtil::NameFromCommitEntryResponse(entry_response);
205 if (!response_name.empty())
206 return response_name;
207 return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
210 bool UpdateVersionAfterCommit(
211 const sync_pb::SyncEntity& committed_entry,
212 const sync_pb::CommitResponse_EntryResponse& entry_response,
213 const syncable::Id& pre_commit_id,
214 syncable::ModelNeutralMutableEntry* local_entry) {
215 int64 old_version = local_entry->GetBaseVersion();
216 int64 new_version = entry_response.version();
217 bool bad_commit_version = false;
218 if (committed_entry.deleted() &&
219 !local_entry->GetUniqueClientTag().empty()) {
220 // If the item was deleted, and it's undeletable (uses the client tag),
221 // change the version back to zero. We must set the version to zero so
222 // that the server knows to re-create the item if it gets committed
223 // later for undeletion.
224 new_version = 0;
225 } else if (!pre_commit_id.ServerKnows()) {
226 bad_commit_version = 0 == new_version;
227 } else {
228 bad_commit_version = old_version > new_version;
230 if (bad_commit_version) {
231 LOG(ERROR) << "Bad version in commit return for " << *local_entry
232 << " new_id:" << SyncableIdFromProto(entry_response.id_string())
233 << " new_version:" << entry_response.version();
234 return false;
237 // Update the base version and server version. The base version must change
238 // here, even if syncing_was_set is false; that's because local changes were
239 // on top of the successfully committed version.
240 local_entry->PutBaseVersion(new_version);
241 DVLOG(1) << "Commit is changing base version of " << local_entry->GetId()
242 << " to: " << new_version;
243 local_entry->PutServerVersion(new_version);
244 return true;
247 bool ChangeIdAfterCommit(
248 const sync_pb::CommitResponse_EntryResponse& entry_response,
249 const syncable::Id& pre_commit_id,
250 syncable::ModelNeutralMutableEntry* local_entry) {
251 syncable::BaseWriteTransaction* trans = local_entry->base_write_transaction();
252 const syncable::Id& entry_response_id =
253 SyncableIdFromProto(entry_response.id_string());
254 if (entry_response_id != pre_commit_id) {
255 if (pre_commit_id.ServerKnows()) {
256 // The server can sometimes generate a new ID on commit; for example,
257 // when committing an undeletion.
258 DVLOG(1) << " ID changed while committing an old entry. "
259 << pre_commit_id << " became " << entry_response_id << ".";
261 syncable::ModelNeutralMutableEntry same_id(
262 trans,
263 syncable::GET_BY_ID,
264 entry_response_id);
265 // We should trap this before this function.
266 if (same_id.good()) {
267 LOG(ERROR) << "ID clash with id " << entry_response_id
268 << " during commit " << same_id;
269 return false;
271 ChangeEntryIDAndUpdateChildren(trans, local_entry, entry_response_id);
272 DVLOG(1) << "Changing ID to " << entry_response_id;
274 return true;
277 void UpdateServerFieldsAfterCommit(
278 const sync_pb::SyncEntity& committed_entry,
279 const sync_pb::CommitResponse_EntryResponse& entry_response,
280 syncable::ModelNeutralMutableEntry* local_entry) {
282 // We just committed an entry successfully, and now we want to make our view
283 // of the server state consistent with the server state. We must be careful;
284 // |entry_response| and |committed_entry| have some identically named
285 // fields. We only want to consider fields from |committed_entry| when there
286 // is not an overriding field in the |entry_response|. We do not want to
287 // update the server data from the local data in the entry -- it's possible
288 // that the local data changed during the commit, and even if not, the server
289 // has the last word on the values of several properties.
291 local_entry->PutServerIsDel(committed_entry.deleted());
292 if (committed_entry.deleted()) {
293 // Don't clobber any other fields of deleted objects.
294 return;
297 local_entry->PutServerIsDir(
298 (committed_entry.folder() ||
299 committed_entry.bookmarkdata().bookmark_folder()));
300 local_entry->PutServerSpecifics(committed_entry.specifics());
301 local_entry->PutServerMtime(ProtoTimeToTime(committed_entry.mtime()));
302 local_entry->PutServerCtime(ProtoTimeToTime(committed_entry.ctime()));
303 if (committed_entry.has_unique_position()) {
304 local_entry->PutServerUniquePosition(
305 UniquePosition::FromProto(
306 committed_entry.unique_position()));
309 // TODO(nick): The server doesn't set entry_response.server_parent_id in
310 // practice; to update SERVER_PARENT_ID appropriately here we'd need to
311 // get the post-commit ID of the parent indicated by
312 // committed_entry.parent_id_string(). That should be inferrable from the
313 // information we have, but it's a bit convoluted to pull it out directly.
314 // Getting this right is important: SERVER_PARENT_ID gets fed back into
315 // old_parent_id during the next commit.
316 local_entry->PutServerParentId(local_entry->GetParentId());
317 local_entry->PutServerNonUniqueName(
318 GetResultingPostCommitName(committed_entry, entry_response));
320 if (local_entry->GetIsUnappliedUpdate()) {
321 // This shouldn't happen; an unapplied update shouldn't be committed, and
322 // if it were, the commit should have failed. But if it does happen: we've
323 // just overwritten the update info, so clear the flag.
324 local_entry->PutIsUnappliedUpdate(false);
328 void ProcessSuccessfulCommitResponse(
329 const sync_pb::SyncEntity& committed_entry,
330 const sync_pb::CommitResponse_EntryResponse& entry_response,
331 const syncable::Id& pre_commit_id,
332 syncable::ModelNeutralMutableEntry* local_entry,
333 bool syncing_was_set, set<syncable::Id>* deleted_folders) {
334 DCHECK(local_entry->GetIsUnsynced());
336 // Update SERVER_VERSION and BASE_VERSION.
337 if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id,
338 local_entry)) {
339 LOG(ERROR) << "Bad version in commit return for " << *local_entry
340 << " new_id:" << SyncableIdFromProto(entry_response.id_string())
341 << " new_version:" << entry_response.version();
342 return;
345 // If the server gave us a new ID, apply it.
346 if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) {
347 return;
350 // Update our stored copy of the server state.
351 UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry);
353 // If the item doesn't need to be committed again (an item might need to be
354 // committed again if it changed locally during the commit), we can remove
355 // it from the unsynced list.
356 if (syncing_was_set) {
357 local_entry->PutIsUnsynced(false);
360 // Make a note of any deleted folders, whose children would have
361 // been recursively deleted.
362 // TODO(nick): Here, commit_message.deleted() would be more correct than
363 // local_entry->GetIsDel(). For example, an item could be renamed, and then
364 // deleted during the commit of the rename. Unit test & fix.
365 if (local_entry->GetIsDir() && local_entry->GetIsDel()) {
366 deleted_folders->insert(local_entry->GetId());
370 } // namespace
372 sync_pb::CommitResponse::ResponseType
373 ProcessSingleCommitResponse(
374 syncable::BaseWriteTransaction* trans,
375 const sync_pb::CommitResponse_EntryResponse& server_entry,
376 const sync_pb::SyncEntity& commit_request_entry,
377 int64 metahandle,
378 set<syncable::Id>* deleted_folders) {
379 syncable::ModelNeutralMutableEntry local_entry(
380 trans,
381 syncable::GET_BY_HANDLE,
382 metahandle);
383 CHECK(local_entry.good());
384 bool syncing_was_set = local_entry.GetSyncing();
385 local_entry.PutSyncing(false);
387 sync_pb::CommitResponse::ResponseType response = server_entry.response_type();
388 if (!sync_pb::CommitResponse::ResponseType_IsValid(response)) {
389 LOG(ERROR) << "Commit response has unknown response type! Possibly out "
390 "of date client?";
391 return sync_pb::CommitResponse::INVALID_MESSAGE;
393 if (sync_pb::CommitResponse::TRANSIENT_ERROR == response) {
394 DVLOG(1) << "Transient Error Committing: " << local_entry;
395 LogServerError(server_entry);
396 return sync_pb::CommitResponse::TRANSIENT_ERROR;
398 if (sync_pb::CommitResponse::INVALID_MESSAGE == response) {
399 LOG(ERROR) << "Error Commiting: " << local_entry;
400 LogServerError(server_entry);
401 return response;
403 if (sync_pb::CommitResponse::CONFLICT == response) {
404 DVLOG(1) << "Conflict Committing: " << local_entry;
405 return response;
407 if (sync_pb::CommitResponse::RETRY == response) {
408 DVLOG(1) << "Retry Committing: " << local_entry;
409 return response;
411 if (sync_pb::CommitResponse::OVER_QUOTA == response) {
412 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
413 return response;
415 if (!server_entry.has_id_string()) {
416 LOG(ERROR) << "Commit response has no id";
417 return sync_pb::CommitResponse::INVALID_MESSAGE;
420 // Implied by the IsValid call above, but here for clarity.
421 DCHECK_EQ(sync_pb::CommitResponse::SUCCESS, response) << response;
422 // Check to see if we've been given the ID of an existing entry. If so treat
423 // it as an error response and retry later.
424 const syncable::Id& server_entry_id =
425 SyncableIdFromProto(server_entry.id_string());
426 if (local_entry.GetId() != server_entry_id) {
427 Entry e(trans, syncable::GET_BY_ID, server_entry_id);
428 if (e.good()) {
429 LOG(ERROR)
430 << "Got duplicate id when commiting id: "
431 << local_entry.GetId()
432 << ". Treating as an error return";
433 return sync_pb::CommitResponse::INVALID_MESSAGE;
437 if (server_entry.version() == 0) {
438 LOG(WARNING) << "Server returned a zero version on a commit response.";
441 ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
442 local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
443 return response;
446 } // namespace commit_util
448 } // namespace syncer