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