1// Copyright (c) 2011 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/typed_url_data_type_controller.h"
6
7#include "base/logging.h"
8#include "base/metrics/histogram.h"
9#include "base/task.h"
10#include "base/time.h"
11#include "chrome/browser/history/history.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/sync/glue/typed_url_change_processor.h"
14#include "chrome/browser/sync/glue/typed_url_model_associator.h"
15#include "chrome/browser/sync/profile_sync_factory.h"
16#include "chrome/browser/sync/profile_sync_service.h"
17#include "content/browser/browser_thread.h"
18#include "content/common/notification_service.h"
19
20namespace browser_sync {
21
22class ControlTask : public HistoryDBTask {
23 public:
24  ControlTask(TypedUrlDataTypeController* controller, bool start)
25    : controller_(controller), start_(start) {}
26
27  virtual bool RunOnDBThread(history::HistoryBackend* backend,
28                             history::HistoryDatabase* db) {
29    if (start_) {
30      controller_->StartImpl(backend);
31    } else {
32      controller_->StopImpl();
33    }
34
35    // Release the reference to the controller.  This ensures that
36    // the controller isn't held past its lifetime in unit tests.
37    controller_ = NULL;
38    return true;
39  }
40
41  virtual void DoneRunOnMainThread() {}
42
43 protected:
44  scoped_refptr<TypedUrlDataTypeController> controller_;
45  bool start_;
46};
47
48TypedUrlDataTypeController::TypedUrlDataTypeController(
49    ProfileSyncFactory* profile_sync_factory,
50    Profile* profile,
51    ProfileSyncService* sync_service)
52    : profile_sync_factory_(profile_sync_factory),
53      profile_(profile),
54      sync_service_(sync_service),
55      state_(NOT_RUNNING),
56      abort_association_(false),
57      abort_association_complete_(false, false),
58      datatype_stopped_(false, false) {
59  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
60  DCHECK(profile_sync_factory);
61  DCHECK(profile);
62  DCHECK(sync_service);
63}
64
65TypedUrlDataTypeController::~TypedUrlDataTypeController() {
66  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
67}
68
69void TypedUrlDataTypeController::Start(StartCallback* start_callback) {
70  VLOG(1) << "Starting typed_url data controller.";
71  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
72  DCHECK(start_callback);
73  if (state_ != NOT_RUNNING || start_callback_.get()) {
74    start_callback->Run(BUSY, FROM_HERE);
75    delete start_callback;
76    return;
77  }
78
79  start_callback_.reset(start_callback);
80  abort_association_ = false;
81
82  HistoryService* history = profile_->GetHistoryServiceWithoutCreating();
83  if (history) {
84    set_state(ASSOCIATING);
85    history_service_ = history;
86    history_service_->ScheduleDBTask(new ControlTask(this, true), this);
87  } else {
88    set_state(MODEL_STARTING);
89    notification_registrar_.Add(this, NotificationType::HISTORY_LOADED,
90                                NotificationService::AllSources());
91  }
92}
93
94void TypedUrlDataTypeController::Observe(NotificationType type,
95                                         const NotificationSource& source,
96                                         const NotificationDetails& details) {
97  VLOG(1) << "History loaded observed.";
98  notification_registrar_.Remove(this,
99                                 NotificationType::HISTORY_LOADED,
100                                 NotificationService::AllSources());
101
102  history_service_ = profile_->GetHistoryServiceWithoutCreating();
103  DCHECK(history_service_.get());
104  history_service_->ScheduleDBTask(new ControlTask(this, true), this);
105}
106
107// TODO(sync): Blocking the UI thread at shutdown is bad. If we had a way of
108// distinguishing chrome shutdown from sync shutdown, we should be able to avoid
109// this (http://crbug.com/55662). Further, all this functionality should be
110// abstracted to a higher layer, where we could ensure all datatypes are doing
111// the same thing (http://crbug.com/76232).
112void TypedUrlDataTypeController::Stop() {
113  VLOG(1) << "Stopping typed_url data type controller.";
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115
116  // If Stop() is called while Start() is waiting for association to
117  // complete, we need to abort the association and wait for the DB
118  // thread to finish the StartImpl() task.
119  if (state_ == ASSOCIATING) {
120    {
121      base::AutoLock lock(abort_association_lock_);
122      abort_association_ = true;
123      if (model_associator_.get())
124        model_associator_->AbortAssociation();
125    }
126    // Wait for the model association to abort.
127    abort_association_complete_.Wait();
128    StartDoneImpl(ABORTED, STOPPING);
129  }
130
131  // If Stop() is called while Start() is waiting for the history service to
132  // load, abort the start.
133  if (state_ == MODEL_STARTING)
134    StartDoneImpl(ABORTED, STOPPING);
135
136  DCHECK(!start_callback_.get());
137
138  if (change_processor_ != NULL)
139    sync_service_->DeactivateDataType(this, change_processor_.get());
140
141  set_state(NOT_RUNNING);
142  DCHECK(history_service_.get());
143  history_service_->ScheduleDBTask(new ControlTask(this, false), this);
144  datatype_stopped_.Wait();
145}
146
147bool TypedUrlDataTypeController::enabled() {
148  return true;
149}
150
151syncable::ModelType TypedUrlDataTypeController::type() const {
152  return syncable::TYPED_URLS;
153}
154
155browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group()
156    const {
157  return browser_sync::GROUP_HISTORY;
158}
159
160std::string TypedUrlDataTypeController::name() const {
161  // For logging only.
162  return "typed_url";
163}
164
165DataTypeController::State TypedUrlDataTypeController::state() const {
166  return state_;
167}
168
169void TypedUrlDataTypeController::StartImpl(history::HistoryBackend* backend) {
170  VLOG(1) << "TypedUrl data type controller StartImpl called.";
171  // No additional services need to be started before we can proceed
172  // with model association.
173  {
174    base::AutoLock lock(abort_association_lock_);
175    if (abort_association_) {
176      abort_association_complete_.Signal();
177      return;
178    }
179    ProfileSyncFactory::SyncComponents sync_components =
180        profile_sync_factory_->CreateTypedUrlSyncComponents(
181            sync_service_,
182            backend,
183            this);
184    model_associator_.reset(sync_components.model_associator);
185    change_processor_.reset(sync_components.change_processor);
186  }
187
188  if (!model_associator_->CryptoReadyIfNecessary()) {
189    StartFailed(NEEDS_CRYPTO);
190    return;
191  }
192
193  bool sync_has_nodes = false;
194  if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) {
195    StartFailed(UNRECOVERABLE_ERROR);
196    return;
197  }
198
199  base::TimeTicks start_time = base::TimeTicks::Now();
200  bool merge_success = model_associator_->AssociateModels();
201  UMA_HISTOGRAM_TIMES("Sync.TypedUrlAssociationTime",
202                      base::TimeTicks::Now() - start_time);
203  if (!merge_success) {
204    StartFailed(ASSOCIATION_FAILED);
205    return;
206  }
207
208  sync_service_->ActivateDataType(this, change_processor_.get());
209  StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, RUNNING);
210}
211
212void TypedUrlDataTypeController::StartDone(
213    DataTypeController::StartResult result,
214    DataTypeController::State new_state) {
215  VLOG(1) << "TypedUrl data type controller StartDone called.";
216
217  abort_association_complete_.Signal();
218  base::AutoLock lock(abort_association_lock_);
219  if (!abort_association_) {
220    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
221                           NewRunnableMethod(
222                               this,
223                               &TypedUrlDataTypeController::StartDoneImpl,
224                               result,
225                               new_state));
226  }
227}
228
229void TypedUrlDataTypeController::StartDoneImpl(
230    DataTypeController::StartResult result,
231    DataTypeController::State new_state) {
232  VLOG(1) << "TypedUrl data type controller StartDoneImpl called.";
233  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234  set_state(new_state);
235  start_callback_->Run(result, FROM_HERE);
236  start_callback_.reset();
237
238  if (result == UNRECOVERABLE_ERROR || result == ASSOCIATION_FAILED) {
239    UMA_HISTOGRAM_ENUMERATION("Sync.TypedUrlStartFailures",
240                              result,
241                              MAX_START_RESULT);
242  }
243}
244
245void TypedUrlDataTypeController::StopImpl() {
246  VLOG(1) << "TypedUrl data type controller StopImpl called.";
247
248  if (model_associator_ != NULL)
249    model_associator_->DisassociateModels();
250
251  change_processor_.reset();
252  model_associator_.reset();
253
254  datatype_stopped_.Signal();
255}
256
257void TypedUrlDataTypeController::StartFailed(StartResult result) {
258  change_processor_.reset();
259  model_associator_.reset();
260  StartDone(result, NOT_RUNNING);
261}
262
263void TypedUrlDataTypeController::OnUnrecoverableError(
264    const tracked_objects::Location& from_here,
265    const std::string& message) {
266  BrowserThread::PostTask(
267    BrowserThread::UI, FROM_HERE,
268    NewRunnableMethod(this,
269                      &TypedUrlDataTypeController::OnUnrecoverableErrorImpl,
270                      from_here, message));
271}
272
273void TypedUrlDataTypeController::OnUnrecoverableErrorImpl(
274  const tracked_objects::Location& from_here,
275  const std::string& message) {
276  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
277  UMA_HISTOGRAM_COUNTS("Sync.TypedUrlRunFailures", 1);
278  sync_service_->OnUnrecoverableError(from_here, message);
279}
280
281}  // namespace browser_sync
282