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