普通文本  |  381行  |  11.88 KB

// Copyright (c) 2009 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 "app/sql/connection.h"
#include "app/sql/transaction.h"
#include "base/file_util.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/top_sites.h"
#include "chrome/browser/history/top_sites_database.h"

namespace history {

static const int kVersionNumber = 1;

TopSitesDatabase::TopSitesDatabase() : may_need_history_migration_(false) {
}

TopSitesDatabase::~TopSitesDatabase() {
}

bool TopSitesDatabase::Init(const FilePath& db_name) {
  bool file_existed = file_util::PathExists(db_name);

  if (!file_existed)
    may_need_history_migration_ = true;

  db_.reset(CreateDB(db_name));
  if (!db_.get())
    return false;

  bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get());
  if (!does_meta_exist && file_existed) {
    may_need_history_migration_ = true;

    // If the meta file doesn't exist, this version is old. We could remove all
    // the entries as they are no longer applicable, but it's safest to just
    // remove the file and start over.
    db_.reset(NULL);
    if (!file_util::Delete(db_name, false) &&
        !file_util::Delete(db_name, false)) {
      // Try to delete twice. If we can't, fail.
      LOG(ERROR) << "unable to delete old TopSites file";
      return false;
    }
    db_.reset(CreateDB(db_name));
    if (!db_.get())
      return false;
  }

  if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber))
    return false;

  if (!InitThumbnailTable())
    return false;

  // Version check.
  if (meta_table_.GetVersionNumber() != kVersionNumber)
    return false;

  return true;
}

bool TopSitesDatabase::InitThumbnailTable() {
  if (!db_->DoesTableExist("thumbnails")) {
    if (!db_->Execute("CREATE TABLE thumbnails ("
                      "url LONGVARCHAR PRIMARY KEY,"
                      "url_rank INTEGER ,"
                      "title LONGVARCHAR,"
                      "thumbnail BLOB,"
                      "redirects LONGVARCHAR,"
                      "boring_score DOUBLE DEFAULT 1.0, "
                      "good_clipping INTEGER DEFAULT 0, "
                      "at_top INTEGER DEFAULT 0, "
                      "last_updated INTEGER DEFAULT 0) ")) {
      LOG(WARNING) << db_->GetErrorMessage();
      return false;
    }
  }
  return true;
}

void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
                                             URLToImagesMap* thumbnails) {
  sql::Statement statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT url, url_rank, title, thumbnail, redirects, "
      "boring_score, good_clipping, at_top, last_updated "
      "FROM thumbnails ORDER BY url_rank "));

  if (!statement) {
    LOG(WARNING) << db_->GetErrorMessage();
    return;
  }

  urls->clear();
  thumbnails->clear();

  while (statement.Step()) {
    // Results are sorted by url_rank.
    MostVisitedURL url;
    GURL gurl(statement.ColumnString(0));
    url.url = gurl;
    url.title = statement.ColumnString16(2);
    std::string redirects = statement.ColumnString(4);
    SetRedirects(redirects, &url);
    urls->push_back(url);

    std::vector<unsigned char> data;
    statement.ColumnBlobAsVector(3, &data);
    Images thumbnail;
    thumbnail.thumbnail = RefCountedBytes::TakeVector(&data);
    thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5);
    thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6);
    thumbnail.thumbnail_score.at_top = statement.ColumnBool(7);
    thumbnail.thumbnail_score.time_at_snapshot =
        base::Time::FromInternalValue(statement.ColumnInt64(8));

    (*thumbnails)[gurl] = thumbnail;
  }
}

// static
std::string TopSitesDatabase::GetRedirects(const MostVisitedURL& url) {
  std::vector<std::string> redirects;
  for (size_t i = 0; i < url.redirects.size(); i++)
    redirects.push_back(url.redirects[i].spec());
  return JoinString(redirects, ' ');
}

// static
void TopSitesDatabase::SetRedirects(const std::string& redirects,
                                    MostVisitedURL* url) {
  std::vector<std::string> redirects_vector;
  base::SplitStringAlongWhitespace(redirects, &redirects_vector);
  for (size_t i = 0; i < redirects_vector.size(); ++i)
    url->redirects.push_back(GURL(redirects_vector[i]));
}

void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url,
                                            int new_rank,
                                            const Images& thumbnail) {
  sql::Transaction transaction(db_.get());
  transaction.Begin();

  int rank = GetURLRank(url);
  if (rank == -1) {
    AddPageThumbnail(url, new_rank, thumbnail);
  } else {
    UpdatePageRankNoTransaction(url, new_rank);
    UpdatePageThumbnail(url, thumbnail);
  }

  transaction.Commit();
}

void TopSitesDatabase::UpdatePageThumbnail(
    const MostVisitedURL& url, const Images& thumbnail) {
  sql::Statement statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "UPDATE thumbnails SET "
      "title = ?, thumbnail = ?, redirects = ?, "
      "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ? "
      "WHERE url = ? "));
  if (!statement)
    return;

  statement.BindString16(0, url.title);
  if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    statement.BindBlob(1, thumbnail.thumbnail->front(),
                       static_cast<int>(thumbnail.thumbnail->size()));
  }
  statement.BindString(2, GetRedirects(url));
  const ThumbnailScore& score = thumbnail.thumbnail_score;
  statement.BindDouble(3, score.boring_score);
  statement.BindBool(4, score.good_clipping);
  statement.BindBool(5, score.at_top);
  statement.BindInt64(6, score.time_at_snapshot.ToInternalValue());
  statement.BindString(7, url.url.spec());
  if (!statement.Run())
    NOTREACHED() << db_->GetErrorMessage();
}

