Home | History | Annotate | Download | only in history
      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 "base/file_util.h"
      6 #include "base/memory/ref_counted.h"
      7 #include "base/strings/string_split.h"
      8 #include "base/strings/string_util.h"
      9 #include "chrome/browser/history/history_types.h"
     10 #include "chrome/browser/history/top_sites.h"
     11 #include "chrome/browser/history/top_sites_database.h"
     12 #include "sql/connection.h"
     13 #include "sql/transaction.h"
     14 
     15 namespace history {
     16 
     17 // From the version 1 to 2, one column was added. Old versions of Chrome
     18 // should be able to read version 2 files just fine.
     19 static const int kVersionNumber = 2;
     20 
     21 TopSitesDatabase::TopSitesDatabase() : may_need_history_migration_(false) {
     22 }
     23 
     24 TopSitesDatabase::~TopSitesDatabase() {
     25 }
     26 
     27 bool TopSitesDatabase::Init(const base::FilePath& db_name) {
     28   bool file_existed = base::PathExists(db_name);
     29 
     30   if (!file_existed)
     31     may_need_history_migration_ = true;
     32 
     33   db_.reset(CreateDB(db_name));
     34   if (!db_)
     35     return false;
     36 
     37   bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get());
     38   if (!does_meta_exist && file_existed) {
     39     may_need_history_migration_ = true;
     40 
     41     // If the meta file doesn't exist, this version is old. We could remove all
     42     // the entries as they are no longer applicable, but it's safest to just
     43     // remove the file and start over.
     44     db_.reset(NULL);
     45     if (!sql::Connection::Delete(db_name)) {
     46       LOG(ERROR) << "unable to delete old TopSites file";
     47       return false;
     48     }
     49     db_.reset(CreateDB(db_name));
     50     if (!db_)
     51       return false;
     52   }
     53 
     54   // Scope initialization in a transaction so we can't be partially
     55   // initialized.
     56   sql::Transaction transaction(db_.get());
     57   transaction.Begin();
     58 
     59   if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber))
     60     return false;
     61 
     62   if (!InitThumbnailTable())
     63     return false;
     64 
     65   if (meta_table_.GetVersionNumber() == 1) {
     66     if (!UpgradeToVersion2()) {
     67       LOG(WARNING) << "Unable to upgrade top sites database to version 2.";
     68       return false;
     69     }
     70   }
     71 
     72   // Version check.
     73   if (meta_table_.GetVersionNumber() != kVersionNumber)
     74     return false;
     75 
     76   // Initialization is complete.
     77   if (!transaction.Commit())
     78     return false;
     79 
     80   return true;
     81 }
     82 
     83 bool TopSitesDatabase::InitThumbnailTable() {
     84   if (!db_->DoesTableExist("thumbnails")) {
     85     if (!db_->Execute("CREATE TABLE thumbnails ("
     86                       "url LONGVARCHAR PRIMARY KEY,"
     87                       "url_rank INTEGER ,"
     88                       "title LONGVARCHAR,"
     89                       "thumbnail BLOB,"
     90                       "redirects LONGVARCHAR,"
     91                       "boring_score DOUBLE DEFAULT 1.0, "
     92                       "good_clipping INTEGER DEFAULT 0, "
     93                       "at_top INTEGER DEFAULT 0, "
     94                       "last_updated INTEGER DEFAULT 0, "
     95                       "load_completed INTEGER DEFAULT 0) ")) {
     96       LOG(WARNING) << db_->GetErrorMessage();
     97       return false;
     98     }
     99   }
    100   return true;
    101 }
    102 
    103 bool TopSitesDatabase::UpgradeToVersion2() {
    104   // Add 'load_completed' column.
    105   if (!db_->Execute(
    106           "ALTER TABLE thumbnails ADD load_completed INTEGER DEFAULT 0")) {
    107     NOTREACHED();
    108     return false;
    109   }
    110   meta_table_.SetVersionNumber(2);
    111   return true;
    112 }
    113 
    114 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
    115                                          URLToImagesMap* thumbnails) {
    116   sql::Statement statement(db_->GetCachedStatement(
    117       SQL_FROM_HERE,
    118       "SELECT url, url_rank, title, thumbnail, redirects, "
    119       "boring_score, good_clipping, at_top, last_updated, load_completed "
    120       "FROM thumbnails ORDER BY url_rank "));
    121 
    122   if (!statement.is_valid()) {
    123     LOG(WARNING) << db_->GetErrorMessage();
    124     return;
    125   }
    126 
    127   urls->clear();
    128   thumbnails->clear();
    129 
    130   while (statement.Step()) {
    131     // Results are sorted by url_rank.
    132     MostVisitedURL url;
    133     GURL gurl(statement.ColumnString(0));
    134     url.url = gurl;
    135     url.title = statement.ColumnString16(2);
    136     std::string redirects = statement.ColumnString(4);
    137     SetRedirects(redirects, &url);
    138     urls->push_back(url);
    139 
    140     std::vector<unsigned char> data;
    141     statement.ColumnBlobAsVector(3, &data);
    142     Images thumbnail;
    143     if (!data.empty())
    144       thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data);
    145     thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5);
    146     thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6);
    147     thumbnail.thumbnail_score.at_top = statement.ColumnBool(7);
    148     thumbnail.thumbnail_score.time_at_snapshot =
    149         base::Time::FromInternalValue(statement.ColumnInt64(8));
    150     thumbnail.thumbnail_score.load_completed = statement.ColumnBool(9);
    151 
    152     (*thumbnails)[gurl] = thumbnail;
    153   }
    154 }
    155 
    156 // static
    157 std::string TopSitesDatabase::GetRedirects(const MostVisitedURL& url) {
    158   std::vector<std::string> redirects;
    159   for (size_t i = 0; i < url.redirects.size(); i++)
    160     redirects.push_back(url.redirects[i].spec());
    161   return JoinString(redirects, ' ');
    162 }
    163 
    164 // static
    165 void TopSitesDatabase::SetRedirects(const std::string& redirects,
    166                                     MostVisitedURL* url) {
    167   std::vector<std::string> redirects_vector;
    168   base::SplitStringAlongWhitespace(redirects, &redirects_vector);
    169   for (size_t i = 0; i < redirects_vector.size(); ++i)
    170     url->redirects.push_back(GURL(redirects_vector[i]));
    171 }
    172 
    173 void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url,
    174                                             int new_rank,
    175                                             const Images& thumbnail) {
    176   sql::Transaction transaction(db_.get());
    177   transaction.Begin();
    178 
    179   int rank = GetURLRank(url);
    180   if (rank == -1) {
    181     AddPageThumbnail(url, new_rank, thumbnail);
    182   } else {
    183     UpdatePageRankNoTransaction(url, new_rank);
    184     UpdatePageThumbnail(url, thumbnail);
    185   }
    186 
    187   transaction.Commit();
    188 }
    189 
    190 bool TopSitesDatabase::UpdatePageThumbnail(
    191     const MostVisitedURL& url, const Images& thumbnail) {
    192   sql::Statement statement(db_->GetCachedStatement(
    193       SQL_FROM_HERE,
    194       "UPDATE thumbnails SET "
    195       "title = ?, thumbnail = ?, redirects = ?, "
    196       "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, "
    197       "load_completed = ? "
    198       "WHERE url = ? "));
    199   statement.BindString16(0, url.title);
    200   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    201     statement.BindBlob(1, thumbnail.thumbnail->front(),
    202                        static_cast<int>(thumbnail.thumbnail->size()));
    203   }
    204   statement.BindString(2, GetRedirects(url));
    205   const ThumbnailScore& score = thumbnail.thumbnail_score;
    206   statement.BindDouble(3, score.boring_score);
    207   statement.BindBool(4, score.good_clipping);
    208   statement.BindBool(5, score.at_top);
    209   statement.BindInt64(6, score.time_at_snapshot.ToInternalValue());
    210   statement.BindBool(7, score.load_completed);
    211   statement.BindString(8, url.url.spec());
    212 
    213   return statement.Run();
    214 }
    215 
    216 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
    217                                             int new_rank,
    218                                             const Images& thumbnail) {
    219   int count = GetRowCount();
    220 
    221   sql::Statement statement(db_->GetCachedStatement(
    222       SQL_FROM_HERE,
    223       "INSERT OR REPLACE INTO thumbnails "
    224       "(url, url_rank, title, thumbnail, redirects, "
    225       "boring_score, good_clipping, at_top, last_updated, load_completed) "
    226       "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    227   statement.BindString(0, url.url.spec());
    228   statement.BindInt(1, count);  // Make it the last url.
    229   statement.BindString16(2, url.title);
    230   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    231     statement.BindBlob(3, thumbnail.thumbnail->front(),
    232                        static_cast<int>(thumbnail.thumbnail->size()));
    233   }
    234   statement.BindString(4, GetRedirects(url));
    235   const ThumbnailScore& score = thumbnail.thumbnail_score;
    236   statement.BindDouble(5, score.boring_score);
    237   statement.BindBool(6, score.good_clipping);
    238   statement.BindBool(7, score.at_top);
    239   statement.BindInt64(8, score.time_at_snapshot.ToInternalValue());
    240   statement.BindBool(9, score.load_completed);
    241   if (!statement.Run())
    242     return;
    243 
    244   UpdatePageRankNoTransaction(url, new_rank);
    245 }
    246 
    247 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
    248                                           int new_rank) {
    249   sql::Transaction transaction(db_.get());
    250   transaction.Begin();
    251   UpdatePageRankNoTransaction(url, new_rank);
    252   transaction.Commit();
    253 }
    254 
    255 // Caller should have a transaction open.
    256 void TopSitesDatabase::UpdatePageRankNoTransaction(
    257     const MostVisitedURL& url, int new_rank) {
    258   int prev_rank = GetURLRank(url);
    259   if (prev_rank == -1) {
    260     LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec();
    261     return;
    262   }
    263 
    264   // Shift the ranks.
    265   if (prev_rank > new_rank) {
    266     // Shift up
    267     sql::Statement shift_statement(db_->GetCachedStatement(
    268         SQL_FROM_HERE,
    269         "UPDATE thumbnails "
    270         "SET url_rank = url_rank + 1 "
    271         "WHERE url_rank >= ? AND url_rank < ?"));
    272     shift_statement.BindInt(0, new_rank);
    273     shift_statement.BindInt(1, prev_rank);
    274     shift_statement.Run();
    275   } else if (prev_rank < new_rank) {
    276     // Shift down
    277     sql::Statement shift_statement(db_->GetCachedStatement(
    278         SQL_FROM_HERE,
    279         "UPDATE thumbnails "
    280         "SET url_rank = url_rank - 1 "
    281         "WHERE url_rank > ? AND url_rank <= ?"));
    282     shift_statement.BindInt(0, prev_rank);
    283     shift_statement.BindInt(1, new_rank);
    284     shift_statement.Run();
    285   }
    286 
    287   // Set the url's rank.
    288   sql::Statement set_statement(db_->GetCachedStatement(
    289       SQL_FROM_HERE,
    290       "UPDATE thumbnails "
    291       "SET url_rank = ? "
    292       "WHERE url == ?"));
    293   set_statement.BindInt(0, new_rank);
    294   set_statement.BindString(1, url.url.spec());
    295   set_statement.Run();
    296 }
    297 
    298 bool TopSitesDatabase::GetPageThumbnail(const GURL& url,
    299                                             Images* thumbnail) {
    300   sql::Statement statement(db_->GetCachedStatement(
    301       SQL_FROM_HERE,
    302       "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated "
    303       "FROM thumbnails WHERE url=?"));
    304   statement.BindString(0, url.spec());
    305   if (!statement.Step())
    306     return false;
    307 
    308   std::vector<unsigned char> data;
    309   statement.ColumnBlobAsVector(0, &data);
    310   thumbnail->thumbnail = base::RefCountedBytes::TakeVector(&data);
    311   thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1);
    312   thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2);
    313   thumbnail->thumbnail_score.at_top = statement.ColumnBool(3);
    314   thumbnail->thumbnail_score.time_at_snapshot =
    315       base::Time::FromInternalValue(statement.ColumnInt64(4));
    316   return true;
    317 }
    318 
    319 int TopSitesDatabase::GetRowCount() {
    320   sql::Statement select_statement(db_->GetCachedStatement(
    321       SQL_FROM_HERE,
    322       "SELECT COUNT (url) FROM thumbnails"));
    323   if (select_statement.Step())
    324     return select_statement.ColumnInt(0);
    325 
    326   return 0;
    327 }
    328 
    329 int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) {
    330   sql::Statement select_statement(db_->GetCachedStatement(
    331       SQL_FROM_HERE,
    332       "SELECT url_rank "
    333       "FROM thumbnails WHERE url=?"));
    334   select_statement.BindString(0, url.url.spec());
    335   if (select_statement.Step())
    336     return select_statement.ColumnInt(0);
    337 
    338   return -1;
    339 }
    340 
    341 // Remove the record for this URL. Returns true iff removed successfully.
    342 bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) {
    343   int old_rank = GetURLRank(url);
    344   if (old_rank < 0)
    345     return false;
    346 
    347   sql::Transaction transaction(db_.get());
    348   transaction.Begin();
    349   // Decrement all following ranks.
    350   sql::Statement shift_statement(db_->GetCachedStatement(
    351       SQL_FROM_HERE,
    352       "UPDATE thumbnails "
    353       "SET url_rank = url_rank - 1 "
    354       "WHERE url_rank > ?"));
    355   shift_statement.BindInt(0, old_rank);
    356 
    357   if (!shift_statement.Run())
    358     return false;
    359 
    360   sql::Statement delete_statement(
    361       db_->GetCachedStatement(SQL_FROM_HERE,
    362                               "DELETE FROM thumbnails WHERE url = ?"));
    363   delete_statement.BindString(0, url.url.spec());
    364 
    365   if (!delete_statement.Run())
    366     return false;
    367 
    368   return transaction.Commit();
    369 }
    370 
    371 sql::Connection* TopSitesDatabase::CreateDB(const base::FilePath& db_name) {
    372   scoped_ptr<sql::Connection> db(new sql::Connection());
    373   // Settings copied from ThumbnailDatabase.
    374   db->set_histogram_tag("TopSites");
    375   db->set_page_size(4096);
    376   db->set_cache_size(32);
    377 
    378   if (!db->Open(db_name)) {
    379     LOG(ERROR) << db->GetErrorMessage();
    380     return NULL;
    381   }
    382 
    383   return db.release();
    384 }
    385 
    386 }  // namespace history
    387