summaryrefslogtreecommitdiffstats
path: root/chromium/sync/engine/commit_util.cc
blob: 1081446b7a8a065b02cc42619a7ca71f83bd4889 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sync/engine/commit_util.h"

#include <limits>
#include <set>
#include <string>
#include <vector>

#include "base/strings/string_util.h"
#include "sync/engine/syncer_proto_util.h"
#include "sync/internal_api/public/base/unique_position.h"
#include "sync/protocol/bookmark_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
#include "sync/sessions/sync_session.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/model_neutral_mutable_entry.h"
#include "sync/syncable/syncable_base_transaction.h"
#include "sync/syncable/syncable_base_write_transaction.h"
#include "sync/syncable/syncable_changes_version.h"
#include "sync/syncable/syncable_proto_util.h"
#include "sync/syncable/syncable_util.h"
#include "sync/util/time.h"

using std::set;
using std::string;
using std::vector;

namespace syncer {

using sessions::SyncSession;
using syncable::Entry;
using syncable::IS_DEL;
using syncable::IS_UNAPPLIED_UPDATE;
using syncable::IS_UNSYNCED;
using syncable::Id;
using syncable::SPECIFICS;
using syncable::UNIQUE_POSITION;

namespace commit_util {

void AddExtensionsActivityToMessage(
    ExtensionsActivity* activity,
    ExtensionsActivity::Records* extensions_activity_buffer,
    sync_pb::CommitMessage* message) {
  // This isn't perfect, since the set of extensions activity may not correlate
  // exactly with the items being committed.  That's OK as long as we're looking
  // for a rough estimate of extensions activity, not an precise mapping of
  // which commits were triggered by which extension.
  //
  // We will push this list of extensions activity back into the
  // ExtensionsActivityMonitor if this commit fails.  That's why we must keep a
  // copy of these records in the session.
  activity->GetAndClearRecords(extensions_activity_buffer);

  const ExtensionsActivity::Records& records = *extensions_activity_buffer;
  for (ExtensionsActivity::Records::const_iterator it =
       records.begin();
       it != records.end(); ++it) {
    sync_pb::ChromiumExtensionsActivity* activity_message =
        message->add_extensions_activity();
    activity_message->set_extension_id(it->second.extension_id);
    activity_message->set_bookmark_writes_since_last_commit(
        it->second.bookmark_write_count);
  }
}

void AddClientConfigParamsToMessage(
    ModelTypeSet enabled_types,
    sync_pb::CommitMessage* message) {
  sync_pb::ClientConfigParams* config_params = message->mutable_config_params();
  for (ModelTypeSet::Iterator it = enabled_types.First(); it.Good(); it.Inc()) {
    if (ProxyTypes().Has(it.Get()))
      continue;
    int field_number = GetSpecificsFieldNumberFromModelType(it.Get());
    config_params->mutable_enabled_type_ids()->Add(field_number);
  }
  config_params->set_tabs_datatype_enabled(
      enabled_types.Has(syncer::PROXY_TABS));
}

namespace {
void SetEntrySpecifics(const Entry& meta_entry,
                       sync_pb::SyncEntity* sync_entry) {
  // Add the new style extension and the folder bit.
  sync_entry->mutable_specifics()->CopyFrom(meta_entry.GetSpecifics());
  sync_entry->set_folder(meta_entry.GetIsDir());

  CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data());
  DCHECK_EQ(meta_entry.GetModelType(), GetModelType(*sync_entry));
}
}  // namespace

void BuildCommitItem(
    const syncable::Entry& meta_entry,
    sync_pb::SyncEntity* sync_entry) {
  syncable::Id id = meta_entry.GetId();
  sync_entry->set_id_string(SyncableIdToProto(id));

  string name = meta_entry.GetNonUniqueName();
  CHECK(!name.empty());  // Make sure this isn't an update.
  // Note: Truncation is also performed in WriteNode::SetTitle(..). But this
  // call is still necessary to handle any title changes that might originate
  // elsewhere, or already be persisted in the directory.
  base::TruncateUTF8ToByteSize(name, 255, &name);
  sync_entry->set_name(name);

  // Set the non_unique_name.  If we do, the server ignores
  // the |name| value (using |non_unique_name| instead), and will return
  // in the CommitResponse a unique name if one is generated.
  // We send both because it may aid in logging.
  sync_entry->set_non_unique_name(name);

  if (!meta_entry.GetUniqueClientTag().empty()) {
    sync_entry->set_client_defined_unique_tag(
        meta_entry.GetUniqueClientTag());
  }

  // Deleted items with server-unknown parent ids can be a problem so we set
  // the parent to 0. (TODO(sync): Still true in protocol?).
  Id new_parent_id;
  if (meta_entry.GetIsDel() &&
      !meta_entry.GetParentId().ServerKnows()) {
    new_parent_id = syncable::BaseTransaction::root_id();
  } else {
    new_parent_id = meta_entry.GetParentId();
  }
  sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id));

  // If our parent has changed, send up the old one so the server
  // can correctly deal with multiple parents.
  // TODO(nick): With the server keeping track of the primary sync parent,
  // it should not be necessary to provide the old_parent_id: the version
  // number should suffice.
  if (new_parent_id != meta_entry.GetServerParentId() &&
      0 != meta_entry.GetBaseVersion() &&
      syncable::CHANGES_VERSION != meta_entry.GetBaseVersion()) {
    sync_entry->set_old_parent_id(
        SyncableIdToProto(meta_entry.GetServerParentId()));
  }

  int64 version = meta_entry.GetBaseVersion();
  if (syncable::CHANGES_VERSION == version || 0 == version) {
    // Undeletions are only supported for items that have a client tag.
    DCHECK(!id.ServerKnows() ||
           !meta_entry.GetUniqueClientTag().empty())
        << meta_entry;

    // Version 0 means to create or undelete an object.
    sync_entry->set_version(0);
  } else {
    DCHECK(id.ServerKnows()) << meta_entry;
    sync_entry->set_version(meta_entry.GetBaseVersion());
  }
  sync_entry->set_ctime(TimeToProtoTime(meta_entry.GetCtime()));
  sync_entry->set_mtime(TimeToProtoTime(meta_entry.GetMtime()));

  // Deletion is final on the server, let's move things and then delete them.
  if (meta_entry.GetIsDel()) {
    sync_entry->set_deleted(true);
  } else {
    if (meta_entry.GetSpecifics().has_bookmark()) {
      // Both insert_after_item_id and position_in_parent fields are set only
      // for legacy reasons.  See comments in sync.proto for more information.
      const Id& prev_id = meta_entry.GetPredecessorId();
      string prev_id_string =
          prev_id.IsRoot() ? string() : prev_id.GetServerId();
      sync_entry->set_insert_after_item_id(prev_id_string);
      sync_entry->set_position_in_parent(
          meta_entry.GetUniquePosition().ToInt64());
      meta_entry.GetUniquePosition().ToProto(
          sync_entry->mutable_unique_position());
    }
    SetEntrySpecifics(meta_entry, sync_entry);
  }
}


