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 20 namespace browser_sync { 21 22 class 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 48 TypedUrlDataTypeController::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 65 TypedUrlDataTypeController::~TypedUrlDataTypeController() { 66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 67 } 68 69 void 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 94 void 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). 112 void 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 147 bool TypedUrlDataTypeController::enabled() { 148 return true; 149 } 150 151 syncable::ModelType TypedUrlDataTypeController::type() const { 152 return syncable::TYPED_URLS; 153 } 154 155 browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group() 156 const { 157 return browser_sync::GROUP_HISTORY; 158 } 159 160 std::string TypedUrlDataTypeController::name() const { 161 // For logging only. 162 return "typed_url"; 163 } 164 165 DataTypeController::State TypedUrlDataTypeController::state() const { 166 return state_; 167 } 168 169 void 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 212 void 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 229 void 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 245 void 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 257 void TypedUrlDataTypeController::StartFailed(StartResult result) { 258 change_processor_.reset(); 259 model_associator_.reset(); 260 StartDone(result, NOT_RUNNING); 261 } 262 263 void 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 273 void 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