blob: 599e9dc46ba729dc95d67a906e08e1fdd889c813 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/history/android/android_provider_backend.h"
#include "base/i18n/case_conversion.h"
#include "chrome/browser/bookmarks/bookmark_service.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/favicon/favicon_changed_details.h"
#include "chrome/browser/history/android/android_time.h"
#include "chrome/browser/history/android/android_urls_sql_handler.h"
#include "chrome/browser/history/android/bookmark_model_sql_handler.h"
#include "chrome/browser/history/android/favicon_sql_handler.h"
#include "chrome/browser/history/android/urls_sql_handler.h"
#include "chrome/browser/history/android/visit_sql_handler.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/thumbnail_database.h"
#include "content/public/common/page_transition_types.h"
#include "sql/connection.h"
using base::Time;
using base::TimeDelta;
namespace history {
namespace {
const char* kVirtualHistoryAndBookmarkTable =
"SELECT android_urls.id AS _id, "
"android_cache_db.bookmark_cache.created_time AS created, "
"urls.title AS title, android_urls.raw_url AS url, "
"urls.visit_count AS visits, "
"android_cache_db.bookmark_cache.last_visit_time AS date, "
"android_cache_db.bookmark_cache.bookmark AS bookmark, "
"android_cache_db.bookmark_cache.favicon_id AS favicon, "
"urls.id AS url_id, urls.url AS urls_url, "
// TODO (michaelbai) : Remove folder column once we remove it from Android
// framework.
// Android framework assumes 'folder' column exist in the table, the row is
// the bookmark once folder is 0, though it is not part of public API, it
// has to be added and set as 0 when the row is bookmark.
"(CASE WHEN android_cache_db.bookmark_cache.bookmark IS 0 "
"THEN 1 ELSE 0 END) as folder "
"FROM (android_urls JOIN urls on (android_urls.url_id = urls.id) "
"LEFT JOIN android_cache_db.bookmark_cache "
"on (android_urls.url_id = android_cache_db.bookmark_cache.url_id))";
const char * kURLUpdateClause =
"SELECT urls.id, urls.last_visit_time, created_time, urls.url "
"FROM urls LEFT JOIN "
"(SELECT url as visit_url, min(visit_time) as created_time"
" FROM visits GROUP BY url) ON (visit_url = urls.id) ";
const char* kSearchTermUpdateClause =
"SELECT keyword_search_terms.term, max(urls.last_visit_time) "
"FROM keyword_search_terms JOIN urls ON "
"(keyword_search_terms.url_id = urls.id) "
"GROUP BY keyword_search_terms.term";
void BindStatement(const std::vector<string16>& selection_args,
sql::Statement* statement,
int* col_index) {
for (std::vector<string16>::const_iterator i = selection_args.begin();
i != selection_args.end(); ++i) {
// Using the same method as Android, binding all argument as String.
statement->BindString16(*col_index, *i);
(*col_index)++;
}
}
bool IsHistoryAndBookmarkRowValid(const HistoryAndBookmarkRow& row) {
// The caller should make sure both/neither Raw URL and/nor URL should be set.
DCHECK(row.is_value_set_explicitly(HistoryAndBookmarkRow::RAW_URL) ==
row.is_value_set_explicitly(HistoryAndBookmarkRow::URL));
// The following cases are checked:
// a. Last visit time or created time is large than now.
// b. Last visit time is less than created time.
// c. Created time and last visit time is different, but visit count is less
// than 2.
// d. The difference between created and last visit time is less than
// visit_count.
// e. Visit count is 0 or 1 and both last visit time and created time are set
// explicitly, but the time is different or created time is not UnixEpoch.
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
row.last_visit_time() > Time::Now())
return false;
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
row.created() > Time::Now())
return false;
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED)) {
if (row.created() > row.last_visit_time())
return false;
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) &&
row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
if (row.created() != row.last_visit_time() &&
row.created() != Time::UnixEpoch() &&
(row.visit_count() == 0 || row.visit_count() == 1))
return false;
if (row.last_visit_time().ToInternalValue() -
row.created().ToInternalValue() < row.visit_count())
return false;
}
}
return true;
}
} // namespace
AndroidProviderBackend::AndroidProviderBackend(
const base::FilePath& db_name,
HistoryDatabase* history_db,
ThumbnailDatabase* thumbnail_db,
BookmarkService* bookmark_service,
HistoryBackend::Delegate* delegate)
: android_cache_db_filename_(db_name),
db_(&history_db->GetDB()),
history_db_(history_db),
thumbnail_db_(thumbnail_db),
bookmark_service_(bookmark_service),
initialized_(false),
delegate_(delegate) {
DCHECK(delegate_);
}
AndroidProviderBackend::~AndroidProviderBackend() {
}
AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarks(
const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
const std::string& selection,
const std::vector<string16>& selection_args,
const std::string& sort_order) {
if (projections.empty())
return NULL;
ScopedTransaction transaction(history_db_, thumbnail_db_);
if (!EnsureInitializedAndUpdated())
return NULL;
transaction.Commit();
return QueryHistoryAndBookmarksInternal(projections, selection,
selection_args, sort_order);
}
bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
const HistoryAndBookmarkRow& row,
const std::string& selection,
const std::vector<string16>& selection_args,
int* updated_count) {
HistoryNotifications notifications;
ScopedTransaction transaction(history_db_, thumbnail_db_);
if (!UpdateHistoryAndBookmarks(row, selection, selection_args, updated_count,
&notifications))
return false;
transaction.Commit();
BroadcastNotifications(notifications);
return true;
}
AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
const HistoryAndBookmarkRow& values) {
HistoryNotifications notifications;
ScopedTransaction transaction(history_db_, thumbnail_db_);
AndroidURLID id = InsertHistoryAndBookmark(values, &notifications, true);
if (id) {
transaction.Commit();
BroadcastNotifications(notifications);
return id;
}
return 0;
}
bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
const std::string& selection,
const std::vector<string16>& selection_args,
int* deleted_count) {
HistoryNotifications notifications;
ScopedTransaction transaction(history_db_, thumbnail_db_);
if (!DeleteHistoryAndBookmarks(selection, selection_args, deleted_count,
&notifications))
return false;
transaction.Commit();
BroadcastNotifications(notifications);
return true;
}
bool AndroidProviderBackend::DeleteHistory(
const std::string& selection,
const std::vector<string16>& selection_args,
int* deleted_count) {
HistoryNotifications notifications;
ScopedTransaction transaction(history_db_, thumbnail_db_);
if (!DeleteHistory(selection, selection_args, deleted_count,
&notifications))
return false;
transaction.Commit();
BroadcastNotifications(notifications);
return true;
}
AndroidProviderBackend::HistoryNotification::HistoryNotification(
int type,
HistoryDetails* detail)
: type(type),
detail(detail) {
}
AndroidProviderBackend::HistoryNotification::~HistoryNotification() {
}
AndroidProviderBackend::ScopedTransaction::ScopedTransaction(
HistoryDatabase* history_db,
ThumbnailDatabase* thumbnail_db)
: history_db_(history_db),
thumbnail_db_(thumbnail_db),
committed_(false),
history_transaction_nesting_(history_db_->transaction_nesting()),
thumbnail_transaction_nesting_(
thumbnail_db_ ? thumbnail_db_->transaction_nesting() : 0) {
// Commit all existing transactions since the AndroidProviderBackend's
// transaction is very like to be rolled back when compared with the others.
// The existing transactions have been scheduled to commit by
// ScheduleCommit in HistoryBackend and the same number of transaction
// will be created after this scoped transaction ends, there should have no
// issue to directly commit all transactions here.
int count = history_transaction_nesting_;
while (count--)
history_db_->CommitTransaction();
history_db_->BeginTransaction();
if (thumbnail_db_) {
count = thumbnail_transaction_nesting_;
while (count--)
thumbnail_db_->CommitTransaction();
thumbnail_db_->BeginTransaction();
}
}
AndroidProviderBackend::ScopedTransaction::~ScopedTransaction() {
if (!committed_) {
history_db_->RollbackTransaction();
if (thumbnail_db_)
thumbnail_db_->RollbackTransaction();
}
// There is no transaction now.
DCHECK_EQ(0, history_db_->transaction_nesting());
DCHECK(!thumbnail_db_ || 0 == thumbnail_db_->transaction_nesting());
int count = history_transaction_nesting_;
while (count--)
history_db_->BeginTransaction();
if (thumbnail_db_) {
count = thumbnail_transaction_nesting_;
while (count--)
thumbnail_db_->BeginTransaction();
}
}
void AndroidProviderBackend::ScopedTransaction::Commit() {
DCHECK(!committed_);
history_db_->CommitTransaction();
if (thumbnail_db_)
thumbnail_db_->CommitTransaction();
committed_ = true;
}
bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
const HistoryAndBookmarkRow& row,
const std::string& selection,
const std::vector<string16>& selection_args,
int* updated_count,
HistoryNotifications* notifications) {
if (!IsHistoryAndBookmarkRowValid(row))
return false;
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::ID))
return false;
if (!EnsureInitializedAndUpdated())
return false;
TableIDRows ids_set;
if (!GetSelectedURLs(selection, selection_args, &ids_set))
return false;
if (ids_set.empty()) {
*updated_count = 0;
return true;
}
// URL can not be updated, we simulate the update.
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::URL)) {
// Only one row's URL can be updated at a time as we can't have multiple
// rows have the same URL.
if (ids_set.size() != 1)
return false;
HistoryAndBookmarkRow new_row = row;
if (!SimulateUpdateURL(new_row, ids_set, notifications))
return false;
*updated_count = 1;
return true;
}
for (std::vector<SQLHandler*>::iterator i =
sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
if ((*i)->HasColumnIn(row)) {
if (!(*i)->Update(row, ids_set))
return false;
}
}
*updated_count = ids_set.size();
scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails);
for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
++i) {
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE) ||
row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) ||
row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
URLRow url_row;
if (!history_db_->GetURLRow(i->url_id, &url_row))
return false;
modified->changed_urls.push_back(url_row);
}
if (thumbnail_db_ &&
row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON))
favicon->urls.insert(i->url);
}
if (!modified->changed_urls.empty())
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, modified.release()));
if (!favicon->urls.empty())
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_FAVICON_CHANGED, favicon.release()));
return true;
}
AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
const HistoryAndBookmarkRow& values,
HistoryNotifications* notifications,
bool ensure_initialized_and_updated) {
if (!IsHistoryAndBookmarkRowValid(values))
return false;
if (ensure_initialized_and_updated && !EnsureInitializedAndUpdated())
return 0;
DCHECK(values.is_value_set_explicitly(HistoryAndBookmarkRow::URL));
// Make a copy of values as we need change it during insert.
HistoryAndBookmarkRow row = values;
for (std::vector<SQLHandler*>::iterator i =
sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
if (!(*i)->Insert(&row))
return 0;
}
URLRow url_row;
if (!history_db_->GetURLRow(row.url_id(), &url_row))
return false;
scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
if (!modified.get())
return false;
modified->changed_urls.push_back(url_row);
scoped_ptr<FaviconChangedDetails> favicon;
// No favicon should be changed if the thumbnail_db_ is not available.
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON) &&
row.favicon_valid() && thumbnail_db_) {
favicon.reset(new FaviconChangedDetails);
if (!favicon.get())
return false;
favicon->urls.insert(url_row.url());
}
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, modified.release()));
if (favicon.get())
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_FAVICON_CHANGED, favicon.release()));
return row.id();
}
bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
const std::string& selection,
const std::vector<string16>& selection_args,
int * deleted_count,
HistoryNotifications* notifications) {
if (!EnsureInitializedAndUpdated())
return false;
TableIDRows ids_set;
if (!GetSelectedURLs(selection, selection_args, &ids_set))
return false;
if (ids_set.empty()) {
*deleted_count = 0;
return true;
}
if (!DeleteHistoryInternal(ids_set, true, notifications))
return false;
*deleted_count = ids_set.size();
return true;
}
bool AndroidProviderBackend::DeleteHistory(
const std::string& selection,
const std::vector<string16>& selection_args,
int* deleted_count,
HistoryNotifications* notifications) {
if (!EnsureInitializedAndUpdated())
return false;
TableIDRows ids_set;
if (!GetSelectedURLs(selection, selection_args, &ids_set))
return false;
if (ids_set.empty()) {
*deleted_count = 0;
return true;
}
*deleted_count = ids_set.size();
// Get the bookmarked rows.
std::vector<HistoryAndBookmarkRow> bookmarks;
for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
++i) {
if (i->bookmarked) {
AndroidURLRow android_url_row;
if (!history_db_->GetAndroidURLRow(i->url_id, &android_url_row))
return false;
HistoryAndBookmarkRow row;
row.set_raw_url(android_url_row.raw_url);
row.set_url(i->url);
// Set the visit time to the UnixEpoch since that's when the Android
// system time starts. The Android have a CTS testcase for this.
row.set_last_visit_time(Time::UnixEpoch());
row.set_visit_count(0);
// We don't want to change the bookmark model, so set_is_bookmark() is
// not called.
bookmarks.push_back(row);
}
}
// Don't delete the bookmark from bookmark model when deleting the history.
if (!DeleteHistoryInternal(ids_set, false, notifications))
return false;
for (std::vector<HistoryAndBookmarkRow>::const_iterator i = bookmarks.begin();
i != bookmarks.end(); ++i) {
// Don't update the tables, otherwise, the bookmarks will be added to
// database during UpdateBookmark(), then the insertion will fail.
// We can't rely on UpdateBookmark() to insert the bookmarks into history
// database as the raw_url will be lost.
if (!InsertHistoryAndBookmark(*i, notifications, false))
return false;
}
return true;
}
AndroidStatement* AndroidProviderBackend::QuerySearchTerms(
const std::vector<SearchRow::ColumnID>& projections,
const std::string& selection,
const std::vector<string16>& selection_args,
const std::string& sort_order) {
if (projections.empty())
return NULL;
if (!EnsureInitializedAndUpdated())
return NULL;
std::string sql;
sql.append("SELECT ");
AppendSearchResultColumn(projections, &sql);
sql.append(" FROM android_cache_db.search_terms ");
if (!selection.empty()) {
sql.append(" WHERE ");
sql.append(selection);
}
if (!sort_order.empty()) {
sql.append(" ORDER BY ");
sql.append(sort_order);
}
scoped_ptr<sql::Statement> statement(new sql::Statement(
db_->GetUniqueStatement(sql.c_str())));
int count = 0;
BindStatement(selection_args, statement.get(), &count);
if (!statement->is_valid()) {
LOG(ERROR) << db_->GetErrorMessage();
return NULL;
}
sql::Statement* result = statement.release();
return new AndroidStatement(result, -1);
}
bool AndroidProviderBackend::UpdateSearchTerms(
const SearchRow& row,
const std::string& selection,
const std::vector<string16>& selection_args,
int* update_count) {
if (!EnsureInitializedAndUpdated())
return false;
SearchTerms search_terms;
if (!GetSelectedSearchTerms(selection, selection_args, &search_terms))
return false;
// We can not update search term if multiple row selected.
if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM) &&
search_terms.size() > 1)
return false;
*update_count = search_terms.size();
if (search_terms.empty())
return true;
if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM)) {
SearchTermRow search_term_row;
SearchRow search_row = row;
if (!history_db_->GetSearchTerm(search_terms[0], &search_term_row))
return false;
search_term_row.term = search_row.search_term();
if (!search_row.is_value_set_explicitly(SearchRow::SEARCH_TIME))
search_row.set_search_time(search_term_row.last_visit_time);
else
search_term_row.last_visit_time = search_row.search_time();
// Delete the original search term.
if (!history_db_->DeleteKeywordSearchTerm(search_terms[0]))
return false;
// Add the new one.
if (!AddSearchTerm(search_row))
return false;
// Update the cache table so the id will not be changed.
if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
return false;
return true;
}
for (SearchTerms::const_iterator i = search_terms.begin();
i != search_terms.end(); ++i) {
SearchTermRow search_term_row;
if (!history_db_->GetSearchTerm(*i, &search_term_row))
return false;
// Check whether the given search time less than the existing one.
if (search_term_row.last_visit_time > row.search_time())
return false;
std::vector<KeywordSearchTermRow> search_term_rows;
if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) ||
search_term_rows.empty())
return false;
// Actually only search_time update. As there might multiple URLs
// asocciated with the keyword, Just update the first one's last_visit_time.
URLRow url_row;
if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row))
return false;
HistoryAndBookmarkRow bookmark_row;
bookmark_row.set_last_visit_time(row.search_time());
TableIDRow table_id_row;
table_id_row.url_id = url_row.id();
TableIDRows table_id_rows;
table_id_rows.push_back(table_id_row);
if (!urls_handler_->Update(bookmark_row, table_id_rows))
return false;
if (!visit_handler_->Update(bookmark_row, table_id_rows))
return false;
}
return true;
}
SearchTermID AndroidProviderBackend::InsertSearchTerm(
const SearchRow& values) {
if (!EnsureInitializedAndUpdated())
return 0;
if (!AddSearchTerm(values))
return 0;
SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL);
if (!id)
// Note the passed in Time() will be changed in UpdateSearchTermTable().
id = history_db_->AddSearchTerm(values.search_term(), Time());
return id;
}
bool AndroidProviderBackend::DeleteSearchTerms(
const std::string& selection,
const std::vector<string16>& selection_args,
int * deleted_count) {
if (!EnsureInitializedAndUpdated())
return false;
SearchTerms rows;
if (!GetSelectedSearchTerms(selection, selection_args, &rows))
return false;
*deleted_count = rows.size();
if (rows.empty())
return true;
for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i)
if (!history_db_->DeleteKeywordSearchTerm(*i))
return false;
// We don't delete the rows in search_terms table, as once the
// search_terms table is updated with keyword_search_terms, all
// keyword cache not found in the keyword_search_terms will be removed.
return true;
}
bool AndroidProviderBackend::EnsureInitializedAndUpdated() {
if (!initialized_) {
if (!Init())
return false;
}
return UpdateTables();
}
bool AndroidProviderBackend::Init() {
urls_handler_.reset(new UrlsSQLHandler(history_db_));
visit_handler_.reset(new VisitSQLHandler(history_db_));
android_urls_handler_.reset(new AndroidURLsSQLHandler(history_db_));
if (thumbnail_db_)
favicon_handler_.reset(new FaviconSQLHandler(thumbnail_db_));
bookmark_model_handler_.reset(new BookmarkModelSQLHandler(history_db_));
// The urls_handler must be pushed first, because the subsequent handlers
// depend on its output.
sql_handlers_.push_back(urls_handler_.get());
sql_handlers_.push_back(visit_handler_.get());
sql_handlers_.push_back(android_urls_handler_.get());
if (favicon_handler_.get())
sql_handlers_.push_back(favicon_handler_.get());
sql_handlers_.push_back(bookmark_model_handler_.get());
if (!history_db_->CreateAndroidURLsTable())
return false;
if (sql::INIT_OK != history_db_->InitAndroidCacheDatabase(
android_cache_db_filename_))
return false;
initialized_ = true;
return true;
}
bool AndroidProviderBackend::UpdateTables() {
if (!UpdateVisitedURLs()) {
LOG(ERROR) << "Update of the visisted URLS failed";
return false;
}
if (!UpdateRemovedURLs()) {
LOG(ERROR) << "Update of the removed URLS failed";
return false;
}
if (!UpdateBookmarks()) {
LOG(ERROR) << "Update of the bookmarks failed";
return false;
}
if (!UpdateFavicon()) {
LOG(ERROR) << "Update of the icons failed";
return false;
}
if (!UpdateSearchTermTable()) {
LOG(ERROR) << "Update of the search_terms failed";
return false;
}
return true;
}
bool AndroidProviderBackend::UpdateVisitedURLs() {
std::string sql(kURLUpdateClause);
sql.append("WHERE urls.id NOT IN (SELECT url_id FROM android_urls)");
sql::Statement urls_statement(db_->GetCachedStatement(SQL_FROM_HERE,
sql.c_str()));
if (!urls_statement.is_valid()) {
LOG(ERROR) << db_->GetErrorMessage();
return false;
}
while (urls_statement.Step()) {
if (history_db_->GetAndroidURLRow(urls_statement.ColumnInt64(0), NULL))
continue;
if (!history_db_->AddAndroidURLRow(urls_statement.ColumnString(3),
urls_statement.ColumnInt64(0)))
return false;
}
if (!history_db_->ClearAllBookmarkCache())
return false;
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
kURLUpdateClause));
while (statement.Step()) {
// The last_visit_time and the created time should be same when the visit
// count is 0, this behavior is also required by the Android CTS.
// The created_time could be set to the last_visit_time only when the type
// of the 'created' column is NULL because the left join is used in query
// and there is no row in the visit table when the visit count is 0.
Time last_visit_time = Time::FromInternalValue(statement.ColumnInt64(1));
Time created_time = last_visit_time;
if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL)
created_time = Time::FromInternalValue(statement.ColumnInt64(2));
if (!history_db_->AddBookmarkCacheRow(created_time, last_visit_time,
statement.ColumnInt64(0)))
return false;
}
return true;
}
bool AndroidProviderBackend::UpdateRemovedURLs() {
return history_db_->DeleteUnusedAndroidURLs();
}
bool AndroidProviderBackend::UpdateBookmarks() {
if (bookmark_service_ == NULL) {
LOG(ERROR) << "Bookmark service is not available";
return false;
}
bookmark_service_->BlockTillLoaded();
std::vector<BookmarkService::URLAndTitle> bookmarks;
bookmark_service_->GetBookmarks(&bookmarks);
if (bookmarks.empty())
return true;
std::vector<URLID> url_ids;
for (std::vector<BookmarkService::URLAndTitle>::const_iterator i =
bookmarks.begin(); i != bookmarks.end(); ++i) {
URLID url_id = history_db_->GetRowForURL(i->url, NULL);
if (url_id == 0) {
URLRow url_row(i->url);
url_row.set_title(i->title);
// Set the visit time to the UnixEpoch since that's when the Android
// system time starts. The Android have a CTS testcase for this.
url_row.set_last_visit(Time::UnixEpoch());
url_row.set_hidden(true);
url_id = history_db_->AddURL(url_row);
if (url_id == 0) {
LOG(ERROR) << "Can not add url for the new bookmark";
return false;
}
if (!history_db_->AddAndroidURLRow(i->url.spec(), url_id))
return false;
if (!history_db_->AddBookmarkCacheRow(Time::UnixEpoch(),
Time::UnixEpoch(), url_id))
return false;
}
url_ids.push_back(url_id);
}
return history_db_->MarkURLsAsBookmarked(url_ids);
}
bool AndroidProviderBackend::UpdateFavicon() {
ThumbnailDatabase::IconMappingEnumerator enumerator;
// We want the AndroidProviderBackend run without thumbnail_db_
if (!thumbnail_db_)
return true;
if (!thumbnail_db_->InitIconMappingEnumerator(chrome::FAVICON, &enumerator))
return false;
IconMapping icon_mapping;
while (enumerator.GetNextIconMapping(&icon_mapping)) {
URLID url_id = history_db_->GetRowForURL(icon_mapping.page_url, NULL);
if (url_id == 0) {
LOG(ERROR) << "Can not find favicon's page url";
continue;
}
history_db_->SetFaviconID(url_id, icon_mapping.icon_id);
}
return true;
}
bool AndroidProviderBackend::UpdateSearchTermTable() {
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
kSearchTermUpdateClause));
while (statement.Step()) {
base::string16 term = statement.ColumnString16(0);
Time last_visit_time = Time::FromInternalValue(statement.ColumnInt64(1));
SearchTermRow search_term_row;
if (history_db_->GetSearchTerm(term, &search_term_row)) {
if (search_term_row.last_visit_time != last_visit_time) {
search_term_row.last_visit_time = last_visit_time;
if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
return false;
}
} else {
if (!history_db_->AddSearchTerm(term, last_visit_time))
return false;
}
}
if (!history_db_->DeleteUnusedSearchTerms())
return false;
return true;
}
int AndroidProviderBackend::AppendBookmarkResultColumn(
const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
std::string* result_column) {
int replaced_index = -1;
// Attach the projections
bool first = true;
int index = 0;
for (std::vector<HistoryAndBookmarkRow::ColumnID>::const_iterator i =
projections.begin(); i != projections.end(); ++i) {
if (first)
first = false;
else
result_column->append(", ");
if (*i == HistoryAndBookmarkRow::FAVICON)
replaced_index = index;
result_column->append(HistoryAndBookmarkRow::GetAndroidName(*i));
index++;
}
return replaced_index;
}
bool AndroidProviderBackend::GetSelectedURLs(
const std::string& selection,
const std::vector<string16>& selection_args,
TableIDRows* rows) {
std::string sql("SELECT url_id, urls_url, bookmark FROM (");
sql.append(kVirtualHistoryAndBookmarkTable);
sql.append(" )");
if (!selection.empty()) {
sql.append(" WHERE ");
sql.append(selection);
}
sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
int count = 0;
BindStatement(selection_args, &statement, &count);
if (!statement.is_valid()) {
LOG(ERROR) << db_->GetErrorMessage();
return false;
}
while (statement.Step()) {
TableIDRow row;
row.url_id = statement.ColumnInt64(0);
row.url = GURL(statement.ColumnString(1));
row.bookmarked = statement.ColumnBool(2);
rows->push_back(row);
}
return true;
}
bool AndroidProviderBackend::GetSelectedSearchTerms(
const std::string& selection,
const std::vector<string16>& selection_args,
SearchTerms* rows) {
std::string sql("SELECT search "
"FROM android_cache_db.search_terms ");
if (!selection.empty()) {
sql.append(" WHERE ");
sql.append(selection);
}
sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
int count = 0;
BindStatement(selection_args, &statement, &count);
if (!statement.is_valid()) {
LOG(ERROR) << db_->GetErrorMessage();
return false;
}
while (statement.Step()) {
rows->push_back(statement.ColumnString16(0));
}
return true;
}
void AndroidProviderBackend::AppendSearchResultColumn(
const std::vector<SearchRow::ColumnID>& projections,
std::string* result_column) {
bool first = true;
int index = 0;
for (std::vector<SearchRow::ColumnID>::const_iterator i =
projections.begin(); i != projections.end(); ++i) {
if (first)
first = false;
else
result_column->append(", ");
result_column->append(SearchRow::GetAndroidName(*i));
index++;
}
}
bool AndroidProviderBackend::SimulateUpdateURL(
const HistoryAndBookmarkRow& row,
const TableIDRows& ids,
HistoryNotifications* notifications) {
DCHECK(ids.size() == 1);
// URL can not be updated, we simulate the update by deleting the old URL
// and inserting the new one; We do update the android_urls table as the id
// need to keep same.
// Find all columns value of the current URL.
std::vector<HistoryAndBookmarkRow::ColumnID> projections;
projections.push_back(HistoryAndBookmarkRow::LAST_VISIT_TIME);
projections.push_back(HistoryAndBookmarkRow::CREATED);
projections.push_back(HistoryAndBookmarkRow::VISIT_COUNT);
projections.push_back(HistoryAndBookmarkRow::TITLE);
projections.push_back(HistoryAndBookmarkRow::FAVICON);
projections.push_back(HistoryAndBookmarkRow::BOOKMARK);
std::ostringstream oss;
oss << "url_id = " << ids[0].url_id;
scoped_ptr<AndroidStatement> statement;
statement.reset(QueryHistoryAndBookmarksInternal(projections, oss.str(),
std::vector<string16>(), std::string()));
if (!statement.get() || !statement->statement()->Step())
return false;
HistoryAndBookmarkRow new_row;
new_row.set_last_visit_time(FromDatabaseTime(
statement->statement()->ColumnInt64(0)));
new_row.set_created(FromDatabaseTime(
statement->statement()->ColumnInt64(1)));
new_row.set_visit_count(statement->statement()->ColumnInt(2));
new_row.set_title(statement->statement()->ColumnString16(3));
scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
scoped_ptr<FaviconChangedDetails> favicon_details(new FaviconChangedDetails);
scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
URLRow old_url_row;
if (!history_db_->GetURLRow(ids[0].url_id, &old_url_row))
return false;
deleted_details->rows.push_back(old_url_row);
chrome::FaviconID favicon_id = statement->statement()->ColumnInt64(4);
if (favicon_id) {
std::vector<FaviconBitmap> favicon_bitmaps;
if (!thumbnail_db_ ||
!thumbnail_db_->GetFaviconBitmaps(favicon_id, &favicon_bitmaps))
return false;
scoped_refptr<base::RefCountedMemory> bitmap_data =
favicon_bitmaps[0].bitmap_data;
if (bitmap_data.get() && bitmap_data->size())
new_row.set_favicon(bitmap_data);
favicon_details->urls.insert(old_url_row.url());
favicon_details->urls.insert(row.url());
}
new_row.set_is_bookmark(statement->statement()->ColumnBool(5));
// The SQLHandler vector is not used here because the row in android_url
// shouldn't be deleted, we need keep the AndroidUIID unchanged, so it
// appears update to the client.
if (!urls_handler_->Delete(ids))
return false;
if (!visit_handler_->Delete(ids))
return false;
if (favicon_handler_ && !favicon_handler_->Delete(ids))
return false;
if (!bookmark_model_handler_->Delete(ids))
return false;
new_row.set_url(row.url());
new_row.set_raw_url(row.raw_url());
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME))
new_row.set_last_visit_time(row.last_visit_time());
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED))
new_row.set_created(row.created());
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT))
new_row.set_visit_count(row.visit_count());
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE))
new_row.set_title(row.title());
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON)) {
new_row.set_favicon(row.favicon());
favicon_details->urls.insert(new_row.url());
}
if (row.is_value_set_explicitly(HistoryAndBookmarkRow::BOOKMARK))
new_row.set_is_bookmark(row.is_bookmark());
if (!urls_handler_->Insert(&new_row))
return false;
if (!visit_handler_->Insert(&new_row))
return false;
// Update the current row instead of inserting a new row in android urls
// table. We need keep the AndroidUIID unchanged, so it appears update
// to the client.
if (!android_urls_handler_->Update(new_row, ids))
return false;
if (favicon_handler_ && !favicon_handler_->Insert(&new_row))
return false;
if (!bookmark_model_handler_->Insert(&new_row))
return false;
URLRow new_url_row;
if (!history_db_->GetURLRow(new_row.url_id(), &new_url_row))
return false;
modified->changed_urls.push_back(new_url_row);
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_HISTORY_URLS_DELETED,
deleted_details.release()));
if (favicon_details.get() && !favicon_details->urls.empty())
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_FAVICON_CHANGED, favicon_details.release()));
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, modified.release()));
return true;
}
AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarksInternal(
const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
const std::string& selection,
const std::vector<string16>& selection_args,
const std::string& sort_order) {
std::string sql;
sql.append("SELECT ");
int replaced_index = AppendBookmarkResultColumn(projections, &sql);
sql.append(" FROM (");
sql.append(kVirtualHistoryAndBookmarkTable);
sql.append(")");
if (!selection.empty()) {
sql.append(" WHERE ");
sql.append(selection);
}
if (!sort_order.empty()) {
sql.append(" ORDER BY ");
sql.append(sort_order);
}
scoped_ptr<sql::Statement> statement(new sql::Statement(
db_->GetUniqueStatement(sql.c_str())));
int count = 0;
BindStatement(selection_args, statement.get(), &count);
if (!statement->is_valid()) {
LOG(ERROR) << db_->GetErrorMessage();
return NULL;
}
sql::Statement* result = statement.release();
return new AndroidStatement(result, replaced_index);
}
bool AndroidProviderBackend::DeleteHistoryInternal(
const TableIDRows& urls,
bool delete_bookmarks,
HistoryNotifications* notifications) {
scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails);
for (TableIDRows::const_iterator i = urls.begin(); i != urls.end(); ++i) {
URLRow url_row;
if (!history_db_->GetURLRow(i->url_id, &url_row))
return false;
deleted_details->rows.push_back(url_row);
if (thumbnail_db_ &&
thumbnail_db_->GetIconMappingsForPageURL(url_row.url(), NULL))
favicon->urls.insert(url_row.url());
}
// Only invoke Delete on the BookmarkModelHandler if we need
// to delete bookmarks.
for (std::vector<SQLHandler*>::iterator i =
sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
if ((*i) != bookmark_model_handler_.get() || delete_bookmarks)
if (!(*i)->Delete(urls))
return false;
}
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_HISTORY_URLS_DELETED,
deleted_details.release()));
if (favicon.get() && !favicon->urls.empty())
notifications->push_back(HistoryNotification(
chrome::NOTIFICATION_FAVICON_CHANGED, favicon.release()));
return true;
}
void AndroidProviderBackend::BroadcastNotifications(
const HistoryNotifications& notifications) {
for (HistoryNotifications::const_iterator i = notifications.begin();
i != notifications.end(); ++i) {
delegate_->BroadcastNotifications(i->type, i->detail);
}
}
bool AndroidProviderBackend::AddSearchTerm(const SearchRow& values) {
DCHECK(values.is_value_set_explicitly(SearchRow::SEARCH_TERM));
DCHECK(values.is_value_set_explicitly(SearchRow::TEMPLATE_URL));
DCHECK(values.is_value_set_explicitly(SearchRow::URL));
URLRow url_row;
HistoryAndBookmarkRow bookmark_row;
// Android CTS test BrowserTest.testAccessSearches allows insert the same
// seach term multiple times, and just search time need updated.
if (history_db_->GetRowForURL(values.url(), &url_row)) {
// Already exist, Add a visit.
if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
bookmark_row.set_last_visit_time(values.search_time());
else
bookmark_row.set_visit_count(url_row.visit_count() + 1);
TableIDRows table_id_rows;
TableIDRow table_id_row;
table_id_row.url = values.url();
table_id_row.url_id = url_row.id();
table_id_rows.push_back(table_id_row);
if (!urls_handler_->Update(bookmark_row, table_id_rows))
return false;
if (!visit_handler_->Update(bookmark_row, table_id_rows))
return false;
if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL))
if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(),
values.template_url_id(), values.search_term()))
return false;
} else {
bookmark_row.set_raw_url(values.url().spec());
bookmark_row.set_url(values.url());
if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
bookmark_row.set_last_visit_time(values.search_time());
if (!urls_handler_->Insert(&bookmark_row))
return false;
if (!visit_handler_->Insert(&bookmark_row))
return false;
if (!android_urls_handler_->Insert(&bookmark_row))
return false;
if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(),
values.template_url_id(), values.search_term()))
return false;
}
return true;
}
} // namespace history