1// Copyright 2014 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/directory_commit_contribution.h"
6
7#include "sync/engine/commit_util.h"
8#include "sync/engine/get_commit_ids.h"
9#include "sync/engine/syncer_util.h"
10#include "sync/internal_api/public/sessions/commit_counters.h"
11#include "sync/syncable/model_neutral_mutable_entry.h"
12#include "sync/syncable/syncable_model_neutral_write_transaction.h"
13
14namespace syncer {
15
16using syncable::GET_BY_HANDLE;
17using syncable::SYNCER;
18
19DirectoryCommitContribution::~DirectoryCommitContribution() {
20  DCHECK(!syncing_bits_set_);
21}
22
23// static.
24scoped_ptr<DirectoryCommitContribution> DirectoryCommitContribution::Build(
25    syncable::Directory* dir,
26    ModelType type,
27    size_t max_entries,
28    DirectoryTypeDebugInfoEmitter* debug_info_emitter) {
29  DCHECK(debug_info_emitter);
30
31  std::vector<int64> metahandles;
32
33  syncable::ModelNeutralWriteTransaction trans(FROM_HERE, SYNCER, dir);
34  GetCommitIdsForType(&trans, type, max_entries, &metahandles);
35
36  if (metahandles.empty())
37    return scoped_ptr<DirectoryCommitContribution>();
38
39  google::protobuf::RepeatedPtrField<sync_pb::SyncEntity> entities;
40  for (std::vector<int64>::iterator it = metahandles.begin();
41       it != metahandles.end(); ++it) {
42    sync_pb::SyncEntity* entity = entities.Add();
43    syncable::ModelNeutralMutableEntry entry(&trans, GET_BY_HANDLE, *it);
44    commit_util::BuildCommitItem(entry, entity);
45    entry.PutSyncing(true);
46  }
47
48  sync_pb::DataTypeContext context;
49  dir->GetDataTypeContext(&trans, type, &context);
50
51  return scoped_ptr<DirectoryCommitContribution>(
52      new DirectoryCommitContribution(
53          metahandles,
54          entities,
55          context,
56          dir,
57          debug_info_emitter));
58}
59
60void DirectoryCommitContribution::AddToCommitMessage(
61    sync_pb::ClientToServerMessage* msg) {
62  DCHECK(syncing_bits_set_);
63  sync_pb::CommitMessage* commit_message = msg->mutable_commit();
64  entries_start_index_ = commit_message->entries_size();
65  std::copy(entities_.begin(),
66            entities_.end(),
67            RepeatedPtrFieldBackInserter(commit_message->mutable_entries()));
68  if (!context_.context().empty())
69    commit_message->add_client_contexts()->Swap(&context_);
70
71  CommitCounters* counters = debug_info_emitter_->GetMutableCommitCounters();
72  counters->num_commits_attempted += entities_.size();
73}
74
75SyncerError DirectoryCommitContribution::ProcessCommitResponse(
76    const sync_pb::ClientToServerResponse& response,
77    sessions::StatusController* status) {
78  DCHECK(syncing_bits_set_);
79  const sync_pb::CommitResponse& commit_response = response.commit();
80
81  int transient_error_commits = 0;
82  int conflicting_commits = 0;
83  int error_commits = 0;
84  int successes = 0;
85
86  std::set<syncable::Id> deleted_folders;
87  {
88    syncable::ModelNeutralWriteTransaction trans(FROM_HERE, SYNCER, dir_);
89    for (size_t i = 0; i < metahandles_.size(); ++i) {
90      sync_pb::CommitResponse::ResponseType response_type =
91          commit_util::ProcessSingleCommitResponse(
92              &trans,
93              commit_response.entryresponse(entries_start_index_ + i),
94              entities_.Get(i),
95              metahandles_[i],
96              &deleted_folders);
97      switch (response_type) {
98        case sync_pb::CommitResponse::INVALID_MESSAGE:
99          ++error_commits;
100          break;
101        case sync_pb::CommitResponse::CONFLICT:
102          ++conflicting_commits;
103          status->increment_num_server_conflicts();
104          break;
105        case sync_pb::CommitResponse::SUCCESS:
106          ++successes;
107          {
108            syncable::Entry e(&trans, GET_BY_HANDLE, metahandles_[i]);
109            if (e.GetModelType() == BOOKMARKS)
110              status->increment_num_successful_bookmark_commits();
111          }
112          status->increment_num_successful_commits();
113          break;
114        case sync_pb::CommitResponse::OVER_QUOTA:
115          // We handle over quota like a retry, which is same as transient.
116        case sync_pb::CommitResponse::RETRY:
117        case sync_pb::CommitResponse::TRANSIENT_ERROR:
118          ++transient_error_commits;
119          break;
120        default:
121          LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
122      }
123    }
124    MarkDeletedChildrenSynced(dir_, &trans, &deleted_folders);
125  }
126
127  CommitCounters* counters = debug_info_emitter_->GetMutableCommitCounters();
128  counters->num_commits_success += successes;
129  counters->num_commits_conflict += transient_error_commits;
130  counters->num_commits_error += transient_error_commits;
131
132  int commit_count = static_cast<int>(metahandles_.size());
133  if (commit_count == successes) {
134    return SYNCER_OK;
135  } else if (error_commits > 0) {
136    return SERVER_RETURN_UNKNOWN_ERROR;
137  } else if (transient_error_commits > 0) {
138    return SERVER_RETURN_TRANSIENT_ERROR;
139  } else if (conflicting_commits > 0) {
140    // This means that the server already has an item with this version, but
141    // we haven't seen that update yet.
142    //
143    // A well-behaved client should respond to this by proceeding to the
144    // download updates phase, fetching the conflicting items, then attempting
145    // to resolve the conflict.  That's not what this client does.
146    //
147    // We don't currently have any code to support that exceptional control
148    // flow.  Instead, we abort the current sync cycle and start a new one.  The
149    // end result is the same.
150    return SERVER_RETURN_CONFLICT;
151  } else {
152    LOG(FATAL) << "Inconsistent counts when processing commit response";
153    return SYNCER_OK;
154  }
155}
156
157void DirectoryCommitContribution::CleanUp() {
158  DCHECK(syncing_bits_set_);
159  UnsetSyncingBits();
160  debug_info_emitter_->EmitCommitCountersUpdate();
161  debug_info_emitter_->EmitStatusCountersUpdate();
162}
163
164size_t DirectoryCommitContribution::GetNumEntries() const {
165  return metahandles_.size();
166}
167
168DirectoryCommitContribution::DirectoryCommitContribution(
169    const std::vector<int64>& metahandles,
170    const google::protobuf::RepeatedPtrField<sync_pb::SyncEntity>& entities,
171    const sync_pb::DataTypeContext& context,
172    syncable::Directory* dir,
173    DirectoryTypeDebugInfoEmitter* debug_info_emitter)
174    : dir_(dir),
175      metahandles_(metahandles),
176      entities_(entities),
177      context_(context),
178      entries_start_index_(0xDEADBEEF),
179      syncing_bits_set_(true),
180      debug_info_emitter_(debug_info_emitter) {}
181
182void DirectoryCommitContribution::UnsetSyncingBits() {
183  syncable::ModelNeutralWriteTransaction trans(FROM_HERE, SYNCER, dir_);
184  for (std::vector<int64>::const_iterator it = metahandles_.begin();
185       it != metahandles_.end(); ++it) {
186    syncable::ModelNeutralMutableEntry entry(&trans, GET_BY_HANDLE, *it);
187    entry.PutSyncing(false);
188  }
189  syncing_bits_set_ = false;
190}
191
192}  // namespace syncer
193