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/non_frontend_data_type_controller.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/logging.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/sync/glue/change_processor.h"
12#include "chrome/browser/sync/glue/chrome_report_unrecoverable_error.h"
13#include "chrome/browser/sync/glue/model_associator.h"
14#include "chrome/browser/sync/profile_sync_components_factory.h"
15#include "chrome/browser/sync/profile_sync_service.h"
16#include "content/public/browser/browser_thread.h"
17#include "sync/api/sync_error.h"
18#include "sync/internal_api/public/base/model_type.h"
19#include "sync/internal_api/public/util/weak_handle.h"
20#include "sync/util/data_type_histogram.h"
21
22using content::BrowserThread;
23
24namespace browser_sync {
25
26class NonFrontendDataTypeController::BackendComponentsContainer {
27 public:
28  explicit BackendComponentsContainer(
29      NonFrontendDataTypeController* controller);
30  ~BackendComponentsContainer();
31  void Run();
32  void Disconnect();
33
34 private:
35  bool CreateComponents();
36  void Associate();
37
38  // For creating components.
39  NonFrontendDataTypeController* controller_;
40  base::Lock controller_lock_;
41
42  syncer::ModelType type_;
43
44  // For returning association results to controller on UI.
45  syncer::WeakHandle<NonFrontendDataTypeController> controller_handle_;
46
47  scoped_ptr<AssociatorInterface> model_associator_;
48  scoped_ptr<ChangeProcessor> change_processor_;
49};
50
51NonFrontendDataTypeController::
52BackendComponentsContainer::BackendComponentsContainer(
53    NonFrontendDataTypeController* controller)
54     : controller_(controller),
55       type_(controller->type()) {
56  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
57  controller_handle_ =
58      syncer::MakeWeakHandle(controller_->weak_ptr_factory_.GetWeakPtr());
59}
60
61NonFrontendDataTypeController::
62BackendComponentsContainer::~BackendComponentsContainer() {
63  if (model_associator_)
64    model_associator_->DisassociateModels();
65}
66
67void NonFrontendDataTypeController::BackendComponentsContainer::Run() {
68  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
69  if (CreateComponents())
70    Associate();
71}
72
73bool
74NonFrontendDataTypeController::BackendComponentsContainer::CreateComponents() {
75  base::AutoLock al(controller_lock_);
76  if (!controller_) {
77    DVLOG(1) << "Controller was stopped before sync components are created.";
78    return false;
79  }
80
81  ProfileSyncComponentsFactory::SyncComponents sync_components =
82      controller_->CreateSyncComponents();
83  model_associator_.reset(sync_components.model_associator);
84  change_processor_.reset(sync_components.change_processor);
85  return true;
86}
87
88void NonFrontendDataTypeController::BackendComponentsContainer::Associate() {
89  CHECK(model_associator_);
90
91  bool succeeded = false;
92
93  browser_sync::NonFrontendDataTypeController::AssociationResult result(type_);
94  if (!model_associator_->CryptoReadyIfNecessary()) {
95    result.needs_crypto = true;
96  } else {
97    base::TimeTicks start_time = base::TimeTicks::Now();
98
99    if (!model_associator_->SyncModelHasUserCreatedNodes(
100        &result.sync_has_nodes)) {
101      result.unrecoverable_error = true;
102      result.error = syncer::SyncError(FROM_HERE,
103                                       syncer::SyncError::UNRECOVERABLE_ERROR,
104                                       "Failed to load sync nodes",
105                                       type_);
106    } else {
107      result.error = model_associator_->AssociateModels(
108          &result.local_merge_result, &result.syncer_merge_result);
109
110      // Return components to frontend when no error.
111      if (!result.error.IsSet()) {
112        succeeded = true;
113        result.change_processor = change_processor_.get();
114        result.model_associator = model_associator_.get();
115      }
116    }
117    result.association_time = base::TimeTicks::Now() - start_time;
118  }
119  result.local_merge_result.set_error(result.error);
120
121  // Destroy processor/associator on backend on failure.
122  if (!succeeded) {
123    base::AutoLock al(controller_lock_);
124    model_associator_->DisassociateModels();
125    change_processor_.reset();
126    model_associator_.reset();
127  }
128
129  controller_handle_.Call(
130      FROM_HERE,
131      &browser_sync::NonFrontendDataTypeController::AssociationCallback,
132      result);
133}
134
135void NonFrontendDataTypeController::BackendComponentsContainer::Disconnect() {
136  base::AutoLock al(controller_lock_);
137  CHECK(controller_);
138
139  if (change_processor_)
140    controller_->DisconnectProcessor(change_processor_.get());
141  if (model_associator_)
142    model_associator_->AbortAssociation();
143
144  controller_ = NULL;
145}
146
147NonFrontendDataTypeController::AssociationResult::AssociationResult(
148    syncer::ModelType type)
149    : needs_crypto(false),
150      unrecoverable_error(false),
151      sync_has_nodes(false),
152      local_merge_result(type),
153      syncer_merge_result(type),
154      change_processor(NULL),
155      model_associator(NULL) {}
156
157NonFrontendDataTypeController::AssociationResult::~AssociationResult() {}
158
159NonFrontendDataTypeController::NonFrontendDataTypeController(
160    ProfileSyncComponentsFactory* profile_sync_factory,
161    Profile* profile,
162    ProfileSyncService* sync_service)
163    : state_(NOT_RUNNING),
164      profile_sync_factory_(profile_sync_factory),
165      profile_(profile),
166      profile_sync_service_(sync_service),
167      model_associator_(NULL),
168      change_processor_(NULL),
169      weak_ptr_factory_(this) {
170  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171  DCHECK(profile_sync_factory_);
172  DCHECK(profile_);
173  DCHECK(profile_sync_service_);
174}
175
176void NonFrontendDataTypeController::LoadModels(
177    const ModelLoadCallback& model_load_callback) {
178  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179  DCHECK(!model_load_callback.is_null());
180  if (state_ != NOT_RUNNING) {
181    model_load_callback.Run(type(),
182                            syncer::SyncError(FROM_HERE,
183                                              syncer::SyncError::DATATYPE_ERROR,
184                                              "Model already loaded",
185                                              type()));
186    return;
187  }
188
189  state_ = MODEL_STARTING;
190  if (!StartModels()) {
191    // We failed to start the models. There is no point in waiting.
192    // Note: This code is deprecated. The only 2 datatypes here,
193    // passwords and typed urls, dont have any special loading. So if we
194    // get a false it means they failed.
195    DCHECK(state_ == NOT_RUNNING || state_ == MODEL_STARTING
196           || state_ == DISABLED);
197    model_load_callback.Run(type(),
198                            syncer::SyncError(FROM_HERE,
199                                              syncer::SyncError::DATATYPE_ERROR,
200                                              "Failed loading",
201                                              type()));
202    return;
203  }
204  state_ = MODEL_LOADED;
205
206  model_load_callback.Run(type(), syncer::SyncError());
207}
208
209void NonFrontendDataTypeController::OnModelLoaded() {
210  NOTREACHED();
211}
212
213void NonFrontendDataTypeController::StartAssociating(
214    const StartCallback& start_callback) {
215  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
216  DCHECK(!start_callback.is_null());
217  DCHECK(!components_container_);
218  DCHECK_EQ(state_, MODEL_LOADED);
219
220  // Kick off association on the thread the datatype resides on.
221  state_ = ASSOCIATING;
222  start_callback_ = start_callback;
223
224  components_container_.reset(new BackendComponentsContainer(this));
225
226  if (!PostTaskOnBackendThread(
227      FROM_HERE,
228      base::Bind(&BackendComponentsContainer::Run,
229                 base::Unretained(components_container_.get())))) {
230    syncer::SyncError error(
231        FROM_HERE,
232        syncer::SyncError::DATATYPE_ERROR,
233        "Failed to post StartAssociation", type());
234    syncer::SyncMergeResult local_merge_result(type());
235    local_merge_result.set_error(error);
236    StartDone(ASSOCIATION_FAILED,
237              local_merge_result,
238              syncer::SyncMergeResult(type()));
239  }
240}
241
242void DestroyComponentsInBackend(
243    NonFrontendDataTypeController::BackendComponentsContainer *containter) {
244  delete containter;
245}
246
247void NonFrontendDataTypeController::Stop() {
248  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249  DCHECK_NE(state_, NOT_RUNNING);
250
251  // Deactivate the date type on the UI thread first to stop processing
252  // sync server changes. This needs to happen before posting task to destroy
253  // processor and associator on backend. Otherwise it could crash if syncer
254  // post work to backend after destruction task and that work is run before
255  // deactivation.
256  profile_sync_service()->DeactivateDataType(type());
257
258  // Ignore association callback.
259  weak_ptr_factory_.InvalidateWeakPtrs();
260
261  // Disconnect on UI and post task to destroy on backend.
262  if (components_container_) {
263    components_container_->Disconnect();
264    PostTaskOnBackendThread(
265          FROM_HERE,
266          base::Bind(&DestroyComponentsInBackend,
267                     components_container_.release()));
268    model_associator_ = NULL;
269    change_processor_ = NULL;
270  }
271
272  // Call start callback if waiting for association.
273  if (state_ == ASSOCIATING) {
274    StartDone(ABORTED,
275              syncer::SyncMergeResult(type()),
276              syncer::SyncMergeResult(type()));
277
278  }
279
280  state_ = NOT_RUNNING;
281}
282
283std::string NonFrontendDataTypeController::name() const {
284  // For logging only.
285  return syncer::ModelTypeToString(type());
286}
287
288DataTypeController::State NonFrontendDataTypeController::state() const {
289  return state_;
290}
291
292void NonFrontendDataTypeController::OnSingleDatatypeUnrecoverableError(
293    const tracked_objects::Location& from_here,
294    const std::string& message) {
295  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
296  RecordUnrecoverableError(from_here, message);
297  BrowserThread::PostTask(BrowserThread::UI, from_here,
298      base::Bind(&NonFrontendDataTypeController::DisableImpl,
299                 this,
300                 from_here,
301                 message));
302}
303
304NonFrontendDataTypeController::NonFrontendDataTypeController()
305    : state_(NOT_RUNNING),
306      profile_sync_factory_(NULL),
307      profile_(NULL),
308      profile_sync_service_(NULL),
309      model_associator_(NULL),
310      change_processor_(NULL),
311      weak_ptr_factory_(this) {
312}
313
314NonFrontendDataTypeController::~NonFrontendDataTypeController() {
315  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
316  DCHECK(!change_processor_);
317  DCHECK(!model_associator_);
318}
319
320bool NonFrontendDataTypeController::StartModels() {
321  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322  DCHECK_EQ(state_, MODEL_STARTING);
323  // By default, no additional services need to be started before we can proceed
324  // with model association, so do nothing.
325  return true;
326}
327
328void NonFrontendDataTypeController::StartDone(
329    DataTypeController::StartResult start_result,
330    const syncer::SyncMergeResult& local_merge_result,
331    const syncer::SyncMergeResult& syncer_merge_result) {
332  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
333  DataTypeController::State new_state;
334
335  if (IsSuccessfulResult(start_result)) {
336    new_state = RUNNING;
337  } else {
338    new_state = (start_result == ASSOCIATION_FAILED ? DISABLED : NOT_RUNNING);
339    if (IsUnrecoverableResult(start_result))
340      RecordUnrecoverableError(FROM_HERE, "StartFailed");
341  }
342
343  StartDoneImpl(start_result, new_state, local_merge_result,
344                syncer_merge_result);
345}
346
347void NonFrontendDataTypeController::StartDoneImpl(
348    DataTypeController::StartResult start_result,
349    DataTypeController::State new_state,
350    const syncer::SyncMergeResult& local_merge_result,
351    const syncer::SyncMergeResult& syncer_merge_result) {
352  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353
354  state_ = new_state;
355  if (state_ != RUNNING) {
356    // Start failed.
357    RecordStartFailure(start_result);
358  }
359
360  DCHECK(!start_callback_.is_null());
361  // We have to release the callback before we call it, since it's possible
362  // invoking the callback will trigger a call to STOP(), which will get
363  // confused by the non-NULL start_callback_.
364  StartCallback callback = start_callback_;
365  start_callback_.Reset();
366  callback.Run(start_result, local_merge_result, syncer_merge_result);
367}
368
369void NonFrontendDataTypeController::DisableImpl(
370    const tracked_objects::Location& from_here,
371    const std::string& message) {
372  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373  profile_sync_service_->DisableBrokenDatatype(type(), from_here, message);
374}
375
376void NonFrontendDataTypeController::RecordAssociationTime(
377    base::TimeDelta time) {
378  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
379#define PER_DATA_TYPE_MACRO(type_str) \
380    UMA_HISTOGRAM_TIMES("Sync." type_str "AssociationTime", time);
381  SYNC_DATA_TYPE_HISTOGRAM(type());
382#undef PER_DATA_TYPE_MACRO
383}
384
385void NonFrontendDataTypeController::RecordStartFailure(StartResult result) {
386  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
387  UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures",
388                            ModelTypeToHistogramInt(type()),
389                            syncer::MODEL_TYPE_COUNT);
390#define PER_DATA_TYPE_MACRO(type_str) \
391    UMA_HISTOGRAM_ENUMERATION("Sync." type_str "StartFailure", result, \
392                              MAX_START_RESULT);
393  SYNC_DATA_TYPE_HISTOGRAM(type());
394#undef PER_DATA_TYPE_MACRO
395}
396
397ProfileSyncComponentsFactory*
398    NonFrontendDataTypeController::profile_sync_factory() const {
399  return profile_sync_factory_;
400}
401
402Profile* NonFrontendDataTypeController::profile() const {
403  return profile_;
404}
405
406ProfileSyncService* NonFrontendDataTypeController::profile_sync_service()
407    const {
408  return profile_sync_service_;
409}
410
411void NonFrontendDataTypeController::set_start_callback(
412    const StartCallback& callback) {
413  start_callback_ = callback;
414}
415
416void NonFrontendDataTypeController::set_state(State state) {
417  state_ = state;
418}
419
420AssociatorInterface* NonFrontendDataTypeController::associator() const {
421  return model_associator_;
422}
423
424ChangeProcessor* NonFrontendDataTypeController::change_processor() const {
425  return change_processor_;
426}
427
428void NonFrontendDataTypeController::AssociationCallback(
429    AssociationResult result) {
430  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
431
432  if (result.needs_crypto) {
433    StartDone(NEEDS_CRYPTO,
434              result.local_merge_result,
435              result.syncer_merge_result);
436    return;
437  }
438
439  if (result.unrecoverable_error) {
440    StartDone(UNRECOVERABLE_ERROR,
441              result.local_merge_result,
442              result.syncer_merge_result);
443    return;
444  }
445
446  RecordAssociationTime(result.association_time);
447  if (result.error.IsSet()) {
448    StartDone(ASSOCIATION_FAILED,
449              result.local_merge_result,
450              result.syncer_merge_result);
451    return;
452  }
453
454  CHECK(result.change_processor);
455  CHECK(result.model_associator);
456  change_processor_ = result.change_processor;
457  model_associator_ = result.model_associator;
458
459  profile_sync_service_->ActivateDataType(type(), model_safe_group(),
460                                          change_processor());
461  StartDone(!result.sync_has_nodes ? OK_FIRST_RUN : OK,
462            result.local_merge_result,
463            result.syncer_merge_result);
464}
465
466}  // namespace browser_sync
467