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