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