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