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/chrome_report_unrecoverable_error.h"
12#include "chrome/browser/sync/profile_sync_components_factory.h"
13#include "chrome/browser/sync/profile_sync_service.h"
14#include "components/sync_driver/change_processor.h"
15#include "components/sync_driver/model_associator.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<sync_driver::AssociatorInterface> model_associator_;
48  scoped_ptr<sync_driver::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(controller_->IsOnBackendThread());
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
159// TODO(tim): Legacy controllers are being left behind in componentization
160// effort for now, hence  still having a dependency on ProfileSyncService.
161// That dep can probably be removed without too much work.
162NonFrontendDataTypeController::NonFrontendDataTypeController(
163    scoped_refptr<base::MessageLoopProxy> ui_thread,
164    const base::Closure& error_callback,
165    ProfileSyncComponentsFactory* profile_sync_factory,
166    Profile* profile,
167    ProfileSyncService* sync_service)
168    : DataTypeController(ui_thread, error_callback),
169      state_(NOT_RUNNING),
170      profile_sync_factory_(profile_sync_factory),
171      profile_(profile),
172      profile_sync_service_(sync_service),
173      model_associator_(NULL),
174      change_processor_(NULL),
175      weak_ptr_factory_(this) {
176  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
177  DCHECK(profile_sync_factory_);
178  DCHECK(profile_);
179  DCHECK(profile_sync_service_);
180}
181
182void NonFrontendDataTypeController::LoadModels(
183    const ModelLoadCallback& model_load_callback) {
184  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
185  model_load_callback_ = model_load_callback;
186  if (state_ != NOT_RUNNING) {
187    model_load_callback.Run(type(),
188                            syncer::SyncError(FROM_HERE,
189                                              syncer::SyncError::DATATYPE_ERROR,
190                                              "Model already loaded",
191                                              type()));
192    return;
193  }
194
195  state_ = MODEL_STARTING;
196  if (!StartModels()) {
197    // We failed to start the models. There is no point in waiting.
198    // Note: This code is deprecated. The only 2 datatypes here,
199    // passwords and typed urls, dont have any special loading. So if we
200    // get a false it means they failed.
201    DCHECK(state_ == NOT_RUNNING || state_ == MODEL_STARTING
202           || state_ == DISABLED);
203    model_load_callback.Run(type(),
204                            syncer::SyncError(FROM_HERE,
205                                              syncer::SyncError::DATATYPE_ERROR,
206                                              "Failed loading",
207                                              type()));
208    return;
209  }
210  state_ = MODEL_LOADED;
211
212  model_load_callback.Run(type(), syncer::SyncError());
213}
214
215void NonFrontendDataTypeController::OnModelLoaded() {
216  NOTREACHED();
217}
218
219void NonFrontendDataTypeController::StartAssociating(
220    const StartCallback& start_callback) {
221  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
222  DCHECK(!start_callback.is_null());
223  DCHECK(!components_container_);
224  DCHECK_EQ(state_, MODEL_LOADED);
225
226  // Kick off association on the thread the datatype resides on.
227  state_ = ASSOCIATING;
228  start_callback_ = start_callback;
229
230  components_container_.reset(new BackendComponentsContainer(this));
231
232  if (!PostTaskOnBackendThread(
233      FROM_HERE,
234      base::Bind(&BackendComponentsContainer::Run,
235                 base::Unretained(components_container_.get())))) {
236    syncer::SyncError error(
237        FROM_HERE,
238        syncer::SyncError::DATATYPE_ERROR,
239        "Failed to post StartAssociation", type());
240    syncer::SyncMergeResult local_merge_result(type());
241    local_merge_result.set_error(error);
242    StartDone(ASSOCIATION_FAILED,
243              local_merge_result,
244              syncer::SyncMergeResult(type()));
245  }
246}
247
248void DestroyComponentsInBackend(
249    NonFrontendDataTypeController::BackendComponentsContainer *container) {
250  delete container;
251}
252
253void NonFrontendDataTypeController::Stop() {
254  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255
256  if (state_ == NOT_RUNNING)
257    return;
258
259  // Deactivate the date type on the UI thread first to stop processing
260  // sync server changes. This needs to happen before posting task to destroy
261  // processor and associator on backend. Otherwise it could crash if syncer
262  // post work to backend after destruction task and that work is run before
263  // deactivation.
264  profile_sync_service()->DeactivateDataType(type());
265
266  // Ignore association callback.
267  weak_ptr_factory_.InvalidateWeakPtrs();
268
269  // Disconnect on UI and post task to destroy on backend.
270  if (components_container_) {
271    components_container_->Disconnect();
272    PostTaskOnBackendThread(
273          FROM_HERE,
274          base::Bind(&DestroyComponentsInBackend,
275                     components_container_.release()));
276    model_associator_ = NULL;
277    change_processor_ = NULL;
278  }
279
280  state_ = NOT_RUNNING;
281}
282
283std::string NonFrontendDataTypeController::name() const {
284  // For logging only.
285  return syncer::ModelTypeToString(type());
286}
287
288sync_driver::DataTypeController::State NonFrontendDataTypeController::state()
289    const {
290  return state_;
291}
292
293void NonFrontendDataTypeController::OnSingleDataTypeUnrecoverableError(
294    const syncer::SyncError& error) {
295  DCHECK(IsOnBackendThread());
296  DCHECK_EQ(type(), error.model_type());
297  RecordUnrecoverableError(error.location(), error.message());
298  BrowserThread::PostTask(BrowserThread::UI, error.location(),
299      base::Bind(&NonFrontendDataTypeController::DisableImpl,
300                 this,
301                 error));
302}
303
304NonFrontendDataTypeController::NonFrontendDataTypeController()
305    : DataTypeController(base::MessageLoopProxy::current(), base::Closure()),
306      state_(NOT_RUNNING),
307      profile_sync_factory_(NULL),
308      profile_(NULL),
309      profile_sync_service_(NULL),
310      model_associator_(NULL),
311      change_processor_(NULL),
312      weak_ptr_factory_(this) {
313}
314
315NonFrontendDataTypeController::~NonFrontendDataTypeController() {
316  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
317  DCHECK(!change_processor_);
318  DCHECK(!model_associator_);
319}
320
321bool NonFrontendDataTypeController::StartModels() {
322  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323  DCHECK_EQ(state_, MODEL_STARTING);
324  // By default, no additional services need to be started before we can proceed
325  // with model association, so do nothing.
326  return true;
327}
328
329bool NonFrontendDataTypeController::IsOnBackendThread() {
330  return !BrowserThread::CurrentlyOn(BrowserThread::UI);
331}
332
333void NonFrontendDataTypeController::StartDone(
334    DataTypeController::ConfigureResult start_result,
335    const syncer::SyncMergeResult& local_merge_result,
336    const syncer::SyncMergeResult& syncer_merge_result) {
337  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338  DataTypeController::State new_state;
339
340  if (IsSuccessfulResult(start_result)) {
341    new_state = RUNNING;
342  } else {
343    new_state = (start_result == ASSOCIATION_FAILED ? DISABLED : NOT_RUNNING);
344    if (IsUnrecoverableResult(start_result))
345      RecordUnrecoverableError(FROM_HERE, "StartFailed");
346  }
347
348  StartDoneImpl(start_result, new_state, local_merge_result,
349                syncer_merge_result);
350}
351
352void NonFrontendDataTypeController::StartDoneImpl(
353    DataTypeController::ConfigureResult start_result,
354    DataTypeController::State new_state,
355    const syncer::SyncMergeResult& local_merge_result,
356    const syncer::SyncMergeResult& syncer_merge_result) {
357  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
358
359  state_ = new_state;
360  if (state_ != RUNNING) {
361    // Start failed.
362    RecordStartFailure(start_result);
363  }
364
365  start_callback_.Run(start_result, local_merge_result, syncer_merge_result);
366}
367
368void NonFrontendDataTypeController::DisableImpl(
369    const syncer::SyncError& error) {
370  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
371  if (!model_load_callback_.is_null()) {
372    model_load_callback_.Run(type(), error);
373  }
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(ConfigureResult 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
397void NonFrontendDataTypeController::RecordUnrecoverableError(
398    const tracked_objects::Location& from_here,
399    const std::string& message) {
400  DVLOG(1) << "Datatype Controller failed for type "
401           << ModelTypeToString(type()) << "  "
402           << message << " at location "
403           << from_here.ToString();
404  UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures",
405                            ModelTypeToHistogramInt(type()),
406                            syncer::MODEL_TYPE_COUNT);
407
408  if (!error_callback_.is_null())
409    error_callback_.Run();
410}
411
412
413ProfileSyncComponentsFactory*
414    NonFrontendDataTypeController::profile_sync_factory() const {
415  return profile_sync_factory_;
416}
417
418Profile* NonFrontendDataTypeController::profile() const {
419  return profile_;
420}
421
422ProfileSyncService* NonFrontendDataTypeController::profile_sync_service()
423    const {
424  return profile_sync_service_;
425}
426
427void NonFrontendDataTypeController::set_start_callback(
428    const StartCallback& callback) {
429  start_callback_ = callback;
430}
431
432void NonFrontendDataTypeController::set_state(State state) {
433  state_ = state;
434}
435
436sync_driver::AssociatorInterface* NonFrontendDataTypeController::associator()
437    const {
438  return model_associator_;
439}
440
441sync_driver::ChangeProcessor*
442NonFrontendDataTypeController::GetChangeProcessor() const {
443  return change_processor_;
444}
445
446void NonFrontendDataTypeController::AssociationCallback(
447    AssociationResult result) {
448  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449
450  if (result.needs_crypto) {
451    StartDone(NEEDS_CRYPTO,
452              result.local_merge_result,
453              result.syncer_merge_result);
454    return;
455  }
456
457  if (result.unrecoverable_error) {
458    StartDone(UNRECOVERABLE_ERROR,
459              result.local_merge_result,
460              result.syncer_merge_result);
461    return;
462  }
463
464  RecordAssociationTime(result.association_time);
465  if (result.error.IsSet()) {
466    StartDone(ASSOCIATION_FAILED,
467              result.local_merge_result,
468              result.syncer_merge_result);
469    return;
470  }
471
472  CHECK(result.change_processor);
473  CHECK(result.model_associator);
474  change_processor_ = result.change_processor;
475  model_associator_ = result.model_associator;
476
477  StartDone(!result.sync_has_nodes ? OK_FIRST_RUN : OK,
478            result.local_merge_result,
479            result.syncer_merge_result);
480}
481
482}  // namespace browser_sync
483