OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/sync/glue/typed_url_data_type_controller.h" | 5 #include "chrome/browser/sync/glue/typed_url_data_type_controller.h" |
6 | 6 |
7 #include "base/logging.h" | |
8 #include "base/metrics/histogram.h" | 7 #include "base/metrics/histogram.h" |
9 #include "base/task.h" | 8 #include "base/task.h" |
10 #include "base/time.h" | |
11 #include "chrome/browser/history/history.h" | 9 #include "chrome/browser/history/history.h" |
12 #include "chrome/browser/profiles/profile.h" | 10 #include "chrome/browser/profiles/profile.h" |
13 #include "chrome/browser/sync/glue/typed_url_change_processor.h" | |
14 #include "chrome/browser/sync/glue/typed_url_model_associator.h" | |
15 #include "chrome/browser/sync/profile_sync_factory.h" | 11 #include "chrome/browser/sync/profile_sync_factory.h" |
16 #include "chrome/browser/sync/profile_sync_service.h" | 12 #include "chrome/browser/sync/profile_sync_service.h" |
17 #include "content/browser/browser_thread.h" | 13 #include "content/browser/browser_thread.h" |
18 #include "content/common/notification_service.h" | 14 #include "content/common/notification_service.h" |
19 | 15 |
20 namespace browser_sync { | 16 namespace browser_sync { |
21 | 17 |
22 class ControlTask : public HistoryDBTask { | 18 class ControlTask : public HistoryDBTask { |
23 public: | 19 public: |
24 ControlTask(TypedUrlDataTypeController* controller, bool start) | 20 ControlTask(TypedUrlDataTypeController* controller, bool start) |
25 : controller_(controller), start_(start) {} | 21 : controller_(controller), start_(start) {} |
26 | 22 |
27 virtual bool RunOnDBThread(history::HistoryBackend* backend, | 23 virtual bool RunOnDBThread(history::HistoryBackend* backend, |
28 history::HistoryDatabase* db) { | 24 history::HistoryDatabase* db) { |
29 if (start_) { | 25 controller_->RunOnHistoryThread(start_, backend); |
30 controller_->StartImpl(backend); | |
31 } else { | |
32 controller_->StopImpl(); | |
33 } | |
34 | 26 |
35 // Release the reference to the controller. This ensures that | 27 // Release the reference to the controller. This ensures that |
36 // the controller isn't held past its lifetime in unit tests. | 28 // the controller isn't held past its lifetime in unit tests. |
37 controller_ = NULL; | 29 controller_ = NULL; |
38 return true; | 30 return true; |
39 } | 31 } |
40 | 32 |
41 virtual void DoneRunOnMainThread() {} | 33 virtual void DoneRunOnMainThread() {} |
42 | 34 |
43 protected: | 35 protected: |
44 scoped_refptr<TypedUrlDataTypeController> controller_; | 36 scoped_refptr<TypedUrlDataTypeController> controller_; |
45 bool start_; | 37 bool start_; |
46 }; | 38 }; |
47 | 39 |
48 TypedUrlDataTypeController::TypedUrlDataTypeController( | 40 TypedUrlDataTypeController::TypedUrlDataTypeController( |
49 ProfileSyncFactory* profile_sync_factory, | 41 ProfileSyncFactory* profile_sync_factory, |
50 Profile* profile, | 42 Profile* profile) |
51 ProfileSyncService* sync_service) | 43 : NonFrontendDataTypeController(profile_sync_factory, |
52 : profile_sync_factory_(profile_sync_factory), | 44 profile), |
53 profile_(profile), | 45 backend_(NULL) { |
54 sync_service_(sync_service), | |
55 state_(NOT_RUNNING), | |
56 abort_association_(false), | |
57 abort_association_complete_(false, false), | |
58 datatype_stopped_(false, false) { | |
59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
60 DCHECK(profile_sync_factory); | |
61 DCHECK(profile); | |
62 DCHECK(sync_service); | |
63 } | 46 } |
64 | 47 |
65 TypedUrlDataTypeController::~TypedUrlDataTypeController() { | 48 TypedUrlDataTypeController::~TypedUrlDataTypeController() { |
66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
67 } | 49 } |
68 | 50 |
69 void TypedUrlDataTypeController::Start(StartCallback* start_callback) { | 51 void TypedUrlDataTypeController::RunOnHistoryThread(bool start, |
70 VLOG(1) << "Starting typed_url data controller."; | 52 history::HistoryBackend* backend) { |
| 53 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 54 // The only variable we can access here is backend_, since it is always |
| 55 // read from the DB thread. Touching anything else could lead to memory |
| 56 // corruption. |
| 57 backend_ = backend; |
| 58 if (start) { |
| 59 StartAssociation(); |
| 60 } else { |
| 61 StopAssociation(); |
| 62 } |
| 63 backend_ = NULL; |
| 64 } |
| 65 |
| 66 bool TypedUrlDataTypeController::StartModels() { |
71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
72 DCHECK(start_callback); | 68 DCHECK_EQ(state(), MODEL_STARTING); |
73 if (state_ != NOT_RUNNING || start_callback_.get()) { | 69 HistoryService* history = profile()->GetHistoryServiceWithoutCreating(); |
74 start_callback->Run(BUSY, FROM_HERE); | |
75 delete start_callback; | |
76 return; | |
77 } | |
78 | |
79 start_callback_.reset(start_callback); | |
80 abort_association_ = false; | |
81 | |
82 HistoryService* history = profile_->GetHistoryServiceWithoutCreating(); | |
83 if (history) { | 70 if (history) { |
84 set_state(ASSOCIATING); | |
85 history_service_ = history; | 71 history_service_ = history; |
86 history_service_->ScheduleDBTask(new ControlTask(this, true), this); | 72 return true; |
87 } else { | 73 } else { |
88 set_state(MODEL_STARTING); | |
89 notification_registrar_.Add(this, NotificationType::HISTORY_LOADED, | 74 notification_registrar_.Add(this, NotificationType::HISTORY_LOADED, |
90 NotificationService::AllSources()); | 75 NotificationService::AllSources()); |
| 76 return false; |
91 } | 77 } |
92 } | 78 } |
93 | 79 |
| 80 bool TypedUrlDataTypeController::StartAssociationAsync() { |
| 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 82 DCHECK_EQ(state(), ASSOCIATING); |
| 83 DCHECK(history_service_.get()); |
| 84 history_service_->ScheduleDBTask(new ControlTask(this, true), this); |
| 85 return true; |
| 86 } |
| 87 |
| 88 void TypedUrlDataTypeController::CreateSyncComponents() { |
| 89 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 90 DCHECK_EQ(state(), ASSOCIATING); |
| 91 DCHECK(backend_); |
| 92 ProfileSyncFactory::SyncComponents sync_components = |
| 93 profile_sync_factory()->CreateTypedUrlSyncComponents( |
| 94 profile_sync_service(), |
| 95 backend_, |
| 96 this); |
| 97 set_model_associator(sync_components.model_associator); |
| 98 set_change_processor(sync_components.change_processor); |
| 99 } |
| 100 |
94 void TypedUrlDataTypeController::Observe(NotificationType type, | 101 void TypedUrlDataTypeController::Observe(NotificationType type, |
95 const NotificationSource& source, | 102 const NotificationSource& source, |
96 const NotificationDetails& details) { | 103 const NotificationDetails& details) { |
97 VLOG(1) << "History loaded observed."; | 104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 105 DCHECK_EQ(state(), MODEL_STARTING); |
98 notification_registrar_.Remove(this, | 106 notification_registrar_.Remove(this, |
99 NotificationType::HISTORY_LOADED, | 107 NotificationType::HISTORY_LOADED, |
100 NotificationService::AllSources()); | 108 NotificationService::AllSources()); |
101 | 109 history_service_ = profile()->GetHistoryServiceWithoutCreating(); |
102 history_service_ = profile_->GetHistoryServiceWithoutCreating(); | |
103 DCHECK(history_service_.get()); | 110 DCHECK(history_service_.get()); |
104 history_service_->ScheduleDBTask(new ControlTask(this, true), this); | 111 set_state(ASSOCIATING); |
| 112 StopAssociationAsync(); |
105 } | 113 } |
106 | 114 |
107 // TODO(sync): Blocking the UI thread at shutdown is bad. If we had a way of | 115 void TypedUrlDataTypeController::StopModels() { |
108 // distinguishing chrome shutdown from sync shutdown, we should be able to avoid | |
109 // this (http://crbug.com/55662). Further, all this functionality should be | |
110 // abstracted to a higher layer, where we could ensure all datatypes are doing | |
111 // the same thing (http://crbug.com/76232). | |
112 void TypedUrlDataTypeController::Stop() { | |
113 VLOG(1) << "Stopping typed_url data type controller."; | |
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 117 DCHECK(state() == STOPPING || state() == NOT_RUNNING); |
| 118 notification_registrar_.RemoveAll(); |
| 119 } |
115 | 120 |
116 // If Stop() is called while Start() is waiting for association to | 121 bool TypedUrlDataTypeController::StopAssociationAsync() { |
117 // complete, we need to abort the association and wait for the DB | 122 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
118 // thread to finish the StartImpl() task. | 123 DCHECK_EQ(state(), STOPPING); |
119 if (state_ == ASSOCIATING) { | |
120 { | |
121 base::AutoLock lock(abort_association_lock_); | |
122 abort_association_ = true; | |
123 if (model_associator_.get()) | |
124 model_associator_->AbortAssociation(); | |
125 } | |
126 // Wait for the model association to abort. | |
127 abort_association_complete_.Wait(); | |
128 StartDoneImpl(ABORTED, STOPPING); | |
129 } | |
130 | |
131 // If Stop() is called while Start() is waiting for the history service to | |
132 // load, abort the start. | |
133 if (state_ == MODEL_STARTING) | |
134 StartDoneImpl(ABORTED, STOPPING); | |
135 | |
136 DCHECK(!start_callback_.get()); | |
137 | |
138 if (change_processor_ != NULL) | |
139 sync_service_->DeactivateDataType(this, change_processor_.get()); | |
140 | |
141 set_state(NOT_RUNNING); | |
142 DCHECK(history_service_.get()); | 124 DCHECK(history_service_.get()); |
143 history_service_->ScheduleDBTask(new ControlTask(this, false), this); | 125 history_service_->ScheduleDBTask(new ControlTask(this, false), this); |
144 datatype_stopped_.Wait(); | |
145 } | |
146 | |
147 bool TypedUrlDataTypeController::enabled() { | |
148 return true; | 126 return true; |
149 } | 127 } |
150 | 128 |
151 syncable::ModelType TypedUrlDataTypeController::type() const { | 129 syncable::ModelType TypedUrlDataTypeController::type() const { |
152 return syncable::TYPED_URLS; | 130 return syncable::TYPED_URLS; |
153 } | 131 } |
154 | 132 |
155 browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group() | 133 browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group() |
156 const { | 134 const { |
157 return browser_sync::GROUP_HISTORY; | 135 return browser_sync::GROUP_HISTORY; |
158 } | 136 } |
159 | 137 |
160 std::string TypedUrlDataTypeController::name() const { | 138 void TypedUrlDataTypeController::RecordUnrecoverableError( |
161 // For logging only. | 139 const tracked_objects::Location& from_here, |
162 return "typed_url"; | 140 const std::string& message) { |
| 141 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 142 UMA_HISTOGRAM_COUNTS("Sync.TypedUrlRunFailures", 1); |
163 } | 143 } |
164 | 144 |
165 DataTypeController::State TypedUrlDataTypeController::state() const { | 145 void TypedUrlDataTypeController::RecordAssociationTime(base::TimeDelta time) { |
166 return state_; | 146 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 147 UMA_HISTOGRAM_TIMES("Sync.TypedUrlAssociationTime", time); |
167 } | 148 } |
168 | 149 |
169 void TypedUrlDataTypeController::StartImpl(history::HistoryBackend* backend) { | 150 void TypedUrlDataTypeController::RecordStartFailure(StartResult result) { |
170 VLOG(1) << "TypedUrl data type controller StartImpl called."; | 151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
171 // No additional services need to be started before we can proceed | 152 UMA_HISTOGRAM_ENUMERATION("Sync.TypedUrlStartFailures", |
172 // with model association. | 153 result, |
173 { | 154 MAX_START_RESULT); |
174 base::AutoLock lock(abort_association_lock_); | |
175 if (abort_association_) { | |
176 abort_association_complete_.Signal(); | |
177 return; | |
178 } | |
179 ProfileSyncFactory::SyncComponents sync_components = | |
180 profile_sync_factory_->CreateTypedUrlSyncComponents( | |
181 sync_service_, | |
182 backend, | |
183 this); | |
184 model_associator_.reset(sync_components.model_associator); | |
185 change_processor_.reset(sync_components.change_processor); | |
186 } | |
187 | |
188 if (!model_associator_->CryptoReadyIfNecessary()) { | |
189 StartFailed(NEEDS_CRYPTO); | |
190 return; | |
191 } | |
192 | |
193 bool sync_has_nodes = false; | |
194 if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { | |
195 StartFailed(UNRECOVERABLE_ERROR); | |
196 return; | |
197 } | |
198 | |
199 base::TimeTicks start_time = base::TimeTicks::Now(); | |
200 bool merge_success = model_associator_->AssociateModels(); | |
201 UMA_HISTOGRAM_TIMES("Sync.TypedUrlAssociationTime", | |
202 base::TimeTicks::Now() - start_time); | |
203 if (!merge_success) { | |
204 StartFailed(ASSOCIATION_FAILED); | |
205 return; | |
206 } | |
207 | |
208 sync_service_->ActivateDataType(this, change_processor_.get()); | |
209 StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, RUNNING); | |
210 } | 155 } |
211 | |
212 void TypedUrlDataTypeController::StartDone( | |
213 DataTypeController::StartResult result, | |
214 DataTypeController::State new_state) { | |
215 VLOG(1) << "TypedUrl data type controller StartDone called."; | |
216 | |
217 abort_association_complete_.Signal(); | |
218 base::AutoLock lock(abort_association_lock_); | |
219 if (!abort_association_) { | |
220 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
221 NewRunnableMethod( | |
222 this, | |
223 &TypedUrlDataTypeController::StartDoneImpl, | |
224 result, | |
225 new_state)); | |
226 } | |
227 } | |
228 | |
229 void TypedUrlDataTypeController::StartDoneImpl( | |
230 DataTypeController::StartResult result, | |
231 DataTypeController::State new_state) { | |
232 VLOG(1) << "TypedUrl data type controller StartDoneImpl called."; | |
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
234 set_state(new_state); | |
235 start_callback_->Run(result, FROM_HERE); | |
236 start_callback_.reset(); | |
237 | |
238 if (result == UNRECOVERABLE_ERROR || result == ASSOCIATION_FAILED) { | |
239 UMA_HISTOGRAM_ENUMERATION("Sync.TypedUrlStartFailures", | |
240 result, | |
241 MAX_START_RESULT); | |
242 } | |
243 } | |
244 | |
245 void TypedUrlDataTypeController::StopImpl() { | |
246 VLOG(1) << "TypedUrl data type controller StopImpl called."; | |
247 | |
248 if (model_associator_ != NULL) | |
249 model_associator_->DisassociateModels(); | |
250 | |
251 change_processor_.reset(); | |
252 model_associator_.reset(); | |
253 | |
254 datatype_stopped_.Signal(); | |
255 } | |
256 | |
257 void TypedUrlDataTypeController::StartFailed(StartResult result) { | |
258 change_processor_.reset(); | |
259 model_associator_.reset(); | |
260 StartDone(result, NOT_RUNNING); | |
261 } | |
262 | |
263 void TypedUrlDataTypeController::OnUnrecoverableError( | |
264 const tracked_objects::Location& from_here, | |
265 const std::string& message) { | |
266 BrowserThread::PostTask( | |
267 BrowserThread::UI, FROM_HERE, | |
268 NewRunnableMethod(this, | |
269 &TypedUrlDataTypeController::OnUnrecoverableErrorImpl, | |
270 from_here, message)); | |
271 } | |
272 | |
273 void TypedUrlDataTypeController::OnUnrecoverableErrorImpl( | |
274 const tracked_objects::Location& from_here, | |
275 const std::string& message) { | |
276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
277 UMA_HISTOGRAM_COUNTS("Sync.TypedUrlRunFailures", 1); | |
278 sync_service_->OnUnrecoverableError(from_here, message); | |
279 } | |
280 | |
281 } // namespace browser_sync | 156 } // namespace browser_sync |
OLD | NEW |