1 // Copyright (c) 2012 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/history/android/android_provider_backend.h" 6 7 #include "base/i18n/case_conversion.h" 8 #include "chrome/browser/chrome_notification_types.h" 9 #include "chrome/browser/favicon/favicon_changed_details.h" 10 #include "chrome/browser/history/android/android_time.h" 11 #include "chrome/browser/history/android/android_urls_sql_handler.h" 12 #include "chrome/browser/history/android/bookmark_model_sql_handler.h" 13 #include "chrome/browser/history/android/favicon_sql_handler.h" 14 #include "chrome/browser/history/android/urls_sql_handler.h" 15 #include "chrome/browser/history/android/visit_sql_handler.h" 16 #include "chrome/browser/history/history_backend.h" 17 #include "chrome/browser/history/history_database.h" 18 #include "chrome/browser/history/thumbnail_database.h" 19 #include "components/history/core/browser/history_client.h" 20 #include "content/public/common/page_transition_types.h" 21 #include "sql/connection.h" 22 23 24 namespace history { 25 26 27 // Helpers -------------------------------------------------------------------- 28 29 namespace { 30 31 const char kVirtualHistoryAndBookmarkTable[] = 32 "SELECT android_urls.id AS _id, " 33 "android_cache_db.bookmark_cache.created_time AS created, " 34 "urls.title AS title, android_urls.raw_url AS url, " 35 "urls.visit_count AS visits, " 36 "android_cache_db.bookmark_cache.last_visit_time AS date, " 37 "android_cache_db.bookmark_cache.bookmark AS bookmark, " 38 "android_cache_db.bookmark_cache.favicon_id AS favicon, " 39 "urls.id AS url_id, urls.url AS urls_url, " 40 // TODO (michaelbai) : Remove folder column once we remove it from Android 41 // framework. 42 // Android framework assumes 'folder' column exist in the table, the row is 43 // the bookmark once folder is 0, though it is not part of public API, it 44 // has to be added and set as 0 when the row is bookmark. 45 "(CASE WHEN android_cache_db.bookmark_cache.bookmark IS 0 " 46 "THEN 1 ELSE 0 END) as folder " 47 "FROM (android_urls JOIN urls on (android_urls.url_id = urls.id) " 48 "LEFT JOIN android_cache_db.bookmark_cache " 49 "on (android_urls.url_id = android_cache_db.bookmark_cache.url_id))"; 50 51 const char kURLUpdateClause[] = 52 "SELECT urls.id, urls.last_visit_time, created_time, urls.url " 53 "FROM urls LEFT JOIN " 54 "(SELECT url as visit_url, min(visit_time) as created_time" 55 " FROM visits GROUP BY url) ON (visit_url = urls.id) "; 56 57 const char kSearchTermUpdateClause[] = 58 "SELECT keyword_search_terms.term, max(urls.last_visit_time) " 59 "FROM keyword_search_terms JOIN urls ON " 60 "(keyword_search_terms.url_id = urls.id) " 61 "GROUP BY keyword_search_terms.term"; 62 63 void BindStatement(const std::vector<base::string16>& selection_args, 64 sql::Statement* statement, 65 int* col_index) { 66 for (std::vector<base::string16>::const_iterator i = selection_args.begin(); 67 i != selection_args.end(); ++i) { 68 // Using the same method as Android, binding all argument as String. 69 statement->BindString16(*col_index, *i); 70 ++(*col_index); 71 } 72 } 73 74 bool IsHistoryAndBookmarkRowValid(const HistoryAndBookmarkRow& row) { 75 // The caller should make sure both/neither Raw URL and/nor URL should be set. 76 DCHECK(row.is_value_set_explicitly(HistoryAndBookmarkRow::RAW_URL) == 77 row.is_value_set_explicitly(HistoryAndBookmarkRow::URL)); 78 79 // The following cases are checked: 80 // a. Last visit time or created time is large than now. 81 // b. Last visit time is less than created time. 82 // c. Created time and last visit time is different, but visit count is less 83 // than 2. 84 // d. The difference between created and last visit time is less than 85 // visit_count. 86 // e. Visit count is 0 or 1 and both last visit time and created time are set 87 // explicitly, but the time is different or created time is not UnixEpoch. 88 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) && 89 row.last_visit_time() > base::Time::Now()) 90 return false; 91 92 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) && 93 row.created() > base::Time::Now()) 94 return false; 95 96 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) && 97 row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED)) { 98 if (row.created() > row.last_visit_time()) 99 return false; 100 101 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) && 102 row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) && 103 row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) { 104 if (row.created() != row.last_visit_time() && 105 row.created() != base::Time::UnixEpoch() && 106 (row.visit_count() == 0 || row.visit_count() == 1)) 107 return false; 108 109 if (row.last_visit_time().ToInternalValue() - 110 row.created().ToInternalValue() < row.visit_count()) 111 return false; 112 } 113 } 114 return true; 115 } 116 117 } // namespace 118 119 120 // AndroidProviderBackend::HistoryNotifications ------------------------------- 121 122 AndroidProviderBackend::HistoryNotifications::HistoryNotifications() { 123 } 124 125 AndroidProviderBackend::HistoryNotifications::~HistoryNotifications() { 126 } 127 128 void AndroidProviderBackend::HistoryNotifications::PushBack( 129 int type, 130 scoped_ptr<HistoryDetails> detail) { 131 DCHECK_EQ(types_.size(), details_.size()); 132 types_.push_back(type); 133 details_.push_back(detail.release()); 134 } 135 136 int AndroidProviderBackend::HistoryNotifications::PopBackType() { 137 DCHECK(!empty()); 138 int type = types_.back(); 139 types_.pop_back(); 140 return type; 141 } 142 143 scoped_ptr<HistoryDetails> 144 AndroidProviderBackend::HistoryNotifications::PopBackDetails() { 145 DCHECK(!details_.empty()); 146 scoped_ptr<HistoryDetails> detail(details_.back()); 147 details_.weak_erase(details_.end() - 1); 148 return detail.Pass(); 149 } 150 151 152 // AndroidProviderBackend::ScopedTransaction ---------------------------------- 153 154 AndroidProviderBackend::ScopedTransaction::ScopedTransaction( 155 HistoryDatabase* history_db, 156 ThumbnailDatabase* thumbnail_db) 157 : history_db_(history_db), 158 thumbnail_db_(thumbnail_db), 159 committed_(false), 160 history_transaction_nesting_(history_db_->transaction_nesting()), 161 thumbnail_transaction_nesting_( 162 thumbnail_db_ ? thumbnail_db_->transaction_nesting() : 0) { 163 // Commit all existing transactions since the AndroidProviderBackend's 164 // transaction is very like to be rolled back when compared with the others. 165 // The existing transactions have been scheduled to commit by 166 // ScheduleCommit in HistoryBackend and the same number of transaction 167 // will be created after this scoped transaction ends, there should have no 168 // issue to directly commit all transactions here. 169 int count = history_transaction_nesting_; 170 while (count--) 171 history_db_->CommitTransaction(); 172 history_db_->BeginTransaction(); 173 174 if (thumbnail_db_) { 175 count = thumbnail_transaction_nesting_; 176 while (count--) 177 thumbnail_db_->CommitTransaction(); 178 thumbnail_db_->BeginTransaction(); 179 } 180 } 181 182 AndroidProviderBackend::ScopedTransaction::~ScopedTransaction() { 183 if (!committed_) { 184 history_db_->RollbackTransaction(); 185 if (thumbnail_db_) 186 thumbnail_db_->RollbackTransaction(); 187 } 188 // There is no transaction now. 189 DCHECK_EQ(0, history_db_->transaction_nesting()); 190 DCHECK(!thumbnail_db_ || 0 == thumbnail_db_->transaction_nesting()); 191 192 int count = history_transaction_nesting_; 193 while (count--) 194 history_db_->BeginTransaction(); 195 196 if (thumbnail_db_) { 197 count = thumbnail_transaction_nesting_; 198 while (count--) 199 thumbnail_db_->BeginTransaction(); 200 } 201 } 202 203 void AndroidProviderBackend::ScopedTransaction::Commit() { 204 DCHECK(!committed_); 205 history_db_->CommitTransaction(); 206 if (thumbnail_db_) 207 thumbnail_db_->CommitTransaction(); 208 committed_ = true; 209 } 210 211 212 // AndroidProviderBackend ----------------------------------------------------- 213 214 AndroidProviderBackend::AndroidProviderBackend( 215 const base::FilePath& db_name, 216 HistoryDatabase* history_db, 217 ThumbnailDatabase* thumbnail_db, 218 HistoryClient* history_client, 219 HistoryBackend::Delegate* delegate) 220 : android_cache_db_filename_(db_name), 221 db_(&history_db->GetDB()), 222 history_db_(history_db), 223 thumbnail_db_(thumbnail_db), 224 history_client_(history_client), 225 initialized_(false), 226 delegate_(delegate) { 227 DCHECK(delegate_); 228 } 229 230 AndroidProviderBackend::~AndroidProviderBackend() { 231 } 232 233 AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarks( 234 const std::vector<HistoryAndBookmarkRow::ColumnID>& projections, 235 const std::string& selection, 236 const std::vector<base::string16>& selection_args, 237 const std::string& sort_order) { 238 if (projections.empty()) 239 return NULL; 240 241 ScopedTransaction transaction(history_db_, thumbnail_db_); 242 243 if (!EnsureInitializedAndUpdated()) 244 return NULL; 245 246 transaction.Commit(); 247 248 return QueryHistoryAndBookmarksInternal(projections, selection, 249 selection_args, sort_order); 250 } 251 252 bool AndroidProviderBackend::UpdateHistoryAndBookmarks( 253 const HistoryAndBookmarkRow& row, 254 const std::string& selection, 255 const std::vector<base::string16>& selection_args, 256 int* updated_count) { 257 HistoryNotifications notifications; 258 259 ScopedTransaction transaction(history_db_, thumbnail_db_); 260 261 if (!UpdateHistoryAndBookmarks(row, selection, selection_args, updated_count, 262 ¬ifications)) 263 return false; 264 265 transaction.Commit(); 266 BroadcastNotifications(¬ifications); 267 return true; 268 } 269 270 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark( 271 const HistoryAndBookmarkRow& values) { 272 HistoryNotifications notifications; 273 274 ScopedTransaction transaction(history_db_, thumbnail_db_); 275 276 AndroidURLID id = InsertHistoryAndBookmark(values, true, ¬ifications); 277 if (!id) 278 return 0; 279 280 transaction.Commit(); 281 BroadcastNotifications(¬ifications); 282 return id; 283 } 284 285 bool AndroidProviderBackend::DeleteHistoryAndBookmarks( 286 const std::string& selection, 287 const std::vector<base::string16>& selection_args, 288 int* deleted_count) { 289 HistoryNotifications notifications; 290 291 ScopedTransaction transaction(history_db_, thumbnail_db_); 292 293 if (!DeleteHistoryAndBookmarks(selection, selection_args, deleted_count, 294 ¬ifications)) 295 return false; 296 297 transaction.Commit(); 298 BroadcastNotifications(¬ifications); 299 return true; 300 } 301 302 bool AndroidProviderBackend::DeleteHistory( 303 const std::string& selection, 304 const std::vector<base::string16>& selection_args, 305 int* deleted_count) { 306 HistoryNotifications notifications; 307 308 ScopedTransaction transaction(history_db_, thumbnail_db_); 309 310 if (!DeleteHistory(selection, selection_args, deleted_count, ¬ifications)) 311 return false; 312 313 transaction.Commit(); 314 BroadcastNotifications(¬ifications); 315 return true; 316 } 317 318 bool AndroidProviderBackend::UpdateHistoryAndBookmarks( 319 const HistoryAndBookmarkRow& row, 320 const std::string& selection, 321 const std::vector<base::string16>& selection_args, 322 int* updated_count, 323 HistoryNotifications* notifications) { 324 if (!IsHistoryAndBookmarkRowValid(row)) 325 return false; 326 327 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::ID)) 328 return false; 329 330 if (!EnsureInitializedAndUpdated()) 331 return false; 332 333 TableIDRows ids_set; 334 if (!GetSelectedURLs(selection, selection_args, &ids_set)) 335 return false; 336 337 if (ids_set.empty()) { 338 *updated_count = 0; 339 return true; 340 } 341 342 // URL can not be updated, we simulate the update. 343 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::URL)) { 344 // Only one row's URL can be updated at a time as we can't have multiple 345 // rows have the same URL. 346 if (ids_set.size() != 1) 347 return false; 348 349 HistoryAndBookmarkRow new_row = row; 350 if (!SimulateUpdateURL(new_row, ids_set, notifications)) 351 return false; 352 *updated_count = 1; 353 return true; 354 } 355 356 for (std::vector<SQLHandler*>::iterator i = 357 sql_handlers_.begin(); i != sql_handlers_.end(); ++i) { 358 if ((*i)->HasColumnIn(row)) { 359 if (!(*i)->Update(row, ids_set)) 360 return false; 361 } 362 } 363 *updated_count = ids_set.size(); 364 365 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails); 366 scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails); 367 368 for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end(); 369 ++i) { 370 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE) || 371 row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) || 372 row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) { 373 URLRow url_row; 374 if (!history_db_->GetURLRow(i->url_id, &url_row)) 375 return false; 376 modified->changed_urls.push_back(url_row); 377 } 378 if (thumbnail_db_ && 379 row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON)) 380 favicon->urls.insert(i->url); 381 } 382 383 if (!modified->changed_urls.empty()) { 384 notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 385 modified.PassAs<HistoryDetails>()); 386 } 387 388 if (!favicon->urls.empty()) { 389 notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED, 390 favicon.PassAs<HistoryDetails>()); 391 } 392 393 return true; 394 } 395 396 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark( 397 const HistoryAndBookmarkRow& values, 398 bool ensure_initialized_and_updated, 399 HistoryNotifications* notifications) { 400 if (!IsHistoryAndBookmarkRowValid(values)) 401 return false; 402 403 if (ensure_initialized_and_updated && !EnsureInitializedAndUpdated()) 404 return 0; 405 406 DCHECK(values.is_value_set_explicitly(HistoryAndBookmarkRow::URL)); 407 // Make a copy of values as we need change it during insert. 408 HistoryAndBookmarkRow row = values; 409 for (std::vector<SQLHandler*>::iterator i = 410 sql_handlers_.begin(); i != sql_handlers_.end(); ++i) { 411 if (!(*i)->Insert(&row)) 412 return 0; 413 } 414 415 URLRow url_row; 416 if (!history_db_->GetURLRow(row.url_id(), &url_row)) 417 return false; 418 419 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails); 420 if (!modified.get()) 421 return false; 422 modified->changed_urls.push_back(url_row); 423 424 scoped_ptr<FaviconChangedDetails> favicon; 425 // No favicon should be changed if the thumbnail_db_ is not available. 426 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON) && 427 row.favicon_valid() && thumbnail_db_) { 428 favicon.reset(new FaviconChangedDetails); 429 if (!favicon.get()) 430 return false; 431 favicon->urls.insert(url_row.url()); 432 } 433 434 notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 435 modified.PassAs<HistoryDetails>()); 436 if (favicon) { 437 notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED, 438 favicon.PassAs<HistoryDetails>()); 439 } 440 441 return row.id(); 442 } 443 444 bool AndroidProviderBackend::DeleteHistoryAndBookmarks( 445 const std::string& selection, 446 const std::vector<base::string16>& selection_args, 447 int * deleted_count, 448 HistoryNotifications* notifications) { 449 if (!EnsureInitializedAndUpdated()) 450 return false; 451 452 TableIDRows ids_set; 453 if (!GetSelectedURLs(selection, selection_args, &ids_set)) 454 return false; 455 456 if (ids_set.empty()) { 457 *deleted_count = 0; 458 return true; 459 } 460 461 if (!DeleteHistoryInternal(ids_set, true, notifications)) 462 return false; 463 464 *deleted_count = ids_set.size(); 465 466 return true; 467 } 468 469 bool AndroidProviderBackend::DeleteHistory( 470 const std::string& selection, 471 const std::vector<base::string16>& selection_args, 472 int* deleted_count, 473 HistoryNotifications* notifications) { 474 if (!EnsureInitializedAndUpdated()) 475 return false; 476 477 TableIDRows ids_set; 478 if (!GetSelectedURLs(selection, selection_args, &ids_set)) 479 return false; 480 481 if (ids_set.empty()) { 482 *deleted_count = 0; 483 return true; 484 } 485 486 *deleted_count = ids_set.size(); 487 488 // Get the bookmarked rows. 489 std::vector<HistoryAndBookmarkRow> bookmarks; 490 for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end(); 491 ++i) { 492 if (i->bookmarked) { 493 AndroidURLRow android_url_row; 494 if (!history_db_->GetAndroidURLRow(i->url_id, &android_url_row)) 495 return false; 496 HistoryAndBookmarkRow row; 497 row.set_raw_url(android_url_row.raw_url); 498 row.set_url(i->url); 499 // Set the visit time to the UnixEpoch since that's when the Android 500 // system time starts. The Android have a CTS testcase for this. 501 row.set_last_visit_time(base::Time::UnixEpoch()); 502 row.set_visit_count(0); 503 // We don't want to change the bookmark model, so set_is_bookmark() is 504 // not called. 505 bookmarks.push_back(row); 506 } 507 } 508 509 // Don't delete the bookmark from bookmark model when deleting the history. 510 if (!DeleteHistoryInternal(ids_set, false, notifications)) 511 return false; 512 513 for (std::vector<HistoryAndBookmarkRow>::const_iterator i = bookmarks.begin(); 514 i != bookmarks.end(); ++i) { 515 // Don't update the tables, otherwise, the bookmarks will be added to 516 // database during UpdateBookmark(), then the insertion will fail. 517 // We can't rely on UpdateBookmark() to insert the bookmarks into history 518 // database as the raw_url will be lost. 519 if (!InsertHistoryAndBookmark(*i, false, notifications)) 520 return false; 521 } 522 return true; 523 } 524 525 AndroidStatement* AndroidProviderBackend::QuerySearchTerms( 526 const std::vector<SearchRow::ColumnID>& projections, 527 const std::string& selection, 528 const std::vector<base::string16>& selection_args, 529 const std::string& sort_order) { 530 if (projections.empty()) 531 return NULL; 532 533 if (!EnsureInitializedAndUpdated()) 534 return NULL; 535 536 std::string sql; 537 sql.append("SELECT "); 538 AppendSearchResultColumn(projections, &sql); 539 sql.append(" FROM android_cache_db.search_terms "); 540 541 if (!selection.empty()) { 542 sql.append(" WHERE "); 543 sql.append(selection); 544 } 545 546 if (!sort_order.empty()) { 547 sql.append(" ORDER BY "); 548 sql.append(sort_order); 549 } 550 551 scoped_ptr<sql::Statement> statement(new sql::Statement( 552 db_->GetUniqueStatement(sql.c_str()))); 553 int count = 0; 554 BindStatement(selection_args, statement.get(), &count); 555 if (!statement->is_valid()) { 556 LOG(ERROR) << db_->GetErrorMessage(); 557 return NULL; 558 } 559 sql::Statement* result = statement.release(); 560 return new AndroidStatement(result, -1); 561 } 562 563 bool AndroidProviderBackend::UpdateSearchTerms( 564 const SearchRow& row, 565 const std::string& selection, 566 const std::vector<base::string16>& selection_args, 567 int* update_count) { 568 if (!EnsureInitializedAndUpdated()) 569 return false; 570 571 SearchTerms search_terms; 572 if (!GetSelectedSearchTerms(selection, selection_args, &search_terms)) 573 return false; 574 575 // We can not update search term if multiple row selected. 576 if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM) && 577 search_terms.size() > 1) 578 return false; 579 580 *update_count = search_terms.size(); 581 582 if (search_terms.empty()) 583 return true; 584 585 if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM)) { 586 SearchTermRow search_term_row; 587 SearchRow search_row = row; 588 if (!history_db_->GetSearchTerm(search_terms[0], &search_term_row)) 589 return false; 590 591 search_term_row.term = search_row.search_term(); 592 if (!search_row.is_value_set_explicitly(SearchRow::SEARCH_TIME)) 593 search_row.set_search_time(search_term_row.last_visit_time); 594 else 595 search_term_row.last_visit_time = search_row.search_time(); 596 597 // Delete the original search term. 598 if (!history_db_->DeleteKeywordSearchTerm(search_terms[0])) 599 return false; 600 601 // Add the new one. 602 if (!AddSearchTerm(search_row)) 603 return false; 604 605 // Update the cache table so the id will not be changed. 606 if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row)) 607 return false; 608 609 return true; 610 } 611 612 for (SearchTerms::const_iterator i = search_terms.begin(); 613 i != search_terms.end(); ++i) { 614 SearchTermRow search_term_row; 615 if (!history_db_->GetSearchTerm(*i, &search_term_row)) 616 return false; 617 618 // Check whether the given search time less than the existing one. 619 if (search_term_row.last_visit_time > row.search_time()) 620 return false; 621 622 std::vector<KeywordSearchTermRow> search_term_rows; 623 if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) || 624 search_term_rows.empty()) 625 return false; 626 627 // Actually only search_time update. As there might multiple URLs 628 // asocciated with the keyword, Just update the first one's last_visit_time. 629 URLRow url_row; 630 if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row)) 631 return false; 632 633 HistoryAndBookmarkRow bookmark_row; 634 bookmark_row.set_last_visit_time(row.search_time()); 635 TableIDRow table_id_row; 636 table_id_row.url_id = url_row.id(); 637 TableIDRows table_id_rows; 638 table_id_rows.push_back(table_id_row); 639 if (!urls_handler_->Update(bookmark_row, table_id_rows)) 640 return false; 641 642 if (!visit_handler_->Update(bookmark_row, table_id_rows)) 643 return false; 644 } 645 return true; 646 } 647 648 SearchTermID AndroidProviderBackend::InsertSearchTerm( 649 const SearchRow& values) { 650 if (!EnsureInitializedAndUpdated()) 651 return 0; 652 653 if (!AddSearchTerm(values)) 654 return 0; 655 656 SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL); 657 if (!id) 658 // Note the passed in Time() will be changed in UpdateSearchTermTable(). 659 id = history_db_->AddSearchTerm(values.search_term(), base::Time()); 660 return id; 661 } 662 663 bool AndroidProviderBackend::DeleteSearchTerms( 664 const std::string& selection, 665 const std::vector<base::string16>& selection_args, 666 int * deleted_count) { 667 if (!EnsureInitializedAndUpdated()) 668 return false; 669 670 SearchTerms rows; 671 if (!GetSelectedSearchTerms(selection, selection_args, &rows)) 672 return false; 673 674 *deleted_count = rows.size(); 675 if (rows.empty()) 676 return true; 677 678 for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i) 679 if (!history_db_->DeleteKeywordSearchTerm(*i)) 680 return false; 681 // We don't delete the rows in search_terms table, as once the 682 // search_terms table is updated with keyword_search_terms, all 683 // keyword cache not found in the keyword_search_terms will be removed. 684 return true; 685 } 686 687 bool AndroidProviderBackend::EnsureInitializedAndUpdated() { 688 if (!initialized_) { 689 if (!Init()) 690 return false; 691 } 692 return UpdateTables(); 693 } 694 695 bool AndroidProviderBackend::Init() { 696 urls_handler_.reset(new UrlsSQLHandler(history_db_)); 697 visit_handler_.reset(new VisitSQLHandler(history_db_)); 698 android_urls_handler_.reset(new AndroidURLsSQLHandler(history_db_)); 699 if (thumbnail_db_) 700 favicon_handler_.reset(new FaviconSQLHandler(thumbnail_db_)); 701 bookmark_model_handler_.reset(new BookmarkModelSQLHandler(history_db_)); 702 // The urls_handler must be pushed first, because the subsequent handlers 703 // depend on its output. 704 sql_handlers_.push_back(urls_handler_.get()); 705 sql_handlers_.push_back(visit_handler_.get()); 706 sql_handlers_.push_back(android_urls_handler_.get()); 707 if (favicon_handler_.get()) 708 sql_handlers_.push_back(favicon_handler_.get()); 709 sql_handlers_.push_back(bookmark_model_handler_.get()); 710 711 if (!history_db_->CreateAndroidURLsTable()) 712 return false; 713 if (sql::INIT_OK != history_db_->InitAndroidCacheDatabase( 714 android_cache_db_filename_)) 715 return false; 716 initialized_ = true; 717 return true; 718 } 719 720 bool AndroidProviderBackend::UpdateTables() { 721 if (!UpdateVisitedURLs()) { 722 LOG(ERROR) << "Update of the visisted URLS failed"; 723 return false; 724 } 725 726 if (!UpdateRemovedURLs()) { 727 LOG(ERROR) << "Update of the removed URLS failed"; 728 return false; 729 } 730 731 if (!UpdateBookmarks()) { 732 LOG(ERROR) << "Update of the bookmarks failed"; 733 return false; 734 } 735 736 if (!UpdateFavicon()) { 737 LOG(ERROR) << "Update of the icons failed"; 738 return false; 739 } 740 741 if (!UpdateSearchTermTable()) { 742 LOG(ERROR) << "Update of the search_terms failed"; 743 return false; 744 } 745 return true; 746 } 747 748 bool AndroidProviderBackend::UpdateVisitedURLs() { 749 std::string sql(kURLUpdateClause); 750 sql.append("WHERE urls.id NOT IN (SELECT url_id FROM android_urls)"); 751 sql::Statement urls_statement(db_->GetCachedStatement(SQL_FROM_HERE, 752 sql.c_str())); 753 if (!urls_statement.is_valid()) { 754 LOG(ERROR) << db_->GetErrorMessage(); 755 return false; 756 } 757 758 while (urls_statement.Step()) { 759 if (history_db_->GetAndroidURLRow(urls_statement.ColumnInt64(0), NULL)) 760 continue; 761 if (!history_db_->AddAndroidURLRow(urls_statement.ColumnString(3), 762 urls_statement.ColumnInt64(0))) 763 return false; 764 } 765 766 if (!history_db_->ClearAllBookmarkCache()) 767 return false; 768 769 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, 770 kURLUpdateClause)); 771 while (statement.Step()) { 772 // The last_visit_time and the created time should be same when the visit 773 // count is 0, this behavior is also required by the Android CTS. 774 // The created_time could be set to the last_visit_time only when the type 775 // of the 'created' column is NULL because the left join is used in query 776 // and there is no row in the visit table when the visit count is 0. 777 base::Time last_visit_time = 778 base::Time::FromInternalValue(statement.ColumnInt64(1)); 779 base::Time created_time = last_visit_time; 780 781 if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL) 782 created_time = base::Time::FromInternalValue(statement.ColumnInt64(2)); 783 784 if (!history_db_->AddBookmarkCacheRow(created_time, last_visit_time, 785 statement.ColumnInt64(0))) 786 return false; 787 } 788 return true; 789 } 790 791 bool AndroidProviderBackend::UpdateRemovedURLs() { 792 return history_db_->DeleteUnusedAndroidURLs(); 793 } 794 795 bool AndroidProviderBackend::UpdateBookmarks() { 796 if (history_client_ == NULL) { 797 LOG(ERROR) << "HistoryClient is not available"; 798 return false; 799 } 800 801 std::vector<URLAndTitle> bookmarks; 802 history_client_->GetBookmarks(&bookmarks); 803 804 if (bookmarks.empty()) 805 return true; 806 807 std::vector<URLID> url_ids; 808 for (std::vector<URLAndTitle>::const_iterator i = 809 bookmarks.begin(); i != bookmarks.end(); ++i) { 810 URLID url_id = history_db_->GetRowForURL(i->url, NULL); 811 if (url_id == 0) { 812 URLRow url_row(i->url); 813 url_row.set_title(i->title); 814 // Set the visit time to the UnixEpoch since that's when the Android 815 // system time starts. The Android have a CTS testcase for this. 816 url_row.set_last_visit(base::Time::UnixEpoch()); 817 url_row.set_hidden(true); 818 url_id = history_db_->AddURL(url_row); 819 if (url_id == 0) { 820 LOG(ERROR) << "Can not add url for the new bookmark"; 821 return false; 822 } 823 if (!history_db_->AddAndroidURLRow(i->url.spec(), url_id)) 824 return false; 825 if (!history_db_->AddBookmarkCacheRow(base::Time::UnixEpoch(), 826 base::Time::UnixEpoch(), url_id)) 827 return false; 828 } 829 url_ids.push_back(url_id); 830 } 831 832 return history_db_->MarkURLsAsBookmarked(url_ids); 833 } 834 835 bool AndroidProviderBackend::UpdateFavicon() { 836 ThumbnailDatabase::IconMappingEnumerator enumerator; 837 838 // We want the AndroidProviderBackend run without thumbnail_db_ 839 if (!thumbnail_db_) 840 return true; 841 842 if (!thumbnail_db_->InitIconMappingEnumerator(favicon_base::FAVICON, 843 &enumerator)) 844 return false; 845 846 IconMapping icon_mapping; 847 while (enumerator.GetNextIconMapping(&icon_mapping)) { 848 URLID url_id = history_db_->GetRowForURL(icon_mapping.page_url, NULL); 849 if (url_id == 0) { 850 LOG(ERROR) << "Can not find favicon's page url"; 851 continue; 852 } 853 history_db_->SetFaviconID(url_id, icon_mapping.icon_id); 854 } 855 return true; 856 } 857 858 bool AndroidProviderBackend::UpdateSearchTermTable() { 859 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, 860 kSearchTermUpdateClause)); 861 while (statement.Step()) { 862 base::string16 term = statement.ColumnString16(0); 863 base::Time last_visit_time = 864 base::Time::FromInternalValue(statement.ColumnInt64(1)); 865 SearchTermRow search_term_row; 866 if (history_db_->GetSearchTerm(term, &search_term_row)) { 867 if (search_term_row.last_visit_time != last_visit_time) { 868 search_term_row.last_visit_time = last_visit_time; 869 if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row)) 870 return false; 871 } 872 } else { 873 if (!history_db_->AddSearchTerm(term, last_visit_time)) 874 return false; 875 } 876 } 877 if (!history_db_->DeleteUnusedSearchTerms()) 878 return false; 879 880 return true; 881 } 882 883 int AndroidProviderBackend::AppendBookmarkResultColumn( 884 const std::vector<HistoryAndBookmarkRow::ColumnID>& projections, 885 std::string* result_column) { 886 int replaced_index = -1; 887 // Attach the projections 888 bool first = true; 889 int index = 0; 890 for (std::vector<HistoryAndBookmarkRow::ColumnID>::const_iterator i = 891 projections.begin(); i != projections.end(); ++i) { 892 if (first) 893 first = false; 894 else 895 result_column->append(", "); 896 897 if (*i == HistoryAndBookmarkRow::FAVICON) 898 replaced_index = index; 899 900 result_column->append(HistoryAndBookmarkRow::GetAndroidName(*i)); 901 index++; 902 } 903 return replaced_index; 904 } 905 906 bool AndroidProviderBackend::GetSelectedURLs( 907 const std::string& selection, 908 const std::vector<base::string16>& selection_args, 909 TableIDRows* rows) { 910 std::string sql("SELECT url_id, urls_url, bookmark FROM ("); 911 sql.append(kVirtualHistoryAndBookmarkTable); 912 sql.append(" )"); 913 914 if (!selection.empty()) { 915 sql.append(" WHERE "); 916 sql.append(selection); 917 } 918 919 sql::Statement statement(db_->GetUniqueStatement(sql.c_str())); 920 int count = 0; 921 BindStatement(selection_args, &statement, &count); 922 if (!statement.is_valid()) { 923 LOG(ERROR) << db_->GetErrorMessage(); 924 return false; 925 } 926 while (statement.Step()) { 927 TableIDRow row; 928 row.url_id = statement.ColumnInt64(0); 929 row.url = GURL(statement.ColumnString(1)); 930 row.bookmarked = statement.ColumnBool(2); 931 rows->push_back(row); 932 } 933 return true; 934 } 935 936 bool AndroidProviderBackend::GetSelectedSearchTerms( 937 const std::string& selection, 938 const std::vector<base::string16>& selection_args, 939 SearchTerms* rows) { 940 std::string sql("SELECT search " 941 "FROM android_cache_db.search_terms "); 942 if (!selection.empty()) { 943 sql.append(" WHERE "); 944 sql.append(selection); 945 } 946 sql::Statement statement(db_->GetUniqueStatement(sql.c_str())); 947 int count = 0; 948 BindStatement(selection_args, &statement, &count); 949 if (!statement.is_valid()) { 950 LOG(ERROR) << db_->GetErrorMessage(); 951 return false; 952 } 953 while (statement.Step()) { 954 rows->push_back(statement.ColumnString16(0)); 955 } 956 return true; 957 } 958 959 void AndroidProviderBackend::AppendSearchResultColumn( 960 const std::vector<SearchRow::ColumnID>& projections, 961 std::string* result_column) { 962 bool first = true; 963 int index = 0; 964 for (std::vector<SearchRow::ColumnID>::const_iterator i = 965 projections.begin(); i != projections.end(); ++i) { 966 if (first) 967 first = false; 968 else 969 result_column->append(", "); 970 971 result_column->append(SearchRow::GetAndroidName(*i)); 972 index++; 973 } 974 } 975 976 bool AndroidProviderBackend::SimulateUpdateURL( 977 const HistoryAndBookmarkRow& row, 978 const TableIDRows& ids, 979 HistoryNotifications* notifications) { 980 DCHECK(ids.size() == 1); 981 // URL can not be updated, we simulate the update by deleting the old URL 982 // and inserting the new one; We do update the android_urls table as the id 983 // need to keep same. 984 985 // Find all columns value of the current URL. 986 std::vector<HistoryAndBookmarkRow::ColumnID> projections; 987 projections.push_back(HistoryAndBookmarkRow::LAST_VISIT_TIME); 988 projections.push_back(HistoryAndBookmarkRow::CREATED); 989 projections.push_back(HistoryAndBookmarkRow::VISIT_COUNT); 990 projections.push_back(HistoryAndBookmarkRow::TITLE); 991 projections.push_back(HistoryAndBookmarkRow::FAVICON); 992 projections.push_back(HistoryAndBookmarkRow::BOOKMARK); 993 994 std::ostringstream oss; 995 oss << "url_id = " << ids[0].url_id; 996 997 scoped_ptr<AndroidStatement> statement; 998 statement.reset(QueryHistoryAndBookmarksInternal(projections, oss.str(), 999 std::vector<base::string16>(), std::string())); 1000 if (!statement.get() || !statement->statement()->Step()) 1001 return false; 1002 1003 HistoryAndBookmarkRow new_row; 1004 new_row.set_last_visit_time(FromDatabaseTime( 1005 statement->statement()->ColumnInt64(0))); 1006 new_row.set_created(FromDatabaseTime( 1007 statement->statement()->ColumnInt64(1))); 1008 new_row.set_visit_count(statement->statement()->ColumnInt(2)); 1009 new_row.set_title(statement->statement()->ColumnString16(3)); 1010 1011 scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails); 1012 scoped_ptr<FaviconChangedDetails> favicon_details(new FaviconChangedDetails); 1013 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails); 1014 URLRow old_url_row; 1015 if (!history_db_->GetURLRow(ids[0].url_id, &old_url_row)) 1016 return false; 1017 deleted_details->rows.push_back(old_url_row); 1018 1019 favicon_base::FaviconID favicon_id = statement->statement()->ColumnInt64(4); 1020 if (favicon_id) { 1021 std::vector<FaviconBitmap> favicon_bitmaps; 1022 if (!thumbnail_db_ || 1023 !thumbnail_db_->GetFaviconBitmaps(favicon_id, &favicon_bitmaps)) 1024 return false; 1025 scoped_refptr<base::RefCountedMemory> bitmap_data = 1026 favicon_bitmaps[0].bitmap_data; 1027 if (bitmap_data.get() && bitmap_data->size()) 1028 new_row.set_favicon(bitmap_data); 1029 favicon_details->urls.insert(old_url_row.url()); 1030 favicon_details->urls.insert(row.url()); 1031 } 1032 new_row.set_is_bookmark(statement->statement()->ColumnBool(5)); 1033 1034 // The SQLHandler vector is not used here because the row in android_url 1035 // shouldn't be deleted, we need keep the AndroidUIID unchanged, so it 1036 // appears update to the client. 1037 if (!urls_handler_->Delete(ids)) 1038 return false; 1039 1040 if (!visit_handler_->Delete(ids)) 1041 return false; 1042 1043 if (favicon_handler_ && !favicon_handler_->Delete(ids)) 1044 return false; 1045 1046 if (!bookmark_model_handler_->Delete(ids)) 1047 return false; 1048 1049 new_row.set_url(row.url()); 1050 new_row.set_raw_url(row.raw_url()); 1051 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) 1052 new_row.set_last_visit_time(row.last_visit_time()); 1053 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED)) 1054 new_row.set_created(row.created()); 1055 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT)) 1056 new_row.set_visit_count(row.visit_count()); 1057 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE)) 1058 new_row.set_title(row.title()); 1059 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON)) { 1060 new_row.set_favicon(row.favicon()); 1061 favicon_details->urls.insert(new_row.url()); 1062 } 1063 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::BOOKMARK)) 1064 new_row.set_is_bookmark(row.is_bookmark()); 1065 1066 if (!urls_handler_->Insert(&new_row)) 1067 return false; 1068 1069 if (!visit_handler_->Insert(&new_row)) 1070 return false; 1071 1072 // Update the current row instead of inserting a new row in android urls 1073 // table. We need keep the AndroidUIID unchanged, so it appears update 1074 // to the client. 1075 if (!android_urls_handler_->Update(new_row, ids)) 1076 return false; 1077 1078 if (favicon_handler_ && !favicon_handler_->Insert(&new_row)) 1079 return false; 1080 1081 if (!bookmark_model_handler_->Insert(&new_row)) 1082 return false; 1083 1084 URLRow new_url_row; 1085 if (!history_db_->GetURLRow(new_row.url_id(), &new_url_row)) 1086 return false; 1087 1088 modified->changed_urls.push_back(new_url_row); 1089 1090 notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_DELETED, 1091 deleted_details.PassAs<HistoryDetails>()); 1092 if (favicon_details && !favicon_details->urls.empty()) { 1093 notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED, 1094 favicon_details.PassAs<HistoryDetails>()); 1095 } 1096 notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 1097 modified.PassAs<HistoryDetails>()); 1098 1099 return true; 1100 } 1101 1102 AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarksInternal( 1103 const std::vector<HistoryAndBookmarkRow::ColumnID>& projections, 1104 const std::string& selection, 1105 const std::vector<base::string16>& selection_args, 1106 const std::string& sort_order) { 1107 std::string sql; 1108 sql.append("SELECT "); 1109 int replaced_index = AppendBookmarkResultColumn(projections, &sql); 1110 sql.append(" FROM ("); 1111 sql.append(kVirtualHistoryAndBookmarkTable); 1112 sql.append(")"); 1113 1114 if (!selection.empty()) { 1115 sql.append(" WHERE "); 1116 sql.append(selection); 1117 } 1118 1119 if (!sort_order.empty()) { 1120 sql.append(" ORDER BY "); 1121 sql.append(sort_order); 1122 } 1123 1124 scoped_ptr<sql::Statement> statement(new sql::Statement( 1125 db_->GetUniqueStatement(sql.c_str()))); 1126 int count = 0; 1127 BindStatement(selection_args, statement.get(), &count); 1128 if (!statement->is_valid()) { 1129 LOG(ERROR) << db_->GetErrorMessage(); 1130 return NULL; 1131 } 1132 sql::Statement* result = statement.release(); 1133 return new AndroidStatement(result, replaced_index); 1134 } 1135 1136 bool AndroidProviderBackend::DeleteHistoryInternal( 1137 const TableIDRows& urls, 1138 bool delete_bookmarks, 1139 HistoryNotifications* notifications) { 1140 scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails); 1141 scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails); 1142 for (TableIDRows::const_iterator i = urls.begin(); i != urls.end(); ++i) { 1143 URLRow url_row; 1144 if (!history_db_->GetURLRow(i->url_id, &url_row)) 1145 return false; 1146 deleted_details->rows.push_back(url_row); 1147 if (thumbnail_db_ && 1148 thumbnail_db_->GetIconMappingsForPageURL(url_row.url(), NULL)) 1149 favicon->urls.insert(url_row.url()); 1150 } 1151 1152 // Only invoke Delete on the BookmarkModelHandler if we need 1153 // to delete bookmarks. 1154 for (std::vector<SQLHandler*>::iterator i = 1155 sql_handlers_.begin(); i != sql_handlers_.end(); ++i) { 1156 if ((*i) != bookmark_model_handler_.get() || delete_bookmarks) 1157 if (!(*i)->Delete(urls)) 1158 return false; 1159 } 1160 1161 notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_DELETED, 1162 deleted_details.PassAs<HistoryDetails>()); 1163 if (favicon && !favicon->urls.empty()) { 1164 notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED, 1165 favicon.PassAs<HistoryDetails>()); 1166 } 1167 return true; 1168 } 1169 1170 void AndroidProviderBackend::BroadcastNotifications( 1171 HistoryNotifications* notifications) { 1172 while (!notifications->empty()) { 1173 delegate_->BroadcastNotifications(notifications->PopBackType(), 1174 notifications->PopBackDetails()); 1175 } 1176 } 1177 1178 bool AndroidProviderBackend::AddSearchTerm(const SearchRow& values) { 1179 DCHECK(values.is_value_set_explicitly(SearchRow::SEARCH_TERM)); 1180 DCHECK(values.is_value_set_explicitly(SearchRow::TEMPLATE_URL)); 1181 DCHECK(values.is_value_set_explicitly(SearchRow::URL)); 1182 1183 URLRow url_row; 1184 HistoryAndBookmarkRow bookmark_row; 1185 // Android CTS test BrowserTest.testAccessSearches allows insert the same 1186 // seach term multiple times, and just search time need updated. 1187 if (history_db_->GetRowForURL(values.url(), &url_row)) { 1188 // Already exist, Add a visit. 1189 if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME)) 1190 bookmark_row.set_last_visit_time(values.search_time()); 1191 else 1192 bookmark_row.set_visit_count(url_row.visit_count() + 1); 1193 TableIDRows table_id_rows; 1194 TableIDRow table_id_row; 1195 table_id_row.url = values.url(); 1196 table_id_row.url_id = url_row.id(); 1197 table_id_rows.push_back(table_id_row); 1198 if (!urls_handler_->Update(bookmark_row, table_id_rows)) 1199 return false; 1200 if (!visit_handler_->Update(bookmark_row, table_id_rows)) 1201 return false; 1202 1203 if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL)) 1204 if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(), 1205 values.template_url_id(), values.search_term())) 1206 return false; 1207 } else { 1208 bookmark_row.set_raw_url(values.url().spec()); 1209 bookmark_row.set_url(values.url()); 1210 if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME)) 1211 bookmark_row.set_last_visit_time(values.search_time()); 1212 1213 if (!urls_handler_->Insert(&bookmark_row)) 1214 return false; 1215 1216 if (!visit_handler_->Insert(&bookmark_row)) 1217 return false; 1218 1219 if (!android_urls_handler_->Insert(&bookmark_row)) 1220 return false; 1221 1222 if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(), 1223 values.template_url_id(), values.search_term())) 1224 return false; 1225 } 1226 return true; 1227 } 1228 1229 } // namespace history 1230