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/glue/typed_url_change_processor.h" 6 7 #include "base/string_util.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/history/history_backend.h" 10 #include "chrome/browser/history/history_notifications.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/sync/glue/typed_url_model_associator.h" 13 #include "chrome/browser/sync/profile_sync_service.h" 14 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" 15 #include "content/common/notification_service.h" 16 #include "content/common/notification_type.h" 17 18 namespace browser_sync { 19 20 TypedUrlChangeProcessor::TypedUrlChangeProcessor( 21 TypedUrlModelAssociator* model_associator, 22 history::HistoryBackend* history_backend, 23 UnrecoverableErrorHandler* error_handler) 24 : ChangeProcessor(error_handler), 25 model_associator_(model_associator), 26 history_backend_(history_backend), 27 observing_(false), 28 expected_loop_(MessageLoop::current()) { 29 DCHECK(model_associator); 30 DCHECK(history_backend); 31 DCHECK(error_handler); 32 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 33 // When running in unit tests, there is already a NotificationService object. 34 // Since only one can exist at a time per thread, check first. 35 if (!NotificationService::current()) 36 notification_service_.reset(new NotificationService); 37 StartObserving(); 38 } 39 40 TypedUrlChangeProcessor::~TypedUrlChangeProcessor() { 41 DCHECK(expected_loop_ == MessageLoop::current()); 42 } 43 44 void TypedUrlChangeProcessor::Observe(NotificationType type, 45 const NotificationSource& source, 46 const NotificationDetails& details) { 47 DCHECK(expected_loop_ == MessageLoop::current()); 48 if (!observing_) 49 return; 50 51 VLOG(1) << "Observed typed_url change."; 52 DCHECK(running()); 53 DCHECK(NotificationType::HISTORY_TYPED_URLS_MODIFIED == type || 54 NotificationType::HISTORY_URLS_DELETED == type || 55 NotificationType::HISTORY_URL_VISITED == type); 56 if (type == NotificationType::HISTORY_TYPED_URLS_MODIFIED) { 57 HandleURLsModified(Details<history::URLsModifiedDetails>(details).ptr()); 58 } else if (type == NotificationType::HISTORY_URLS_DELETED) { 59 HandleURLsDeleted(Details<history::URLsDeletedDetails>(details).ptr()); 60 } else if (type == NotificationType::HISTORY_URL_VISITED) { 61 HandleURLsVisited(Details<history::URLVisitedDetails>(details).ptr()); 62 } 63 } 64 65 void TypedUrlChangeProcessor::HandleURLsModified( 66 history::URLsModifiedDetails* details) { 67 // Get all the visits. 68 std::map<history::URLID, history::VisitVector> visit_vectors; 69 for (std::vector<history::URLRow>::iterator url = 70 details->changed_urls.begin(); url != details->changed_urls.end(); 71 ++url) { 72 if (!history_backend_->GetVisitsForURL(url->id(), 73 &(visit_vectors[url->id()]))) { 74 error_handler()->OnUnrecoverableError(FROM_HERE, 75 "Could not get the url's visits."); 76 return; 77 } 78 DCHECK(!visit_vectors[url->id()].empty()); 79 } 80 81 sync_api::WriteTransaction trans(share_handle()); 82 83 sync_api::ReadNode typed_url_root(&trans); 84 if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) { 85 error_handler()->OnUnrecoverableError(FROM_HERE, 86 "Server did not create the top-level typed_url node. We " 87 "might be running against an out-of-date server."); 88 return; 89 } 90 91 for (std::vector<history::URLRow>::iterator url = 92 details->changed_urls.begin(); url != details->changed_urls.end(); 93 ++url) { 94 std::string tag = url->url().spec(); 95 96 history::VisitVector& visits = visit_vectors[url->id()]; 97 98 DCHECK(!visits.empty()); 99 100 DCHECK(static_cast<size_t>(url->visit_count()) == visits.size()); 101 if (static_cast<size_t>(url->visit_count()) != visits.size()) { 102 error_handler()->OnUnrecoverableError(FROM_HERE, 103 "Visit count does not match."); 104 return; 105 } 106 107 sync_api::WriteNode update_node(&trans); 108 if (update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) { 109 model_associator_->WriteToSyncNode(*url, visits, &update_node); 110 } else { 111 sync_api::WriteNode create_node(&trans); 112 if (!create_node.InitUniqueByCreation(syncable::TYPED_URLS, 113 typed_url_root, tag)) { 114 error_handler()->OnUnrecoverableError(FROM_HERE, 115 "Failed to create typed_url sync node."); 116 return; 117 } 118 119 create_node.SetTitle(UTF8ToWide(tag)); 120 model_associator_->WriteToSyncNode(*url, visits, &create_node); 121 122 model_associator_->Associate(&tag, create_node.GetId()); 123 } 124 } 125 } 126 127 void TypedUrlChangeProcessor::HandleURLsDeleted( 128 history::URLsDeletedDetails* details) { 129 sync_api::WriteTransaction trans(share_handle()); 130 131 if (details->all_history) { 132 if (!model_associator_->DeleteAllNodes(&trans)) { 133 error_handler()->OnUnrecoverableError(FROM_HERE, std::string()); 134 return; 135 } 136 } else { 137 for (std::set<GURL>::iterator url = details->urls.begin(); 138 url != details->urls.end(); ++url) { 139 sync_api::WriteNode sync_node(&trans); 140 int64 sync_id = 141 model_associator_->GetSyncIdFromChromeId(url->spec()); 142 if (sync_api::kInvalidId != sync_id) { 143 if (!sync_node.InitByIdLookup(sync_id)) { 144 error_handler()->OnUnrecoverableError(FROM_HERE, 145 "Typed url node lookup failed."); 146 return; 147 } 148 model_associator_->Disassociate(sync_node.GetId()); 149 sync_node.Remove(); 150 } 151 } 152 } 153 } 154 155 void TypedUrlChangeProcessor::HandleURLsVisited( 156 history::URLVisitedDetails* details) { 157 if (!details->row.typed_count()) { 158 // We only care about typed urls. 159 return; 160 } 161 history::VisitVector visits; 162 if (!history_backend_->GetVisitsForURL(details->row.id(), &visits) || 163 visits.empty()) { 164 error_handler()->OnUnrecoverableError(FROM_HERE, 165 "Could not get the url's visits."); 166 return; 167 } 168 169 DCHECK(static_cast<size_t>(details->row.visit_count()) == visits.size()); 170 171 sync_api::WriteTransaction trans(share_handle()); 172 std::string tag = details->row.url().spec(); 173 sync_api::WriteNode update_node(&trans); 174 if (!update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) { 175 // If we don't know about it yet, it will be added later. 176 return; 177 } 178 sync_pb::TypedUrlSpecifics typed_url(update_node.GetTypedUrlSpecifics()); 179 typed_url.add_visit(visits.back().visit_time.ToInternalValue()); 180 update_node.SetTypedUrlSpecifics(typed_url); 181 } 182 183 void TypedUrlChangeProcessor::ApplyChangesFromSyncModel( 184 const sync_api::BaseTransaction* trans, 185 const sync_api::SyncManager::ChangeRecord* changes, 186 int change_count) { 187 DCHECK(expected_loop_ == MessageLoop::current()); 188 if (!running()) 189 return; 190 StopObserving(); 191 192 sync_api::ReadNode typed_url_root(trans); 193 if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) { 194 error_handler()->OnUnrecoverableError(FROM_HERE, 195 "TypedUrl root node lookup failed."); 196 return; 197 } 198 199 TypedUrlModelAssociator::TypedUrlTitleVector titles; 200 TypedUrlModelAssociator::TypedUrlVector new_urls; 201 TypedUrlModelAssociator::TypedUrlVisitVector new_visits; 202 history::VisitVector deleted_visits; 203 TypedUrlModelAssociator::TypedUrlUpdateVector updated_urls; 204 205 for (int i = 0; i < change_count; ++i) { 206 if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == 207 changes[i].action) { 208 DCHECK(changes[i].specifics.HasExtension(sync_pb::typed_url)) << 209 "Typed URL delete change does not have necessary specifics."; 210 GURL url(changes[i].specifics.GetExtension(sync_pb::typed_url).url()); 211 history_backend_->DeleteURL(url); 212 continue; 213 } 214 215 sync_api::ReadNode sync_node(trans); 216 if (!sync_node.InitByIdLookup(changes[i].id)) { 217 error_handler()->OnUnrecoverableError(FROM_HERE, 218 "TypedUrl node lookup failed."); 219 return; 220 } 221 222 // Check that the changed node is a child of the typed_urls folder. 223 DCHECK(typed_url_root.GetId() == sync_node.GetParentId()); 224 DCHECK(syncable::TYPED_URLS == sync_node.GetModelType()); 225 226 const sync_pb::TypedUrlSpecifics& typed_url( 227 sync_node.GetTypedUrlSpecifics()); 228 GURL url(typed_url.url()); 229 230 if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) { 231 DCHECK(typed_url.visit_size()); 232 if (!typed_url.visit_size()) { 233 continue; 234 } 235 236 history::URLRow new_url(url); 237 new_url.set_title(UTF8ToUTF16(typed_url.title())); 238 239 // When we add a new url, the last visit is always added, thus we set 240 // the initial visit count to one. This value will be automatically 241 // incremented as visits are added. 242 new_url.set_visit_count(1); 243 new_url.set_typed_count(typed_url.typed_count()); 244 new_url.set_hidden(typed_url.hidden()); 245 246 new_url.set_last_visit(base::Time::FromInternalValue( 247 typed_url.visit(typed_url.visit_size() - 1))); 248 249 new_urls.push_back(new_url); 250 251 // The latest visit gets added automatically, so skip it. 252 std::vector<base::Time> added_visits; 253 for (int c = 0; c < typed_url.visit_size() - 1; ++c) { 254 DCHECK(typed_url.visit(c) < typed_url.visit(c + 1)); 255 added_visits.push_back( 256 base::Time::FromInternalValue(typed_url.visit(c))); 257 } 258 259 new_visits.push_back( 260 std::pair<GURL, std::vector<base::Time> >(url, added_visits)); 261 } else { 262 history::URLRow old_url; 263 if (!history_backend_->GetURL(url, &old_url)) { 264 error_handler()->OnUnrecoverableError(FROM_HERE, 265 "TypedUrl db lookup failed."); 266 return; 267 } 268 269 history::VisitVector visits; 270 if (!history_backend_->GetVisitsForURL(old_url.id(), &visits)) { 271 error_handler()->OnUnrecoverableError(FROM_HERE, 272 "Could not get the url's visits."); 273 return; 274 } 275 276 history::URLRow new_url(url); 277 new_url.set_title(UTF8ToUTF16(typed_url.title())); 278 new_url.set_visit_count(old_url.visit_count()); 279 new_url.set_typed_count(typed_url.typed_count()); 280 new_url.set_last_visit(old_url.last_visit()); 281 new_url.set_hidden(typed_url.hidden()); 282 283 updated_urls.push_back( 284 std::pair<history::URLID, history::URLRow>(old_url.id(), new_url)); 285 286 if (old_url.title().compare(new_url.title()) != 0) { 287 titles.push_back(std::pair<GURL, string16>(new_url.url(), 288 new_url.title())); 289 } 290 291 std::vector<base::Time> added_visits; 292 history::VisitVector removed_visits; 293 TypedUrlModelAssociator::DiffVisits(visits, typed_url, 294 &added_visits, &removed_visits); 295 if (added_visits.size()) { 296 new_visits.push_back( 297 std::pair<GURL, std::vector<base::Time> >(url, added_visits)); 298 } 299 if (removed_visits.size()) { 300 deleted_visits.insert(deleted_visits.end(), removed_visits.begin(), 301 removed_visits.end()); 302 } 303 } 304 } 305 if (!model_associator_->WriteToHistoryBackend(&titles, &new_urls, 306 &updated_urls, 307 &new_visits, &deleted_visits)) { 308 error_handler()->OnUnrecoverableError(FROM_HERE, 309 "Could not write to the history backend."); 310 return; 311 } 312 313 StartObserving(); 314 } 315 316 void TypedUrlChangeProcessor::StartImpl(Profile* profile) { 317 DCHECK(expected_loop_ == MessageLoop::current()); 318 observing_ = true; 319 } 320 321 void TypedUrlChangeProcessor::StopImpl() { 322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 323 observing_ = false; 324 } 325 326 327 void TypedUrlChangeProcessor::StartObserving() { 328 DCHECK(expected_loop_ == MessageLoop::current()); 329 notification_registrar_.Add(this, 330 NotificationType::HISTORY_TYPED_URLS_MODIFIED, 331 NotificationService::AllSources()); 332 notification_registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, 333 NotificationService::AllSources()); 334 notification_registrar_.Add(this, NotificationType::HISTORY_URL_VISITED, 335 NotificationService::AllSources()); 336 } 337 338 void TypedUrlChangeProcessor::StopObserving() { 339 DCHECK(expected_loop_ == MessageLoop::current()); 340 notification_registrar_.Remove(this, 341 NotificationType::HISTORY_TYPED_URLS_MODIFIED, 342 NotificationService::AllSources()); 343 notification_registrar_.Remove(this, 344 NotificationType::HISTORY_URLS_DELETED, 345 NotificationService::AllSources()); 346 notification_registrar_.Remove(this, 347 NotificationType::HISTORY_URL_VISITED, 348 NotificationService::AllSources()); 349 } 350 351 } // namespace browser_sync 352