// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/sync/backend_migrator.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/tracked_objects.h" #include "chrome/browser/sync/internal_api/configure_reason.h" #include "chrome/browser/sync/internal_api/read_transaction.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "sync/protocol/sync.pb.h" #include "sync/sessions/session_state.h" using syncable::ModelTypeSet; namespace browser_sync { using sessions::SyncSessionSnapshot; using syncable::ModelTypeToString; MigrationObserver::~MigrationObserver() {} BackendMigrator::BackendMigrator(const std::string& name, sync_api::UserShare* user_share, ProfileSyncService* service, DataTypeManager* manager) : name_(name), user_share_(user_share), service_(service), manager_(manager), state_(IDLE), weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE, content::Source(manager_)); } BackendMigrator::~BackendMigrator() { } // Helper macros to log with the syncer thread name; useful when there // are multiple syncer threads involved. #define SLOG(severity) LOG(severity) << name_ << ": " #define SDVLOG(verbose_level) DVLOG(verbose_level) << name_ << ": " void BackendMigrator::MigrateTypes(syncable::ModelTypeSet types) { const ModelTypeSet old_to_migrate = to_migrate_; to_migrate_.PutAll(types); SDVLOG(1) << "MigrateTypes called with " << ModelTypeSetToString(types) << ", old_to_migrate = " << ModelTypeSetToString(old_to_migrate) << ", to_migrate_ = " << ModelTypeSetToString(to_migrate_); if (old_to_migrate.Equals(to_migrate_)) { SDVLOG(1) << "MigrateTypes called with no new types; ignoring"; return; } if (state_ == IDLE) ChangeState(WAITING_TO_START); if (state_ == WAITING_TO_START) { if (!TryStart()) SDVLOG(1) << "Manager not configured; waiting"; return; } DCHECK_GT(state_, WAITING_TO_START); // If we're already migrating, interrupt the current migration. RestartMigration(); } void BackendMigrator::AddMigrationObserver(MigrationObserver* observer) { migration_observers_.AddObserver(observer); } bool BackendMigrator::HasMigrationObserver( MigrationObserver* observer) const { return migration_observers_.HasObserver(observer); } void BackendMigrator::RemoveMigrationObserver(MigrationObserver* observer) { migration_observers_.RemoveObserver(observer); } void BackendMigrator::ChangeState(State new_state) { state_ = new_state; FOR_EACH_OBSERVER(MigrationObserver, migration_observers_, OnMigrationStateChange()); } bool BackendMigrator::TryStart() { DCHECK_EQ(state_, WAITING_TO_START); if (manager_->state() == DataTypeManager::CONFIGURED) { RestartMigration(); return true; } return false; } void BackendMigrator::RestartMigration() { // We'll now disable any running types that need to be migrated. ChangeState(DISABLING_TYPES); const ModelTypeSet full_set = service_->GetPreferredDataTypes(); const ModelTypeSet difference = Difference(full_set, to_migrate_); bool configure_with_nigori = !to_migrate_.Has(syncable::NIGORI); SDVLOG(1) << "BackendMigrator disabling types " << ModelTypeSetToString(to_migrate_) << "; configuring " << ModelTypeSetToString(difference) << (configure_with_nigori ? " with nigori" : " without nigori"); // Add nigori for config or not based upon if the server told us to migrate // nigori or not. if (configure_with_nigori) { manager_->Configure(difference, sync_api::CONFIGURE_REASON_MIGRATION); } else { manager_->ConfigureWithoutNigori(difference, sync_api::CONFIGURE_REASON_MIGRATION); } } void BackendMigrator::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(chrome::NOTIFICATION_SYNC_CONFIGURE_DONE, type); if (state_ == IDLE) return; // |manager_|'s methods aren't re-entrant, and we're notified from // them, so post a task to avoid problems. SDVLOG(1) << "Posting OnConfigureDone from Observer"; MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&BackendMigrator::OnConfigureDone, weak_ptr_factory_.GetWeakPtr(), *content::Details( details).ptr())); } namespace { syncable::ModelTypeSet GetUnsyncedDataTypes(sync_api::UserShare* user_share) { sync_api::ReadTransaction trans(FROM_HERE, user_share); syncable::ModelTypeSet unsynced_data_types; for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType type = syncable::ModelTypeFromInt(i); sync_pb::DataTypeProgressMarker progress_marker; trans.GetDirectory()->GetDownloadProgress(type, &progress_marker); if (progress_marker.token().empty()) { unsynced_data_types.Put(type); } } return unsynced_data_types; } } // namespace void BackendMigrator::OnConfigureDone( const DataTypeManager::ConfigureResult& result) { SDVLOG(1) << "OnConfigureDone with requested types " << ModelTypeSetToString(result.requested_types) << ", status " << result.status << ", and to_migrate_ = " << ModelTypeSetToString(to_migrate_); if (state_ == WAITING_TO_START) { if (!TryStart()) SDVLOG(1) << "Manager still not configured; still waiting"; return; } DCHECK_GT(state_, WAITING_TO_START); const ModelTypeSet intersection = Intersection(result.requested_types, to_migrate_); // This intersection check is to determine if our disable request // was interrupted by a user changing preferred types. if (state_ == DISABLING_TYPES && !intersection.Empty()) { SDVLOG(1) << "Disable request interrupted by user changing types"; RestartMigration(); return; } if (result.status != DataTypeManager::OK) { // If this fails, and we're disabling types, a type may or may not be // disabled until the user restarts the browser. If this wasn't an abort, // any failure will be reported as an unrecoverable error to the UI. If it // was an abort, then typically things are shutting down anyway. There isn't // much we can do in any case besides wait until a restart to try again. // The server will send down MIGRATION_DONE again for types needing // migration as the type will still be enabled on restart. SLOG(WARNING) << "Unable to migrate, configuration failed!"; ChangeState(IDLE); to_migrate_.Clear(); return; } if (state_ == DISABLING_TYPES) { const syncable::ModelTypeSet unsynced_types = GetUnsyncedDataTypes(user_share_); if (!unsynced_types.HasAll(to_migrate_)) { SLOG(WARNING) << "Set of unsynced types: " << syncable::ModelTypeSetToString(unsynced_types) << " does not contain types to migrate: " << syncable::ModelTypeSetToString(to_migrate_) << "; not re-enabling yet"; return; } ChangeState(REENABLING_TYPES); // Don't use |to_migrate_| for the re-enabling because the user // may have chosen to disable types during the migration. const ModelTypeSet full_set = service_->GetPreferredDataTypes(); SDVLOG(1) << "BackendMigrator re-enabling types: " << syncable::ModelTypeSetToString(full_set); manager_->Configure(full_set, sync_api::CONFIGURE_REASON_MIGRATION); } else if (state_ == REENABLING_TYPES) { // We're done! ChangeState(IDLE); SDVLOG(1) << "BackendMigrator: Migration complete for: " << syncable::ModelTypeSetToString(to_migrate_); to_migrate_.Clear(); } } BackendMigrator::State BackendMigrator::state() const { return state_; } syncable::ModelTypeSet BackendMigrator::GetPendingMigrationTypesForTest() const { return to_migrate_; } #undef SDVLOG #undef SLOG }; // namespace browser_sync