// Helpers for ProcessSingleCommitResponse.
namespace {

void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
  if (res.has_error_message())
    LOG(WARNING) << "  " << res.error_message();
  else
    LOG(WARNING) << "  No detailed error message returned from server";
}

const string& GetResultingPostCommitName(
    const sync_pb::SyncEntity& committed_entry,
    const sync_pb::CommitResponse_EntryResponse& entry_response) {
  const string& response_name =
      SyncerProtoUtil::NameFromCommitEntryResponse(entry_response);
  if (!response_name.empty())
    return response_name;
  return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
}

bool UpdateVersionAfterCommit(
    const sync_pb::SyncEntity& committed_entry,
    const sync_pb::CommitResponse_EntryResponse& entry_response,
    const syncable::Id& pre_commit_id,
    syncable::ModelNeutralMutableEntry* local_entry) {
  int64 old_version = local_entry->GetBaseVersion();
  int64 new_version = entry_response.version();
  bool bad_commit_version = false;
  if (committed_entry.deleted() &&
      !local_entry->GetUniqueClientTag().empty()) {
    // If the item was deleted, and it's undeletable (uses the client tag),
    // change the version back to zero.  We must set the version to zero so
    // that the server knows to re-create the item if it gets committed
    // later for undeletion.
    new_version = 0;
  } else if (!pre_commit_id.ServerKnows()) {
    bad_commit_version = 0 == new_version;
  } else {
    bad_commit_version = old_version > new_version;
  }
  if (bad_commit_version) {
    LOG(ERROR) << "Bad version in commit return for " << *local_entry
               << " new_id:" << SyncableIdFromProto(entry_response.id_string())
               << " new_version:" << entry_response.version();
    return false;
  }

  // Update the base version and server version.  The base version must change
  // here, even if syncing_was_set is false; that's because local changes were
  // on top of the successfully committed version.
  local_entry->PutBaseVersion(new_version);
  DVLOG(1) << "Commit is changing base version of " << local_entry->GetId()
           << " to: " << new_version;
  local_entry->PutServerVersion(new_version);
  return true;
}

