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 "components/sync_driver/ui_data_type_controller.h"
6
7#include "base/logging.h"
8#include "base/memory/weak_ptr.h"
9#include "components/sync_driver/generic_change_processor_factory.h"
10#include "components/sync_driver/shared_change_processor_ref.h"
11#include "sync/api/sync_error.h"
12#include "sync/api/syncable_service.h"
13#include "sync/internal_api/public/base/model_type.h"
14#include "sync/util/data_type_histogram.h"
15
16namespace sync_driver {
17
18UIDataTypeController::UIDataTypeController()
19    : DataTypeController(base::MessageLoopProxy::current(),
20                         base::Closure()),
21      sync_factory_(NULL),
22      state_(NOT_RUNNING),
23      type_(syncer::UNSPECIFIED) {
24}
25
26UIDataTypeController::UIDataTypeController(
27    scoped_refptr<base::MessageLoopProxy> ui_thread,
28    const base::Closure& error_callback,
29    syncer::ModelType type,
30    SyncApiComponentFactory* sync_factory)
31    : DataTypeController(ui_thread, error_callback),
32      sync_factory_(sync_factory),
33      state_(NOT_RUNNING),
34      type_(type),
35      processor_factory_(new GenericChangeProcessorFactory()),
36      ui_thread_(ui_thread) {
37  DCHECK(ui_thread_->BelongsToCurrentThread());
38  DCHECK(sync_factory);
39  DCHECK(syncer::IsRealDataType(type_));
40}
41
42void UIDataTypeController::SetGenericChangeProcessorFactoryForTest(
43      scoped_ptr<GenericChangeProcessorFactory> factory) {
44  DCHECK_EQ(state_, NOT_RUNNING);
45  processor_factory_ = factory.Pass();
46}
47
48UIDataTypeController::~UIDataTypeController() {
49  DCHECK(ui_thread_->BelongsToCurrentThread());
50}
51
52void UIDataTypeController::LoadModels(
53    const ModelLoadCallback& model_load_callback) {
54  DCHECK(ui_thread_->BelongsToCurrentThread());
55  DCHECK(syncer::IsRealDataType(type_));
56  model_load_callback_ = model_load_callback;
57  if (state_ != NOT_RUNNING) {
58    model_load_callback.Run(type(),
59                            syncer::SyncError(FROM_HERE,
60                                              syncer::SyncError::DATATYPE_ERROR,
61                                              "Model already loaded",
62                                              type()));
63    return;
64  }
65  // Since we can't be called multiple times before Stop() is called,
66  // |shared_change_processor_| must be NULL here.
67  DCHECK(!shared_change_processor_.get());
68  shared_change_processor_ = new SharedChangeProcessor();
69
70  state_ = MODEL_STARTING;
71  if (!StartModels()) {
72    // If we are waiting for some external service to load before associating
73    // or we failed to start the models, we exit early. state_ will control
74    // what we perform next.
75    DCHECK(state_ == NOT_RUNNING || state_ == MODEL_STARTING);
76    return;
77  }
78
79  state_ = MODEL_LOADED;
80  model_load_callback_.Run(type(), syncer::SyncError());
81}
82
83void UIDataTypeController::OnModelLoaded() {
84  DCHECK(ui_thread_->BelongsToCurrentThread());
85  DCHECK_EQ(state_, MODEL_STARTING);
86
87  state_ = MODEL_LOADED;
88  model_load_callback_.Run(type(), syncer::SyncError());
89}
90
91void UIDataTypeController::StartAssociating(
92    const StartCallback& start_callback) {
93  DCHECK(ui_thread_->BelongsToCurrentThread());
94  DCHECK(!start_callback.is_null());
95  DCHECK_EQ(state_, MODEL_LOADED);
96
97  start_callback_ = start_callback;
98  state_ = ASSOCIATING;
99  Associate();
100  // It's possible StartDone(..) resulted in a Stop() call, or that association
101  // failed, so we just verify that the state has moved foward.
102  DCHECK_NE(state_, ASSOCIATING);
103}
104
105bool UIDataTypeController::StartModels() {
106  DCHECK_EQ(state_, MODEL_STARTING);
107  // By default, no additional services need to be started before we can proceed
108  // with model association.
109  return true;
110}
111
112void UIDataTypeController::Associate() {
113  DCHECK_EQ(state_, ASSOCIATING);
114  syncer::SyncMergeResult local_merge_result(type());
115  syncer::SyncMergeResult syncer_merge_result(type());
116  base::WeakPtrFactory<syncer::SyncMergeResult> weak_ptr_factory(
117      &syncer_merge_result);
118
119  // Connect |shared_change_processor_| to the syncer and get the
120  // syncer::SyncableService associated with type().
121  local_service_ = shared_change_processor_->Connect(
122      sync_factory_,
123      processor_factory_.get(),
124      user_share(),
125      this,
126      type(),
127      weak_ptr_factory.GetWeakPtr());
128  if (!local_service_.get()) {
129    syncer::SyncError error(FROM_HERE,
130                            syncer::SyncError::DATATYPE_ERROR,
131                            "Failed to connect to syncer.",
132                            type());
133    local_merge_result.set_error(error);
134    StartDone(ASSOCIATION_FAILED,
135              local_merge_result,
136              syncer_merge_result);
137    return;
138  }
139
140  if (!shared_change_processor_->CryptoReadyIfNecessary()) {
141    syncer::SyncError error(FROM_HERE,
142                            syncer::SyncError::CRYPTO_ERROR,
143                            "",
144                            type());
145    local_merge_result.set_error(error);
146    StartDone(NEEDS_CRYPTO,
147              local_merge_result,
148              syncer_merge_result);
149    return;
150  }
151
152  bool sync_has_nodes = false;
153  if (!shared_change_processor_->SyncModelHasUserCreatedNodes(
154          &sync_has_nodes)) {
155    syncer::SyncError error(FROM_HERE,
156                            syncer::SyncError::UNRECOVERABLE_ERROR,
157                            "Failed to load sync nodes",
158                            type());
159    local_merge_result.set_error(error);
160    StartDone(UNRECOVERABLE_ERROR,
161              local_merge_result,
162              syncer_merge_result);
163    return;
164  }
165
166  base::TimeTicks start_time = base::TimeTicks::Now();
167  syncer::SyncDataList initial_sync_data;
168  syncer::SyncError error =
169      shared_change_processor_->GetAllSyncDataReturnError(
170          type(), &initial_sync_data);
171  if (error.IsSet()) {
172    local_merge_result.set_error(error);
173    StartDone(ASSOCIATION_FAILED,
174              local_merge_result,
175              syncer_merge_result);
176    return;
177  }
178
179  std::string datatype_context;
180  if (shared_change_processor_->GetDataTypeContext(&datatype_context)) {
181    local_service_->UpdateDataTypeContext(
182        type(), syncer::SyncChangeProcessor::NO_REFRESH, datatype_context);
183  }
184
185  syncer_merge_result.set_num_items_before_association(
186      initial_sync_data.size());
187  // Passes a reference to |shared_change_processor_|.
188  local_merge_result = local_service_->MergeDataAndStartSyncing(
189      type(),
190      initial_sync_data,
191      scoped_ptr<syncer::SyncChangeProcessor>(
192          new SharedChangeProcessorRef(shared_change_processor_)),
193      scoped_ptr<syncer::SyncErrorFactory>(
194          new SharedChangeProcessorRef(shared_change_processor_)));
195  RecordAssociationTime(base::TimeTicks::Now() - start_time);
196  if (local_merge_result.error().IsSet()) {
197    StartDone(ASSOCIATION_FAILED,
198              local_merge_result,
199              syncer_merge_result);
200    return;
201  }
202
203  syncer_merge_result.set_num_items_after_association(
204      shared_change_processor_->GetSyncCount());
205
206  state_ = RUNNING;
207  StartDone(sync_has_nodes ? OK : OK_FIRST_RUN,
208            local_merge_result,
209            syncer_merge_result);
210}
211
212ChangeProcessor* UIDataTypeController::GetChangeProcessor() const {
213  DCHECK_EQ(state_, RUNNING);
214  return shared_change_processor_->generic_change_processor();
215}
216
217void UIDataTypeController::AbortModelLoad() {
218  DCHECK(ui_thread_->BelongsToCurrentThread());
219  state_ = NOT_RUNNING;
220
221  if (shared_change_processor_.get()) {
222    shared_change_processor_ = NULL;
223  }
224
225  // We don't want to continue loading models (e.g OnModelLoaded should never be
226  // called after we've decided to abort).
227  StopModels();
228}
229
230void UIDataTypeController::StartDone(
231    ConfigureResult start_result,
232    const syncer::SyncMergeResult& local_merge_result,
233    const syncer::SyncMergeResult& syncer_merge_result) {
234  DCHECK(ui_thread_->BelongsToCurrentThread());
235
236  if (!IsSuccessfulResult(start_result)) {
237    StopModels();
238    if (start_result == ASSOCIATION_FAILED) {
239      state_ = DISABLED;
240    } else {
241      state_ = NOT_RUNNING;
242    }
243    RecordStartFailure(start_result);
244
245    if (shared_change_processor_.get()) {
246      shared_change_processor_->Disconnect();
247      shared_change_processor_ = NULL;
248    }
249  }
250
251  start_callback_.Run(start_result, local_merge_result, syncer_merge_result);
252}
253
254void UIDataTypeController::Stop() {
255  DCHECK(ui_thread_->BelongsToCurrentThread());
256  DCHECK(syncer::IsRealDataType(type_));
257
258  if (state_ == NOT_RUNNING)
259    return;
260
261  State prev_state = state_;
262  state_ = STOPPING;
263
264  if (shared_change_processor_.get()) {
265    shared_change_processor_->Disconnect();
266    shared_change_processor_ = NULL;
267  }
268
269  // If Stop() is called while Start() is waiting for the datatype model to
270  // load, abort the start.
271  if (prev_state == MODEL_STARTING) {
272    AbortModelLoad();
273    // We can just return here since we haven't performed association if we're
274    // still in MODEL_STARTING.
275    return;
276  }
277
278  StopModels();
279
280  if (local_service_.get()) {
281    local_service_->StopSyncing(type());
282  }
283
284  state_ = NOT_RUNNING;
285}
286
287syncer::ModelType UIDataTypeController::type() const {
288  DCHECK(syncer::IsRealDataType(type_));
289  return type_;
290}
291
292void UIDataTypeController::StopModels() {
293  // Do nothing by default.
294}
295
296syncer::ModelSafeGroup UIDataTypeController::model_safe_group() const {
297  DCHECK(syncer::IsRealDataType(type_));
298  return syncer::GROUP_UI;
299}
300
301std::string UIDataTypeController::name() const {
302  // For logging only.
303  return syncer::ModelTypeToString(type());
304}
305
306DataTypeController::State UIDataTypeController::state() const {
307  return state_;
308}
309
310void UIDataTypeController::OnSingleDataTypeUnrecoverableError(
311    const syncer::SyncError& error) {
312  DCHECK_EQ(type(), error.model_type());
313  UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures",
314                            ModelTypeToHistogramInt(type()),
315                            syncer::MODEL_TYPE_COUNT);
316  // TODO(tim): We double-upload some errors.  See bug 383480.
317  if (!error_callback_.is_null())
318    error_callback_.Run();
319  if (!model_load_callback_.is_null()) {
320    base::MessageLoop::current()->PostTask(
321        FROM_HERE, base::Bind(model_load_callback_, type(), error));
322  }
323}
324
325void UIDataTypeController::RecordAssociationTime(base::TimeDelta time) {
326  DCHECK(ui_thread_->BelongsToCurrentThread());
327#define PER_DATA_TYPE_MACRO(type_str) \
328    UMA_HISTOGRAM_TIMES("Sync." type_str "AssociationTime", time);
329  SYNC_DATA_TYPE_HISTOGRAM(type());
330#undef PER_DATA_TYPE_MACRO
331}
332
333void UIDataTypeController::RecordStartFailure(ConfigureResult result) {
334  DCHECK(ui_thread_->BelongsToCurrentThread());
335  UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures",
336                            ModelTypeToHistogramInt(type()),
337                            syncer::MODEL_TYPE_COUNT);
338#define PER_DATA_TYPE_MACRO(type_str) \
339    UMA_HISTOGRAM_ENUMERATION("Sync." type_str "StartFailure", result, \
340                              MAX_START_RESULT);
341  SYNC_DATA_TYPE_HISTOGRAM(type());
342#undef PER_DATA_TYPE_MACRO
343}
344
345}  // namespace sync_driver
346