1// Copyright (c) 2011 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/backend_migrator.h"
6
7#include <algorithm>
8
9#include "base/string_number_conversions.h"
10#include "chrome/browser/sync/profile_sync_service.h"
11#include "chrome/browser/sync/glue/data_type_manager.h"
12#include "chrome/browser/sync/sessions/session_state.h"
13#include "content/browser/browser_thread.h"
14#include "content/common/notification_details.h"
15#include "content/common/notification_source.h"
16
17using syncable::ModelTypeSet;
18
19namespace browser_sync {
20
21using sessions::SyncSessionSnapshot;
22
23BackendMigrator::BackendMigrator(ProfileSyncService* service,
24                                 DataTypeManager* manager)
25    : state_(IDLE), service_(service), manager_(manager),
26      restart_migration_(false),
27      method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
28  registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE,
29                 Source<DataTypeManager>(manager_));
30  service_->AddObserver(this);
31}
32
33BackendMigrator::~BackendMigrator() {
34  service_->RemoveObserver(this);
35}
36
37bool BackendMigrator::HasStartedMigrating() const {
38  return state_ >= DISABLING_TYPES;
39}
40
41void BackendMigrator::MigrateTypes(const syncable::ModelTypeSet& types) {
42  {
43    ModelTypeSet temp;
44    std::set_union(to_migrate_.begin(), to_migrate_.end(),
45                   types.begin(), types.end(),
46                   std::inserter(temp, temp.end()));
47    to_migrate_ = temp;
48  }
49
50  if (HasStartedMigrating()) {
51    VLOG(1) << "BackendMigrator::MigrateTypes: STARTED_MIGRATING early-out.";
52    restart_migration_ = true;
53    return;
54  }
55
56  if (manager_->state() != DataTypeManager::CONFIGURED) {
57    VLOG(1) << "BackendMigrator::MigrateTypes: manager CONFIGURED early-out.";
58    state_ = WAITING_TO_START;
59    return;
60  }
61
62  // We'll now disable any running types that need to be migrated.
63  state_ = DISABLING_TYPES;
64  ModelTypeSet full_set;
65  service_->GetPreferredDataTypes(&full_set);
66  ModelTypeSet difference;
67  std::set_difference(full_set.begin(), full_set.end(),
68                      to_migrate_.begin(), to_migrate_.end(),
69                      std::inserter(difference, difference.end()));
70  VLOG(1) << "BackendMigrator disabling types; calling Configure.";
71  manager_->Configure(difference);
72}
73
74void BackendMigrator::OnStateChanged() {
75  if (restart_migration_ == true) {
76    VLOG(1) << "BackendMigrator restarting migration in OnStateChanged.";
77    state_ = WAITING_TO_START;
78    restart_migration_ = false;
79    MigrateTypes(to_migrate_);
80    return;
81  }
82
83  if (state_ != WAITING_FOR_PURGE)
84    return;
85
86  size_t num_empty_migrated_markers = 0;
87  const SyncSessionSnapshot* snap = service_->GetLastSessionSnapshot();
88  for (ModelTypeSet::const_iterator it = to_migrate_.begin();
89       it != to_migrate_.end(); ++it) {
90    if (snap->download_progress_markers[*it].empty())
91      num_empty_migrated_markers++;
92  }
93
94  if (num_empty_migrated_markers < to_migrate_.size())
95    return;
96
97  state_ = REENABLING_TYPES;
98  ModelTypeSet full_set;
99  service_->GetPreferredDataTypes(&full_set);
100  VLOG(1) << "BackendMigrator re-enabling types.";
101  // Don't use |to_migrate_| for the re-enabling because the user may have
102  // chosen to disable types during the migration.
103  manager_->Configure(full_set);
104}
105
106void BackendMigrator::Observe(NotificationType type,
107                              const NotificationSource& source,
108                              const NotificationDetails& details) {
109  DCHECK_EQ(NotificationType::SYNC_CONFIGURE_DONE, type.value);
110  if (state_ == IDLE)
111    return;
112
113  DataTypeManager::ConfigureResultWithErrorLocation* result =
114      Details<DataTypeManager::ConfigureResultWithErrorLocation>(
115          details).ptr();
116
117  ModelTypeSet intersection;
118  std::set_intersection(result->requested_types.begin(),
119      result->requested_types.end(), to_migrate_.begin(), to_migrate_.end(),
120      std::inserter(intersection, intersection.end()));
121
122  // The intersection check is to determine if our disable request was
123  // interrupted by a user changing preferred types.  May still need to purge.
124  // It's pretty wild if we're in WAITING_FOR_PURGE here, because it would mean
125  // that after our disable-config finished but before the purge, another config
126  // was posted externally _and completed_, which means somehow the nudge to
127  // purge was dropped, yet nudges are reliable.
128  if (state_ == WAITING_TO_START || state_ == WAITING_FOR_PURGE ||
129      (state_ == DISABLING_TYPES && !intersection.empty())) {
130    state_ = WAITING_TO_START;
131    restart_migration_ = false;
132    VLOG(1) << "BackendMigrator::Observe posting MigrateTypes.";
133    if (!BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
134        method_factory_.NewRunnableMethod(&BackendMigrator::MigrateTypes,
135                                          to_migrate_))) {
136      // Unittests need this.
137      // TODO(tim): Clean this up.
138      MigrateTypes(to_migrate_);
139    }
140    return;
141  }
142
143  if (result->result != DataTypeManager::OK) {
144    // If this fails, and we're disabling types, a type may or may not be
145    // disabled until the user restarts the browser.  If this wasn't an abort,
146    // any failure will be reported as an unrecoverable error to the UI. If it
147    // was an abort, then typically things are shutting down anyway. There isn't
148    // much we can do in any case besides wait until a restart to try again.
149    // The server will send down MIGRATION_DONE again for types needing
150    // migration as the type will still be enabled on restart.
151    LOG(WARNING) << "Unable to migrate, configuration failed!";
152    state_ = IDLE;
153    to_migrate_.clear();
154    return;
155  }
156
157  if (state_ == DISABLING_TYPES) {
158    state_ = WAITING_FOR_PURGE;
159    VLOG(1) << "BackendMigrator waiting for purge.";
160  } else if (state_ == REENABLING_TYPES) {
161    // We're done!
162    state_ = IDLE;
163
164    std::stringstream ss;
165    std::copy(to_migrate_.begin(), to_migrate_.end(),
166              std::ostream_iterator<syncable::ModelType>(ss, ","));
167    VLOG(1) << "BackendMigrator: Migration complete for: " << ss.str();
168    to_migrate_.clear();
169  }
170}
171
172BackendMigrator::State BackendMigrator::state() const {
173  return state_;
174}
175
176};  // namespace browser_sync
177