bool ChangeIdAfterCommit(
    const sync_pb::CommitResponse_EntryResponse& entry_response,
    const syncable::Id& pre_commit_id,
    syncable::ModelNeutralMutableEntry* local_entry) {
  syncable::BaseWriteTransaction* trans = local_entry->base_write_transaction();
  const syncable::Id& entry_response_id =
      SyncableIdFromProto(entry_response.id_string());
  if (entry_response_id != pre_commit_id) {
    if (pre_commit_id.ServerKnows()) {
      // The server can sometimes generate a new ID on commit; for example,
      // when committing an undeletion.
      DVLOG(1) << " ID changed while committing an old entry. "
               << pre_commit_id << " became " << entry_response_id << ".";
    }
    syncable::ModelNeutralMutableEntry same_id(
        trans,
        syncable::GET_BY_ID,
        entry_response_id);
    // We should trap this before this function.
    if (same_id.good()) {
      LOG(ERROR) << "ID clash with id " << entry_response_id
                 << " during commit " << same_id;
      return false;
    }
    ChangeEntryIDAndUpdateChildren(trans, local_entry, entry_response_id);
    DVLOG(1) << "Changing ID to " << entry_response_id;
  }
  return true;
}

void UpdateServerFieldsAfterCommit(
    const sync_pb::SyncEntity& committed_entry,
    const sync_pb::CommitResponse_EntryResponse& entry_response,
    syncable::ModelNeutralMutableEntry* local_entry) {

  // We just committed an entry successfully, and now we want to make our view
  // of the server state consistent with the server state. We must be careful;
  // |entry_response| and |committed_entry| have some identically named
  // fields.  We only want to consider fields from |committed_entry| when there
  // is not an overriding field in the |entry_response|.  We do not want to
  // update the server data from the local data in the entry -- it's possible
  // that the local data changed during the commit, and even if not, the server
  // has the last word on the values of several properties.

  local_entry->PutServerIsDel(committed_entry.deleted());
  if (committed_entry.deleted()) {
    // Don't clobber any other fields of deleted objects.
    return;
  }

  local_entry->PutServerIsDir(
      (committed_entry.folder() ||
       committed_entry.bookmarkdata().bookmark_folder()));
  local_entry->PutServerSpecifics(committed_entry.specifics());
  local_entry->PutServerMtime(ProtoTimeToTime(committed_entry.mtime()));
  local_entry->PutServerCtime(ProtoTimeToTime(committed_entry.ctime()));
  if (committed_entry.has_unique_position()) {
    local_entry->PutServerUniquePosition(
                     UniquePosition::FromProto(
                         committed_entry.unique_position()));
  }

  // TODO(nick): The server doesn't set entry_response.server_parent_id in
  // practice; to update SERVER_PARENT_ID appropriately here we'd need to
  // get the post-commit ID of the parent indicated by
  // committed_entry.parent_id_string(). That should be inferrable from the
  // information we have, but it's a bit convoluted to pull it out directly.
  // Getting this right is important: SERVER_PARENT_ID gets fed back into
  // old_parent_id during the next commit.
  local_entry->PutServerParentId(local_entry->GetParentId());
  local_entry->PutServerNonUniqueName(
      GetResultingPostCommitName(committed_entry, entry_response));

  if (local_entry->GetIsUnappliedUpdate()) {
    // This shouldn't happen; an unapplied update shouldn't be committed, and
    // if it were, the commit should have failed.  But if it does happen: we've
    // just overwritten the update info, so clear the flag.
    local_entry->PutIsUnappliedUpdate(false);
  }
}

