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
29using std::set;
30using std::string;
31using std::vector;
32
33namespace syncer {
34
35using sessions::SyncSession;
36using syncable::Entry;
37using syncable::IS_DEL;
38using syncable::IS_UNAPPLIED_UPDATE;
39using syncable::IS_UNSYNCED;
40using syncable::Id;
41using syncable::SPECIFICS;
42using syncable::UNIQUE_POSITION;
43
44namespace commit_util {
45
46void 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
72void 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
86namespace {
87
88void 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
98void 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
109void 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.
212namespace {
213
214void 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
221const 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
231bool 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
268bool 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
298void 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
351void 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
395sync_pb::CommitResponse::ResponseType
396ProcessSingleCommitResponse(
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