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_model_associator.h" 6 7 #include <set> 8 9 #include "base/utf_string_conversions.h" 10 #include "chrome/browser/history/history_backend.h" 11 #include "chrome/browser/sync/engine/syncapi.h" 12 #include "chrome/browser/sync/profile_sync_service.h" 13 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" 14 15 namespace browser_sync { 16 17 const char kTypedUrlTag[] = "google_chrome_typed_urls"; 18 19 TypedUrlModelAssociator::TypedUrlModelAssociator( 20 ProfileSyncService* sync_service, 21 history::HistoryBackend* history_backend) 22 : sync_service_(sync_service), 23 history_backend_(history_backend), 24 typed_url_node_id_(sync_api::kInvalidId), 25 expected_loop_(MessageLoop::current()) { 26 DCHECK(sync_service_); 27 DCHECK(history_backend_); 28 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 29 } 30 31 TypedUrlModelAssociator::~TypedUrlModelAssociator() {} 32 33 bool TypedUrlModelAssociator::AssociateModels() { 34 VLOG(1) << "Associating TypedUrl Models"; 35 DCHECK(expected_loop_ == MessageLoop::current()); 36 37 std::vector<history::URLRow> typed_urls; 38 if (!history_backend_->GetAllTypedURLs(&typed_urls)) { 39 LOG(ERROR) << "Could not get the typed_url entries."; 40 return false; 41 } 42 43 // Get all the visits. 44 std::map<history::URLID, history::VisitVector> visit_vectors; 45 for (std::vector<history::URLRow>::iterator ix = typed_urls.begin(); 46 ix != typed_urls.end(); ++ix) { 47 if (!history_backend_->GetVisitsForURL(ix->id(), 48 &(visit_vectors[ix->id()]))) { 49 LOG(ERROR) << "Could not get the url's visits."; 50 return false; 51 } 52 DCHECK(!visit_vectors[ix->id()].empty()); 53 } 54 55 TypedUrlTitleVector titles; 56 TypedUrlVector new_urls; 57 TypedUrlVisitVector new_visits; 58 TypedUrlUpdateVector updated_urls; 59 60 { 61 sync_api::WriteTransaction trans(sync_service_->GetUserShare()); 62 sync_api::ReadNode typed_url_root(&trans); 63 if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) { 64 LOG(ERROR) << "Server did not create the top-level typed_url node. We " 65 << "might be running against an out-of-date server."; 66 return false; 67 } 68 69 std::set<std::string> current_urls; 70 for (std::vector<history::URLRow>::iterator ix = typed_urls.begin(); 71 ix != typed_urls.end(); ++ix) { 72 std::string tag = ix->url().spec(); 73 74 history::VisitVector& visits = visit_vectors[ix->id()]; 75 DCHECK(visits.size() == static_cast<size_t>(ix->visit_count())); 76 if (visits.size() != static_cast<size_t>(ix->visit_count())) { 77 LOG(ERROR) << "Visit count does not match."; 78 return false; 79 } 80 81 sync_api::ReadNode node(&trans); 82 if (node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) { 83 const sync_pb::TypedUrlSpecifics& typed_url( 84 node.GetTypedUrlSpecifics()); 85 DCHECK_EQ(tag, typed_url.url()); 86 87 history::URLRow new_url(ix->url()); 88 89 std::vector<base::Time> added_visits; 90 int difference = MergeUrls(typed_url, *ix, &visits, &new_url, 91 &added_visits); 92 if (difference & DIFF_NODE_CHANGED) { 93 sync_api::WriteNode write_node(&trans); 94 if (!write_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) { 95 LOG(ERROR) << "Failed to edit typed_url sync node."; 96 return false; 97 } 98 WriteToSyncNode(new_url, visits, &write_node); 99 } 100 if (difference & DIFF_TITLE_CHANGED) { 101 titles.push_back(std::pair<GURL, string16>(new_url.url(), 102 new_url.title())); 103 } 104 if (difference & DIFF_ROW_CHANGED) { 105 updated_urls.push_back( 106 std::pair<history::URLID, history::URLRow>(ix->id(), new_url)); 107 } 108 if (difference & DIFF_VISITS_ADDED) { 109 new_visits.push_back( 110 std::pair<GURL, std::vector<base::Time> >(ix->url(), 111 added_visits)); 112 } 113 114 Associate(&tag, node.GetId()); 115 } else { 116 sync_api::WriteNode node(&trans); 117 if (!node.InitUniqueByCreation(syncable::TYPED_URLS, 118 typed_url_root, tag)) { 119 LOG(ERROR) << "Failed to create typed_url sync node."; 120 return false; 121 } 122 123 node.SetTitle(UTF8ToWide(tag)); 124 WriteToSyncNode(*ix, visits, &node); 125 126 Associate(&tag, node.GetId()); 127 } 128 129 current_urls.insert(tag); 130 } 131 132 int64 sync_child_id = typed_url_root.GetFirstChildId(); 133 while (sync_child_id != sync_api::kInvalidId) { 134 sync_api::ReadNode sync_child_node(&trans); 135 if (!sync_child_node.InitByIdLookup(sync_child_id)) { 136 LOG(ERROR) << "Failed to fetch child node."; 137 return false; 138 } 139 const sync_pb::TypedUrlSpecifics& typed_url( 140 sync_child_node.GetTypedUrlSpecifics()); 141 142 if (current_urls.find(typed_url.url()) == current_urls.end()) { 143 new_visits.push_back( 144 std::pair<GURL, std::vector<base::Time> >( 145 GURL(typed_url.url()), 146 std::vector<base::Time>())); 147 std::vector<base::Time>& visits = new_visits.back().second; 148 history::URLRow new_url(GURL(typed_url.url())); 149 150 new_url.set_title(UTF8ToUTF16(typed_url.title())); 151 152 // When we add a new url, the last visit is always added, thus we set 153 // the initial visit count to one. This value will be automatically 154 // incremented as visits are added. 155 new_url.set_visit_count(1); 156 new_url.set_typed_count(typed_url.typed_count()); 157 new_url.set_hidden(typed_url.hidden()); 158 159 // The latest visit gets added automatically, so skip it. 160 for (int c = 0; c < typed_url.visit_size() - 1; ++c) { 161 DCHECK(typed_url.visit(c) < typed_url.visit(c + 1)); 162 visits.push_back(base::Time::FromInternalValue(typed_url.visit(c))); 163 } 164 165 new_url.set_last_visit(base::Time::FromInternalValue( 166 typed_url.visit(typed_url.visit_size() - 1))); 167 168 Associate(&typed_url.url(), sync_child_node.GetId()); 169 new_urls.push_back(new_url); 170 } 171 172 sync_child_id = sync_child_node.GetSuccessorId(); 173 } 174 } 175 176 // Since we're on the history thread, we don't have to worry about updating 177 // the history database after closing the write transaction, since 178 // this is the only thread that writes to the database. We also don't have 179 // to worry about the sync model getting out of sync, because changes are 180 // propagated to the ChangeProcessor on this thread. 181 return WriteToHistoryBackend(&titles, &new_urls, &updated_urls, 182 &new_visits, NULL); 183 } 184 185 bool TypedUrlModelAssociator::DeleteAllNodes( 186 sync_api::WriteTransaction* trans) { 187 DCHECK(expected_loop_ == MessageLoop::current()); 188 for (TypedUrlToSyncIdMap::iterator node_id = id_map_.begin(); 189 node_id != id_map_.end(); ++node_id) { 190 sync_api::WriteNode sync_node(trans); 191 if (!sync_node.InitByIdLookup(node_id->second)) { 192 LOG(ERROR) << "Typed url node lookup failed."; 193 return false; 194 } 195 sync_node.Remove(); 196 } 197 198 id_map_.clear(); 199 id_map_inverse_.clear(); 200 return true; 201 } 202 203 bool TypedUrlModelAssociator::DisassociateModels() { 204 id_map_.clear(); 205 id_map_inverse_.clear(); 206 return true; 207 } 208 209 bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { 210 DCHECK(has_nodes); 211 *has_nodes = false; 212 int64 typed_url_sync_id; 213 if (!GetSyncIdForTaggedNode(kTypedUrlTag, &typed_url_sync_id)) { 214 LOG(ERROR) << "Server did not create the top-level typed_url node. We " 215 << "might be running against an out-of-date server."; 216 return false; 217 } 218 sync_api::ReadTransaction trans(sync_service_->GetUserShare()); 219 220 sync_api::ReadNode typed_url_node(&trans); 221 if (!typed_url_node.InitByIdLookup(typed_url_sync_id)) { 222 LOG(ERROR) << "Server did not create the top-level typed_url node. We " 223 << "might be running against an out-of-date server."; 224 return false; 225 } 226 227 // The sync model has user created nodes if the typed_url folder has any 228 // children. 229 *has_nodes = sync_api::kInvalidId != typed_url_node.GetFirstChildId(); 230 return true; 231 } 232 233 void TypedUrlModelAssociator::AbortAssociation() { 234 // TODO(zork): Implement this. 235 } 236 237 const std::string* TypedUrlModelAssociator::GetChromeNodeFromSyncId( 238 int64 sync_id) { 239 return NULL; 240 } 241 242 bool TypedUrlModelAssociator::InitSyncNodeFromChromeId( 243 const std::string& node_id, 244 sync_api::BaseNode* sync_node) { 245 return false; 246 } 247 248 int64 TypedUrlModelAssociator::GetSyncIdFromChromeId( 249 const std::string& typed_url) { 250 TypedUrlToSyncIdMap::const_iterator iter = id_map_.find(typed_url); 251 return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; 252 } 253 254 void TypedUrlModelAssociator::Associate( 255 const std::string* typed_url, int64 sync_id) { 256 DCHECK(expected_loop_ == MessageLoop::current()); 257 DCHECK_NE(sync_api::kInvalidId, sync_id); 258 DCHECK(id_map_.find(*typed_url) == id_map_.end()); 259 DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end()); 260 id_map_[*typed_url] = sync_id; 261 id_map_inverse_[sync_id] = *typed_url; 262 } 263 264 void TypedUrlModelAssociator::Disassociate(int64 sync_id) { 265 DCHECK(expected_loop_ == MessageLoop::current()); 266 SyncIdToTypedUrlMap::iterator iter = id_map_inverse_.find(sync_id); 267 if (iter == id_map_inverse_.end()) 268 return; 269 CHECK(id_map_.erase(iter->second)); 270 id_map_inverse_.erase(iter); 271 } 272 273 bool TypedUrlModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, 274 int64* sync_id) { 275 sync_api::ReadTransaction trans(sync_service_->GetUserShare()); 276 sync_api::ReadNode sync_node(&trans); 277 if (!sync_node.InitByTagLookup(tag.c_str())) 278 return false; 279 *sync_id = sync_node.GetId(); 280 return true; 281 } 282 283 bool TypedUrlModelAssociator::WriteToHistoryBackend( 284 const TypedUrlTitleVector* titles, 285 const TypedUrlVector* new_urls, 286 const TypedUrlUpdateVector* updated_urls, 287 const TypedUrlVisitVector* new_visits, 288 const history::VisitVector* deleted_visits) { 289 if (titles) { 290 for (TypedUrlTitleVector::const_iterator title = titles->begin(); 291 title != titles->end(); ++title) { 292 history_backend_->SetPageTitle(title->first, title->second); 293 } 294 } 295 if (new_urls) { 296 history_backend_->AddPagesWithDetails(*new_urls, history::SOURCE_SYNCED); 297 } 298 if (updated_urls) { 299 for (TypedUrlUpdateVector::const_iterator url = updated_urls->begin(); 300 url != updated_urls->end(); ++url) { 301 if (!history_backend_->UpdateURL(url->first, url->second)) { 302 LOG(ERROR) << "Could not update page: " << url->second.url().spec(); 303 return false; 304 } 305 } 306 } 307 if (new_visits) { 308 for (TypedUrlVisitVector::const_iterator visits = new_visits->begin(); 309 visits != new_visits->end(); ++visits) { 310 if (!history_backend_->AddVisits(visits->first, visits->second, 311 history::SOURCE_SYNCED)) { 312 LOG(ERROR) << "Could not add visits."; 313 return false; 314 } 315 } 316 } 317 if (deleted_visits) { 318 if (!history_backend_->RemoveVisits(*deleted_visits)) { 319 LOG(ERROR) << "Could not remove visits."; 320 return false; 321 } 322 } 323 return true; 324 } 325 326 // static 327 int TypedUrlModelAssociator::MergeUrls( 328 const sync_pb::TypedUrlSpecifics& typed_url, 329 const history::URLRow& url, 330 history::VisitVector* visits, 331 history::URLRow* new_url, 332 std::vector<base::Time>* new_visits) { 333 DCHECK(new_url); 334 DCHECK(!typed_url.url().compare(url.url().spec())); 335 DCHECK(!typed_url.url().compare(new_url->url().spec())); 336 DCHECK(visits->size()); 337 338 new_url->set_visit_count(visits->size()); 339 340 // Convert these values only once. 341 string16 typed_title(UTF8ToUTF16(typed_url.title())); 342 base::Time typed_visit = 343 base::Time::FromInternalValue( 344 typed_url.visit(typed_url.visit_size() - 1)); 345 346 // This is a bitfield represting what we'll need to update with the output 347 // value. 348 int different = DIFF_NONE; 349 350 // Check if the non-incremented values changed. 351 if ((typed_title.compare(url.title()) != 0) || 352 (typed_url.hidden() != url.hidden())) { 353 // Use the values from the most recent visit. 354 if (typed_visit >= url.last_visit()) { 355 new_url->set_title(typed_title); 356 new_url->set_hidden(typed_url.hidden()); 357 different |= DIFF_ROW_CHANGED; 358 359 // If we're changing the local title, note this. 360 if (new_url->title().compare(url.title()) != 0) { 361 different |= DIFF_TITLE_CHANGED; 362 } 363 } else { 364 new_url->set_title(url.title()); 365 new_url->set_hidden(url.hidden()); 366 different |= DIFF_NODE_CHANGED; 367 } 368 } else { 369 // No difference. 370 new_url->set_title(url.title()); 371 new_url->set_hidden(url.hidden()); 372 } 373 374 // For typed count, we just select the maximum value. 375 if (typed_url.typed_count() > url.typed_count()) { 376 new_url->set_typed_count(typed_url.typed_count()); 377 different |= DIFF_ROW_CHANGED; 378 } else if (typed_url.typed_count() < url.typed_count()) { 379 new_url->set_typed_count(url.typed_count()); 380 different |= DIFF_NODE_CHANGED; 381 } else { 382 // No difference. 383 new_url->set_typed_count(typed_url.typed_count()); 384 } 385 386 size_t left_visit_count = typed_url.visit_size(); 387 size_t right_visit_count = visits->size(); 388 size_t left = 0; 389 size_t right = 0; 390 while (left < left_visit_count && right < right_visit_count) { 391 base::Time left_time = base::Time::FromInternalValue(typed_url.visit(left)); 392 if (left_time < (*visits)[right].visit_time) { 393 different |= DIFF_VISITS_ADDED; 394 new_visits->push_back(left_time); 395 // This visit is added to visits below. 396 ++left; 397 } else if (left_time > (*visits)[right].visit_time) { 398 different |= DIFF_NODE_CHANGED; 399 ++right; 400 } else { 401 ++left; 402 ++right; 403 } 404 } 405 406 for ( ; left < left_visit_count; ++left) { 407 different |= DIFF_VISITS_ADDED; 408 base::Time left_time = base::Time::FromInternalValue(typed_url.visit(left)); 409 new_visits->push_back(left_time); 410 // This visit is added to visits below. 411 } 412 if (different & DIFF_VISITS_ADDED) { 413 history::VisitVector::iterator visit_ix = visits->begin(); 414 for (std::vector<base::Time>::iterator new_visit = new_visits->begin(); 415 new_visit != new_visits->end(); ++new_visit) { 416 while (visit_ix != visits->end() && *new_visit > visit_ix->visit_time) { 417 ++visit_ix; 418 } 419 visit_ix = visits->insert(visit_ix, 420 history::VisitRow(url.id(), *new_visit, 421 0, 0, 0)); 422 ++visit_ix; 423 } 424 } 425 426 new_url->set_last_visit(visits->back().visit_time); 427 428 DCHECK(static_cast<size_t>(new_url->visit_count()) == 429 (visits->size() - new_visits->size())); 430 431 return different; 432 } 433 434 // static 435 void TypedUrlModelAssociator::WriteToSyncNode( 436 const history::URLRow& url, 437 const history::VisitVector& visits, 438 sync_api::WriteNode* node) { 439 DCHECK(!url.last_visit().is_null()); 440 DCHECK(!visits.empty()); 441 DCHECK(url.last_visit() == visits.back().visit_time); 442 443 sync_pb::TypedUrlSpecifics typed_url; 444 typed_url.set_url(url.url().spec()); 445 typed_url.set_title(UTF16ToUTF8(url.title())); 446 typed_url.set_typed_count(url.typed_count()); 447 typed_url.set_hidden(url.hidden()); 448 449 for (history::VisitVector::const_iterator visit = visits.begin(); 450 visit != visits.end(); ++visit) { 451 typed_url.add_visit(visit->visit_time.ToInternalValue()); 452 } 453 454 node->SetTypedUrlSpecifics(typed_url); 455 } 456 457 // static 458 void TypedUrlModelAssociator::DiffVisits( 459 const history::VisitVector& old_visits, 460 const sync_pb::TypedUrlSpecifics& new_url, 461 std::vector<base::Time>* new_visits, 462 history::VisitVector* removed_visits) { 463 size_t left_visit_count = old_visits.size(); 464 size_t right_visit_count = new_url.visit_size(); 465 size_t left = 0; 466 size_t right = 0; 467 while (left < left_visit_count && right < right_visit_count) { 468 base::Time right_time = base::Time::FromInternalValue(new_url.visit(right)); 469 if (old_visits[left].visit_time < right_time) { 470 removed_visits->push_back(old_visits[left]); 471 ++left; 472 } else if (old_visits[left].visit_time > right_time) { 473 new_visits->push_back(right_time); 474 ++right; 475 } else { 476 ++left; 477 ++right; 478 } 479 } 480 481 for ( ; left < left_visit_count; ++left) { 482 removed_visits->push_back(old_visits[left]); 483 } 484 485 for ( ; right < right_visit_count; ++right) { 486 new_visits->push_back(base::Time::FromInternalValue(new_url.visit(right))); 487 } 488 } 489 490 bool TypedUrlModelAssociator::CryptoReadyIfNecessary() { 491 // We only access the cryptographer while holding a transaction. 492 sync_api::ReadTransaction trans(sync_service_->GetUserShare()); 493 syncable::ModelTypeSet encrypted_types; 494 sync_service_->GetEncryptedDataTypes(&encrypted_types); 495 return encrypted_types.count(syncable::TYPED_URLS) == 0 || 496 sync_service_->IsCryptographerReady(&trans); 497 } 498 499 } // namespace browser_sync 500