void 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) {
  DCHECK(local_entry->GetIsUnsynced());

  // Update SERVER_VERSION and BASE_VERSION.
  if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id,
                                local_entry)) {
    LOG(ERROR) << "Bad version in commit return for " << *local_entry
               << " new_id:" << SyncableIdFromProto(entry_response.id_string())
               << " new_version:" << entry_response.version();
    return;
  }

  // If the server gave us a new ID, apply it.
  if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) {
    return;
  }

  // Update our stored copy of the server state.
  UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry);

  // If the item doesn't need to be committed again (an item might need to be
  // committed again if it changed locally during the commit), we can remove
  // it from the unsynced list.
  if (syncing_was_set) {
    local_entry->PutIsUnsynced(false);
  }

  // Make a note of any deleted folders, whose children would have
  // been recursively deleted.
  // TODO(nick): Here, commit_message.deleted() would be more correct than
  // local_entry->GetIsDel().  For example, an item could be renamed, and then
  // deleted during the commit of the rename.  Unit test & fix.
  if (local_entry->GetIsDir() && local_entry->GetIsDel()) {
    deleted_folders->insert(local_entry->GetId());
  }
}

}  // namespace

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) {
  syncable::ModelNeutralMutableEntry local_entry(
      trans,
      syncable::GET_BY_HANDLE,
      metahandle);
  CHECK(local_entry.good());
  bool syncing_was_set = local_entry.GetSyncing();
  local_entry.PutSyncing(false);

  sync_pb::CommitResponse::ResponseType response = server_entry.response_type();
  if (!sync_pb::CommitResponse::ResponseType_IsValid(response)) {
    LOG(ERROR) << "Commit response has unknown response type! Possibly out "
               "of date client?";
    return sync_pb::CommitResponse::INVALID_MESSAGE;
  }
  if (sync_pb::CommitResponse::TRANSIENT_ERROR == response) {
    DVLOG(1) << "Transient Error Committing: " << local_entry;
    LogServerError(server_entry);
    return sync_pb::CommitResponse::TRANSIENT_ERROR;
  }
  if (sync_pb::CommitResponse::INVALID_MESSAGE == response) {
    LOG(ERROR) << "Error Commiting: " << local_entry;
    LogServerError(server_entry);
    return response;
  }
  if (sync_pb::CommitResponse::CONFLICT == response) {
    DVLOG(1) << "Conflict Committing: " << local_entry;
    return response;
  }
  if (sync_pb::CommitResponse::RETRY == response) {
    DVLOG(1) << "Retry Committing: " << local_entry;
    return response;
  }
  if (sync_pb::CommitResponse::OVER_QUOTA == response) {
    LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
    return response;
  }
  if (!server_entry.has_id_string()) {
    LOG(ERROR) << "Commit response has no id";
    return sync_pb::CommitResponse::INVALID_MESSAGE;
  }

  // Implied by the IsValid call above, but here for clarity.
  DCHECK_EQ(sync_pb::CommitResponse::SUCCESS, response) << response;
  // Check to see if we've been given the ID of an existing entry. If so treat
  // it as an error response and retry later.
  const syncable::Id& server_entry_id =
      SyncableIdFromProto(server_entry.id_string());
  if (local_entry.GetId() != server_entry_id) {
    Entry e(trans, syncable::GET_BY_ID, server_entry_id);
    if (e.good()) {
      LOG(ERROR)
          << "Got duplicate id when commiting id: "
          << local_entry.GetId()
          << ". Treating as an error return";
      return sync_pb::CommitResponse::INVALID_MESSAGE;
    }
  }

  if (server_entry.version() == 0) {
    LOG(WARNING) << "Server returned a zero version on a commit response.";
  }

  ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
      local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
  return response;
}

}  // namespace commit_util

}  // namespace syncer