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