1// Copyright (c) 2012 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 "chrome/browser/sync/glue/frontend_data_type_controller.h"
6
7#include "base/logging.h"
8#include "chrome/browser/profiles/profile.h"
9#include "chrome/browser/sync/glue/change_processor.h"
10#include "chrome/browser/sync/glue/chrome_report_unrecoverable_error.h"
11#include "chrome/browser/sync/glue/model_associator.h"
12#include "chrome/browser/sync/profile_sync_components_factory.h"
13#include "chrome/browser/sync/profile_sync_service.h"
14#include "content/public/browser/browser_thread.h"
15#include "sync/api/sync_error.h"
16#include "sync/internal_api/public/base/model_type.h"
17#include "sync/util/data_type_histogram.h"
18
19using content::BrowserThread;
20
21namespace browser_sync {
22
23FrontendDataTypeController::FrontendDataTypeController(
24    ProfileSyncComponentsFactory* profile_sync_factory,
25    Profile* profile,
26    ProfileSyncService* sync_service)
27    : profile_sync_factory_(profile_sync_factory),
28      profile_(profile),
29      sync_service_(sync_service),
30      state_(NOT_RUNNING) {
31  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
32  DCHECK(profile_sync_factory);
33  DCHECK(profile);
34  DCHECK(sync_service);
35}
36
37void FrontendDataTypeController::LoadModels(
38    const ModelLoadCallback& model_load_callback) {
39  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40  DCHECK(!model_load_callback.is_null());
41
42  if (state_ != NOT_RUNNING) {
43    model_load_callback.Run(type(),
44                            syncer::SyncError(FROM_HERE,
45                                              syncer::SyncError::DATATYPE_ERROR,
46                                              "Model already running",
47                                              type()));
48    return;
49  }
50
51  DCHECK(model_load_callback_.is_null());
52
53  model_load_callback_ = model_load_callback;
54  state_ = MODEL_STARTING;
55  if (!StartModels()) {
56    // If we are waiting for some external service to load before associating
57    // or we failed to start the models, we exit early. state_ will control
58    // what we perform next.
59    DCHECK(state_ == NOT_RUNNING || state_ == MODEL_STARTING);
60    return;
61  }
62
63  OnModelLoaded();
64}
65
66void FrontendDataTypeController::OnModelLoaded() {
67  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
68  DCHECK(!model_load_callback_.is_null());
69  DCHECK_EQ(state_, MODEL_STARTING);
70
71  state_ = MODEL_LOADED;
72  ModelLoadCallback model_load_callback = model_load_callback_;
73  model_load_callback_.Reset();
74  model_load_callback.Run(type(), syncer::SyncError());
75}
76
77void FrontendDataTypeController::StartAssociating(
78    const StartCallback& start_callback) {
79  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
80  DCHECK(!start_callback.is_null());
81  DCHECK(start_callback_.is_null());
82  DCHECK_EQ(state_, MODEL_LOADED);
83
84  start_callback_ = start_callback;
85  state_ = ASSOCIATING;
86  if (!Associate()) {
87    // It's possible StartDone(..) resulted in a Stop() call, or that
88    // association failed, so we just verify that the state has moved forward.
89    DCHECK_NE(state_, ASSOCIATING);
90    return;
91  }
92  DCHECK_EQ(state_, RUNNING);
93}
94
95void FrontendDataTypeController::Stop() {
96  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
97
98  State prev_state = state_;
99  state_ = STOPPING;
100
101  // If Stop() is called while Start() is waiting for the datatype model to
102  // load, abort the start.
103  if (prev_state == MODEL_STARTING) {
104    AbortModelLoad();
105    // We can just return here since we haven't performed association if we're
106    // still in MODEL_STARTING.
107    return;
108  }
109  DCHECK(start_callback_.is_null());
110
111  CleanUpState();
112
113  sync_service_->DeactivateDataType(type());
114
115  if (model_associator()) {
116    syncer::SyncError error;  // Not used.
117    error = model_associator()->DisassociateModels();
118  }
119
120  set_model_associator(NULL);
121  change_processor_.reset();
122
123  state_ = NOT_RUNNING;
124}
125
126syncer::ModelSafeGroup FrontendDataTypeController::model_safe_group()
127    const {
128  return syncer::GROUP_UI;
129}
130
131std::string FrontendDataTypeController::name() const {
132  // For logging only.
133  return syncer::ModelTypeToString(type());
134}
135
136DataTypeController::State FrontendDataTypeController::state() const {
137  return state_;
138}
139
140void FrontendDataTypeController::OnSingleDatatypeUnrecoverableError(
141    const tracked_objects::Location& from_here, const std::string& message) {
142  RecordUnrecoverableError(from_here, message);
143  sync_service_->DisableBrokenDatatype(type(), from_here, message);
144}
145
146FrontendDataTypeController::FrontendDataTypeController()
147    : profile_sync_factory_(NULL),
148      profile_(NULL),
149      sync_service_(NULL),
150      state_(NOT_RUNNING) {
151}
152
153FrontendDataTypeController::~FrontendDataTypeController() {
154  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
155}
156
157bool FrontendDataTypeController::StartModels() {
158  DCHECK_EQ(state_, MODEL_STARTING);
159  // By default, no additional services need to be started before we can proceed
160  // with model association.
161  return true;
162}
163
164bool FrontendDataTypeController::Associate() {
165  DCHECK_EQ(state_, ASSOCIATING);
166  syncer::SyncMergeResult local_merge_result(type());
167  syncer::SyncMergeResult syncer_merge_result(type());
168  CreateSyncComponents();
169  if (!model_associator()->CryptoReadyIfNecessary()) {
170    StartDone(NEEDS_CRYPTO, local_merge_result, syncer_merge_result);
171    return false;
172  }
173
174  bool sync_has_nodes = false;
175  if (!model_associator()->SyncModelHasUserCreatedNodes(&sync_has_nodes)) {
176    syncer::SyncError error(FROM_HERE,
177                            syncer::SyncError::UNRECOVERABLE_ERROR,
178                            "Failed to load sync nodes",
179                            type());
180    local_merge_result.set_error(error);
181    StartDone(UNRECOVERABLE_ERROR, local_merge_result, syncer_merge_result);
182    return false;
183  }
184
185  // TODO(zea): Have AssociateModels fill the local and syncer merge results.
186  base::TimeTicks start_time = base::TimeTicks::Now();
187  syncer::SyncError error;
188  error = model_associator()->AssociateModels(
189      &local_merge_result,
190      &syncer_merge_result);
191  // TODO(lipalani): crbug.com/122690 - handle abort.
192  RecordAssociationTime(base::TimeTicks::Now() - start_time);
193  if (error.IsSet()) {
194    local_merge_result.set_error(error);
195    StartDone(ASSOCIATION_FAILED, local_merge_result, syncer_merge_result);
196    return false;
197  }
198
199  sync_service_->ActivateDataType(type(), model_safe_group(),
200                                  change_processor());
201  state_ = RUNNING;
202  // FinishStart() invokes the DataTypeManager callback, which can lead to a
203  // call to Stop() if one of the other data types being started generates an
204  // error.
205  StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK,
206            local_merge_result,
207            syncer_merge_result);
208  // Return false if we're not in the RUNNING state (due to Stop() being called
209  // from FinishStart()).
210  // TODO(zea/atwilson): Should we maybe move the call to FinishStart() out of
211  // Associate() and into Start(), so we don't need this logic here? It seems
212  // cleaner to call FinishStart() from Start().
213  return state_ == RUNNING;
214}
215
216void FrontendDataTypeController::CleanUpState() {
217  // Do nothing by default.
218}
219
220void FrontendDataTypeController::CleanUp() {
221  CleanUpState();
222  set_model_associator(NULL);
223  change_processor_.reset();
224}
225
226void FrontendDataTypeController::AbortModelLoad() {
227  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228  CleanUp();
229  state_ = NOT_RUNNING;
230  ModelLoadCallback model_load_callback = model_load_callback_;
231  model_load_callback_.Reset();
232  model_load_callback.Run(type(),
233                          syncer::SyncError(FROM_HERE,
234                                            syncer::SyncError::DATATYPE_ERROR,
235                                            "Aborted",
236                                            type()));
237}
238
239void FrontendDataTypeController::StartDone(
240    StartResult start_result,
241    const syncer::SyncMergeResult& local_merge_result,
242    const syncer::SyncMergeResult& syncer_merge_result) {
243  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244  if (!IsSuccessfulResult(start_result)) {
245    if (IsUnrecoverableResult(start_result))
246      RecordUnrecoverableError(FROM_HERE, "StartFailed");
247
248    CleanUp();
249    if (start_result == ASSOCIATION_FAILED) {
250      state_ = DISABLED;
251    } else {
252      state_ = NOT_RUNNING;
253    }
254    RecordStartFailure(start_result);
255  }
256
257  // We have to release the callback before we call it, since it's possible
258  // invoking the callback will trigger a call to STOP(), which will get
259  // confused by the non-NULL start_callback_.
260  StartCallback callback = start_callback_;
261  start_callback_.Reset();
262  callback.Run(start_result, local_merge_result, syncer_merge_result);
263}
264
265void FrontendDataTypeController::RecordAssociationTime(base::TimeDelta time) {
266  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
267#define PER_DATA_TYPE_MACRO(type_str) \
268    UMA_HISTOGRAM_TIMES("Sync." type_str "AssociationTime", time);
269  SYNC_DATA_TYPE_HISTOGRAM(type());
270#undef PER_DATA_TYPE_MACRO
271}
272
273void FrontendDataTypeController::RecordStartFailure(StartResult result) {
274  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
275  UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures",
276                            ModelTypeToHistogramInt(type()),
277                            syncer::MODEL_TYPE_COUNT);
278#define PER_DATA_TYPE_MACRO(type_str) \
279    UMA_HISTOGRAM_ENUMERATION("Sync." type_str "StartFailure", result, \
280                              MAX_START_RESULT);
281  SYNC_DATA_TYPE_HISTOGRAM(type());
282#undef PER_DATA_TYPE_MACRO
283}
284
285AssociatorInterface* FrontendDataTypeController::model_associator() const {
286  return model_associator_.get();
287}
288
289void FrontendDataTypeController::set_model_associator(
290    AssociatorInterface* model_associator) {
291  model_associator_.reset(model_associator);
292}
293
294ChangeProcessor* FrontendDataTypeController::change_processor() const {
295  return change_processor_.get();
296}
297
298void FrontendDataTypeController::set_change_processor(
299    ChangeProcessor* change_processor) {
300  change_processor_.reset(change_processor);
301}
302
303}  // namespace browser_sync
304