void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
                                            int new_rank,
                                            const Images& thumbnail) {
  int count = GetRowCount();

  sql::Statement statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "INSERT OR REPLACE INTO thumbnails "
      "(url, url_rank, title, thumbnail, redirects, "
      "boring_score, good_clipping, at_top, last_updated) "
      "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"));
  if (!statement)
    return;

  statement.BindString(0, url.url.spec());
  statement.BindInt(1, count);  // Make it the last url.
  statement.BindString16(2, url.title);
  if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    statement.BindBlob(3, thumbnail.thumbnail->front(),
                       static_cast<int>(thumbnail.thumbnail->size()));
  }
  statement.BindString(4, GetRedirects(url));
  const ThumbnailScore& score = thumbnail.thumbnail_score;
  statement.BindDouble(5, score.boring_score);
  statement.BindBool(6, score.good_clipping);
  statement.BindBool(7, score.at_top);
  statement.BindInt64(8, score.time_at_snapshot.ToInternalValue());
  if (!statement.Run())
    NOTREACHED() << db_->GetErrorMessage();

  UpdatePageRankNoTransaction(url, new_rank);
}

void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
                                          int new_rank) {
  sql::Transaction transaction(db_.get());
  transaction.Begin();
  UpdatePageRankNoTransaction(url, new_rank);
  transaction.Commit();
}

// Caller should have a transaction open.
void TopSitesDatabase::UpdatePageRankNoTransaction(
    const MostVisitedURL& url, int new_rank) {
  int prev_rank = GetURLRank(url);
  if (prev_rank == -1) {
    LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec();
    return;
  }

  // Shift the ranks.
  if (prev_rank > new_rank) {
    // Shift up
    sql::Statement shift_statement(db_->GetCachedStatement(
        SQL_FROM_HERE,
        "UPDATE thumbnails "
        "SET url_rank = url_rank + 1 "
        "WHERE url_rank >= ? AND url_rank < ?"));
    shift_statement.BindInt(0, new_rank);
    shift_statement.BindInt(1, prev_rank);
    if (shift_statement)
      shift_statement.Run();
  } else if (prev_rank < new_rank) {
    // Shift down
    sql::Statement shift_statement(db_->GetCachedStatement(
        SQL_FROM_HERE,
        "UPDATE thumbnails "
        "SET url_rank = url_rank - 1 "
        "WHERE url_rank > ? AND url_rank <= ?"));
    shift_statement.BindInt(0, prev_rank);
    shift_statement.BindInt(1, new_rank);
    if (shift_statement)
      shift_statement.Run();
  }

  // Set the url's rank.
  sql::Statement set_statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "UPDATE thumbnails "
      "SET url_rank = ? "
      "WHERE url == ?"));
  set_statement.BindInt(0, new_rank);
  set_statement.BindString(1, url.url.spec());
  if (set_statement)
    set_statement.Run();
}

bool TopSitesDatabase::GetPageThumbnail(const GURL& url,
                                            Images* thumbnail) {
  sql::Statement statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated "
      "FROM thumbnails WHERE url=?"));

  if (!statement) {
    LOG(WARNING) << db_->GetErrorMessage();
    return false;
  }

  statement.BindString(0, url.spec());
  if (!statement.Step())
    return false;

  std::vector<unsigned char> data;
  statement.ColumnBlobAsVector(0, &data);
  thumbnail->thumbnail = RefCountedBytes::TakeVector(&data);
  thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1);
  thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2);
  thumbnail->thumbnail_score.at_top = statement.ColumnBool(3);
  thumbnail->thumbnail_score.time_at_snapshot =
      base::Time::FromInternalValue(statement.ColumnInt64(4));
  return true;
}

int TopSitesDatabase::GetRowCount() {
  int result = 0;
  sql::Statement select_statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT COUNT (url) FROM thumbnails"));
  if (!select_statement) {
    LOG(WARNING) << db_->GetErrorMessage();
    return result;
  }

  if (select_statement.Step())
    result = select_statement.ColumnInt(0);

  return result;
}

int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) {
  int result = -1;
  sql::Statement select_statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT url_rank "
      "FROM thumbnails WHERE url=?"));
  if (!select_statement) {
    LOG(WARNING) << db_->GetErrorMessage();
    return result;
  }

  select_statement.BindString(0, url.url.spec());
  if (select_statement.Step())
    result = select_statement.ColumnInt(0);

  return result;
}

// Remove the record for this URL. Returns true iff removed successfully.
bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) {
  int old_rank = GetURLRank(url);
  if (old_rank < 0)
    return false;

  sql::Transaction transaction(db_.get());
  transaction.Begin();
  // Decrement all following ranks.
  sql::Statement shift_statement(db_->GetCachedStatement(
      SQL_FROM_HERE,
      "UPDATE thumbnails "
      "SET url_rank = url_rank - 1 "
      "WHERE url_rank > ?"));
  if (!shift_statement)
    return false;
  shift_statement.BindInt(0, old_rank);
  shift_statement.Run();

  sql::Statement delete_statement(
      db_->GetCachedStatement(SQL_FROM_HERE,
                              "DELETE FROM thumbnails WHERE url = ?"));
  if (!delete_statement)
    return false;
  delete_statement.BindString(0, url.url.spec());
  delete_statement.Run();

  return transaction.Commit();
}

sql::Connection* TopSitesDatabase::CreateDB(const FilePath& db_name) {
  scoped_ptr<sql::Connection> db(new sql::Connection());
  // Settings copied from ThumbnailDatabase.
  db->set_error_delegate(GetErrorHandlerForThumbnailDb());
  db->set_page_size(4096);
  db->set_cache_size(32);

  if (!db->Open(db_name)) {
    LOG(ERROR) << db->GetErrorMessage();
    return NULL;
  }

  return db.release();
}

}  // namespace history