• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AddExtensionsActivityToMessage(ExtensionsActivity * activity,ExtensionsActivity::Records * extensions_activity_buffer,sync_pb::CommitMessage * message)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 
AddClientConfigParamsToMessage(ModelTypeSet enabled_types,sync_pb::CommitMessage * message)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 
SetEntrySpecifics(const Entry & meta_entry,sync_pb::SyncEntity * sync_entry)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 
SetAttachmentIds(const Entry & meta_entry,sync_pb::SyncEntity * sync_entry)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 
BuildCommitItem(const syncable::Entry & meta_entry,sync_pb::SyncEntity * sync_entry)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 
LogServerError(const sync_pb::CommitResponse_EntryResponse & res)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 
GetResultingPostCommitName(const sync_pb::SyncEntity & committed_entry,const sync_pb::CommitResponse_EntryResponse & entry_response)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 
UpdateVersionAfterCommit(const sync_pb::SyncEntity & committed_entry,const sync_pb::CommitResponse_EntryResponse & entry_response,const syncable::Id & pre_commit_id,syncable::ModelNeutralMutableEntry * local_entry)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 
ChangeIdAfterCommit(const sync_pb::CommitResponse_EntryResponse & entry_response,const syncable::Id & pre_commit_id,syncable::ModelNeutralMutableEntry * local_entry)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 
UpdateServerFieldsAfterCommit(const sync_pb::SyncEntity & committed_entry,const sync_pb::CommitResponse_EntryResponse & entry_response,syncable::ModelNeutralMutableEntry * local_entry)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 
ProcessSuccessfulCommitResponse(const sync_pb::SyncEntity & committed_entry,const sync_pb::CommitResponse_EntryResponse & entry_response,const syncable::Id & pre_commit_id,syncable::ModelNeutralMutableEntry * local_entry,bool syncing_was_set,set<syncable::Id> * deleted_folders)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
ProcessSingleCommitResponse(syncable::BaseWriteTransaction * trans,const sync_pb::CommitResponse_EntryResponse & server_entry,const sync_pb::SyncEntity & commit_request_entry,int64 metahandle,set<syncable::Id> * deleted_folders)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