Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(90)

Side by Side Diff: chrome/browser/history/top_sites_database.cc

Issue 870063002: Componentize TopSites, TopSitesBackend, TopSitesDatabase (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@815983002
Patch Set: Fix typo Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/top_sites_database.h"
6
7 #include "base/files/file_util.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "chrome/browser/history/top_sites.h"
13 #include "components/history/core/browser/history_types.h"
14 #include "components/history/core/common/thumbnail_score.h"
15 #include "sql/connection.h"
16 #include "sql/recovery.h"
17 #include "sql/statement.h"
18 #include "sql/transaction.h"
19 #include "third_party/sqlite/sqlite3.h"
20
21 // Description of database table:
22 //
23 // thumbnails
24 // url URL of the sites for which we have a thumbnail.
25 // url_rank Index of the URL in that thumbnail, 0-based. The thumbnail
26 // with the highest rank will be the next one evicted. Forced
27 // thumbnails have a rank of -1.
28 // title The title to display under that thumbnail.
29 // redirects A space separated list of URLs that are known to redirect
30 // to this url.
31 // boring_score How "boring" that thumbnail is. See ThumbnailScore.
32 // good_clipping True if the thumbnail was clipped from the bottom, keeping
33 // the entire width of the window. See ThumbnailScore.
34 // at_top True if the thumbnail was captured at the top of the
35 // website.
36 // last_updated The time at which this thumbnail was last updated.
37 // load_completed True if the thumbnail was captured after the page load was
38 // completed.
39 // last_forced If this is a forced thumbnail, records the last time it
40 // was forced. If it's not a forced thumbnail, 0.
41
42 namespace {
43
44 // For this database, schema migrations are deprecated after two
45 // years. This means that the oldest non-deprecated version should be
46 // two years old or greater (thus the migrations to get there are
47 // older). Databases containing deprecated versions will be cleared
48 // at startup. Since this database is a cache, losing old data is not
49 // fatal (in fact, very old data may be expired immediately at startup
50 // anyhow).
51
52 // Version 3: b6d6a783/r231648 by beaudoin@chromium.org on 2013-10-29
53 // Version 2: eb0b24e6/r87284 by satorux@chromium.org on 2011-05-31
54 // Version 1: 809cc4d8/r64072 by sky@chromium.org on 2010-10-27 (deprecated)
55
56 // NOTE(shess): When changing the version, add a new golden file for
57 // the new version and a test to verify that Init() works with it.
58 // NOTE(shess): RecoverDatabaseOrRaze() depends on the specific
59 // version number. The code is subtle and in development, contact me
60 // if the necessary changes are not obvious.
61 static const int kVersionNumber = 3;
62 static const int kDeprecatedVersionNumber = 1; // and earlier.
63
64 bool InitTables(sql::Connection* db) {
65 const char kThumbnailsSql[] =
66 "CREATE TABLE IF NOT EXISTS thumbnails ("
67 "url LONGVARCHAR PRIMARY KEY,"
68 "url_rank INTEGER,"
69 "title LONGVARCHAR,"
70 "thumbnail BLOB,"
71 "redirects LONGVARCHAR,"
72 "boring_score DOUBLE DEFAULT 1.0,"
73 "good_clipping INTEGER DEFAULT 0,"
74 "at_top INTEGER DEFAULT 0,"
75 "last_updated INTEGER DEFAULT 0,"
76 "load_completed INTEGER DEFAULT 0,"
77 "last_forced INTEGER DEFAULT 0)";
78 return db->Execute(kThumbnailsSql);
79 }
80
81 // Encodes redirects into a string.
82 std::string GetRedirects(const history::MostVisitedURL& url) {
83 std::vector<std::string> redirects;
84 for (size_t i = 0; i < url.redirects.size(); i++)
85 redirects.push_back(url.redirects[i].spec());
86 return JoinString(redirects, ' ');
87 }
88
89 // Decodes redirects from a string and sets them for the url.
90 void SetRedirects(const std::string& redirects, history::MostVisitedURL* url) {
91 std::vector<std::string> redirects_vector;
92 base::SplitStringAlongWhitespace(redirects, &redirects_vector);
93 for (size_t i = 0; i < redirects_vector.size(); ++i) {
94 GURL redirects_url(redirects_vector[i]);
95 if (redirects_url.is_valid())
96 url->redirects.push_back(redirects_url);
97 }
98 }
99
100 // Track various failure (and success) cases in recovery code.
101 //
102 // TODO(shess): The recovery code is complete, but by nature runs in challenging
103 // circumstances, so initially the default error response is to leave the
104 // existing database in place. This histogram is intended to expose the
105 // failures seen in the fleet. Frequent failure cases can be explored more
106 // deeply to see if the complexity to fix them is warranted. Infrequent failure
107 // cases can be resolved by marking the database unrecoverable (which will
108 // delete the data).
109 //
110 // Based on the thumbnail_database.cc recovery code, FAILED_SCOPER should
111 // dominate, followed distantly by FAILED_META, with few or no other failures.
112 enum RecoveryEventType {
113 // Database successfully recovered.
114 RECOVERY_EVENT_RECOVERED = 0,
115
116 // Database successfully deprecated.
117 RECOVERY_EVENT_DEPRECATED,
118
119 // Sqlite.RecoveryEvent can usually be used to get more detail about the
120 // specific failure (see sql/recovery.cc).
121 RECOVERY_EVENT_FAILED_SCOPER,
122 RECOVERY_EVENT_FAILED_META_VERSION,
123 RECOVERY_EVENT_FAILED_META_WRONG_VERSION,
124 RECOVERY_EVENT_FAILED_META_INIT,
125 RECOVERY_EVENT_FAILED_SCHEMA_INIT,
126 RECOVERY_EVENT_FAILED_AUTORECOVER_THUMBNAILS,
127 RECOVERY_EVENT_FAILED_COMMIT,
128
129 // Track invariants resolved by FixThumbnailsTable().
130 RECOVERY_EVENT_INVARIANT_RANK,
131 RECOVERY_EVENT_INVARIANT_REDIRECT,
132 RECOVERY_EVENT_INVARIANT_CONTIGUOUS,
133
134 // Always keep this at the end.
135 RECOVERY_EVENT_MAX,
136 };
137
138 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
139 UMA_HISTOGRAM_ENUMERATION("History.TopSitesRecovery",
140 recovery_event, RECOVERY_EVENT_MAX);
141 }
142
143 // Most corruption comes down to atomic updates between pages being broken
144 // somehow. This can result in either missing data, or overlapping data,
145 // depending on the operation broken. This table has large rows, which will use
146 // overflow pages, so it is possible (though unlikely) that a chain could fit
147 // together and yield a row with errors.
148 void FixThumbnailsTable(sql::Connection* db) {
149 // Enforce invariant separating forced and non-forced thumbnails.
150 const char kFixRankSql[] =
151 "DELETE FROM thumbnails "
152 "WHERE (url_rank = -1 AND last_forced = 0) "
153 "OR (url_rank <> -1 AND last_forced <> 0)";
154 ignore_result(db->Execute(kFixRankSql));
155 if (db->GetLastChangeCount() > 0)
156 RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_RANK);
157
158 // Enforce invariant that url is in its own redirects.
159 const char kFixRedirectsSql[] =
160 "DELETE FROM thumbnails "
161 "WHERE url <> substr(redirects, -length(url), length(url))";
162 ignore_result(db->Execute(kFixRedirectsSql));
163 if (db->GetLastChangeCount() > 0)
164 RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_REDIRECT);
165
166 // Enforce invariant that url_rank>=0 forms a contiguous series.
167 // TODO(shess): I have not found an UPDATE+SUBSELECT method of managing this.
168 // It can be done with a temporary table and a subselect, but doing it
169 // manually is easier to follow. Another option would be to somehow integrate
170 // the renumbering into the table recovery code.
171 const char kByRankSql[] =
172 "SELECT url_rank, rowid FROM thumbnails WHERE url_rank <> -1 "
173 "ORDER BY url_rank";
174 sql::Statement select_statement(db->GetUniqueStatement(kByRankSql));
175
176 const char kAdjustRankSql[] =
177 "UPDATE thumbnails SET url_rank = ? WHERE rowid = ?";
178 sql::Statement update_statement(db->GetUniqueStatement(kAdjustRankSql));
179
180 // Update any rows where |next_rank| doesn't match |url_rank|.
181 int next_rank = 0;
182 bool adjusted = false;
183 while (select_statement.Step()) {
184 const int url_rank = select_statement.ColumnInt(0);
185 if (url_rank != next_rank) {
186 adjusted = true;
187 update_statement.Reset(true);
188 update_statement.BindInt(0, next_rank);
189 update_statement.BindInt64(1, select_statement.ColumnInt64(1));
190 update_statement.Run();
191 }
192 ++next_rank;
193 }
194 if (adjusted)
195 RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_CONTIGUOUS);
196 }
197
198 // Recover the database to the extent possible, razing it if recovery is not
199 // possible.
200 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
201 // NOTE(shess): If the version changes, review this code.
202 DCHECK_EQ(3, kVersionNumber);
203
204 // It is almost certain that some operation against |db| will fail, prevent
205 // reentry.
206 db->reset_error_callback();
207
208 // For generating histogram stats.
209 size_t thumbnails_recovered = 0;
210 int64 original_size = 0;
211 base::GetFileSize(db_path, &original_size);
212
213 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
214 if (!recovery) {
215 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER);
216 return;
217 }
218
219 // Setup the meta recovery table and fetch the version number from the corrupt
220 // database.
221 int version = 0;
222 if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
223 // TODO(shess): Prior histograms indicate all failures are in creating the
224 // recover virtual table for corrupt.meta. The table may not exist, or the
225 // database may be too far gone. Either way, unclear how to resolve.
226 sql::Recovery::Rollback(recovery.Pass());
227 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION);
228 return;
229 }
230
231 // This code runs in a context which may be able to read version information
232 // that the regular deprecation path cannot. The effect of this code will be
233 // to raze the database.
234 if (version <= kDeprecatedVersionNumber) {
235 sql::Recovery::Unrecoverable(recovery.Pass());
236 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED);
237 return;
238 }
239
240 // TODO(shess): Earlier versions have been deprecated, later versions should
241 // be impossible. Unrecoverable() seems like a feasible response if this is
242 // infrequent enough.
243 if (version != 2 && version != 3) {
244 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
245 sql::Recovery::Rollback(recovery.Pass());
246 return;
247 }
248
249 // Both v2 and v3 recover to current schema version.
250 sql::MetaTable recover_meta_table;
251 if (!recover_meta_table.Init(recovery->db(), kVersionNumber,
252 kVersionNumber)) {
253 sql::Recovery::Rollback(recovery.Pass());
254 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
255 return;
256 }
257
258 // Create a fresh version of the schema. The recovery code uses
259 // conflict-resolution to handle duplicates, so any indices are necessary.
260 if (!InitTables(recovery->db())) {
261 // TODO(shess): Unable to create the new schema in the new database. The
262 // new database should be a temporary file, so being unable to work with it
263 // is pretty unclear.
264 //
265 // What are the potential responses, even? The recovery database could be
266 // opened as in-memory. If the temp database had a filesystem problem and
267 // the temp filesystem differs from the main database, then that could fix
268 // it.
269 sql::Recovery::Rollback(recovery.Pass());
270 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCHEMA_INIT);
271 return;
272 }
273
274 // The |1| is because v2 [thumbnails] has one less column than v3 did. In the
275 // v2 case the column will get default values.
276 if (!recovery->AutoRecoverTable("thumbnails", 1, &thumbnails_recovered)) {
277 sql::Recovery::Rollback(recovery.Pass());
278 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_THUMBNAILS);
279 return;
280 }
281
282 // TODO(shess): Inline this?
283 FixThumbnailsTable(recovery->db());
284
285 if (!sql::Recovery::Recovered(recovery.Pass())) {
286 // TODO(shess): Very unclear what this failure would actually mean, and what
287 // should be done. Add histograms to Recovered() implementation to get some
288 // insight.
289 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT);
290 return;
291 }
292
293 // Track the size of the recovered database relative to the size of the input
294 // database. The size should almost always be smaller, unless the input
295 // database was empty to start with. If the percentage results are very low,
296 // something is awry.
297 int64 final_size = 0;
298 if (original_size > 0 &&
299 base::GetFileSize(db_path, &final_size) &&
300 final_size > 0) {
301 UMA_HISTOGRAM_PERCENTAGE("History.TopSitesRecoveredPercentage",
302 final_size * 100 / original_size);
303 }
304
305 // Using 10,000 because these cases mostly care about "none recovered" and
306 // "lots recovered". More than 10,000 rows recovered probably means there's
307 // something wrong with the profile.
308 UMA_HISTOGRAM_COUNTS_10000("History.TopSitesRecoveredRowsThumbnails",
309 thumbnails_recovered);
310
311 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED);
312 }
313
314 void DatabaseErrorCallback(sql::Connection* db,
315 const base::FilePath& db_path,
316 int extended_error,
317 sql::Statement* stmt) {
318 // TODO(shess): Assert that this is running on a safe thread. AFAICT, should
319 // be the history thread, but at this level I can't see how to reach that.
320
321 // Attempt to recover corrupt databases.
322 int error = (extended_error & 0xFF);
323 if (error == SQLITE_CORRUPT ||
324 error == SQLITE_CANTOPEN ||
325 error == SQLITE_NOTADB) {
326 RecoverDatabaseOrRaze(db, db_path);
327 }
328
329 // TODO(shess): This database's error histograms look like:
330 // 84% SQLITE_CORRUPT, SQLITE_CANTOPEN, SQLITE_NOTADB
331 // 7% SQLITE_ERROR
332 // 6% SQLITE_IOERR variants
333 // 2% SQLITE_READONLY
334 // .4% SQLITE_FULL
335 // nominal SQLITE_TOBIG, SQLITE_AUTH, and SQLITE_BUSY. In the case of
336 // thumbnail_database.cc, as soon as the recovery code landed, SQLITE_IOERR
337 // shot to leadership. If the I/O error is system-level, there is probably no
338 // hope, but if it is restricted to something about the database file, it is
339 // possible that the recovery code could be brought to bear. In fact, it is
340 // possible that running recovery would be a reasonable default when errors
341 // are seen.
342
343 // The default handling is to assert on debug and to ignore on release.
344 if (!sql::Connection::ShouldIgnoreSqliteError(extended_error))
345 DLOG(FATAL) << db->GetErrorMessage();
346 }
347
348 } // namespace
349
350 namespace history {
351
352 // static
353 const int TopSitesDatabase::kRankOfForcedURL = -1;
354
355 // static
356 const int TopSitesDatabase::kRankOfNonExistingURL = -2;
357
358 TopSitesDatabase::TopSitesDatabase() {
359 }
360
361 TopSitesDatabase::~TopSitesDatabase() {
362 }
363
364 bool TopSitesDatabase::Init(const base::FilePath& db_name) {
365 // Retry failed InitImpl() in case the recovery system fixed things.
366 // TODO(shess): Instrument to figure out if there are any persistent failure
367 // cases which do not resolve themselves.
368 const size_t kAttempts = 2;
369
370 for (size_t i = 0; i < kAttempts; ++i) {
371 if (InitImpl(db_name))
372 return true;
373
374 meta_table_.Reset();
375 db_.reset();
376 }
377 return false;
378 }
379
380 bool TopSitesDatabase::InitImpl(const base::FilePath& db_name) {
381 const bool file_existed = base::PathExists(db_name);
382
383 db_.reset(CreateDB(db_name));
384 if (!db_)
385 return false;
386
387 // An older version had data with no meta table. Deprecate by razing.
388 // TODO(shess): Just have RazeIfDeprecated() handle this case.
389 const bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get());
390 if (!does_meta_exist && file_existed) {
391 if (!db_->Raze())
392 return false;
393 }
394
395 // Clear databases which are too old to process.
396 DCHECK_LT(kDeprecatedVersionNumber, kVersionNumber);
397 sql::MetaTable::RazeIfDeprecated(db_.get(), kDeprecatedVersionNumber);
398
399 // Scope initialization in a transaction so we can't be partially
400 // initialized.
401 sql::Transaction transaction(db_.get());
402 // TODO(shess): Failure to open transaction is bad, address it.
403 if (!transaction.Begin())
404 return false;
405
406 if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber))
407 return false;
408
409 if (!InitTables(db_.get()))
410 return false;
411
412 if (meta_table_.GetVersionNumber() == 2) {
413 if (!UpgradeToVersion3()) {
414 LOG(WARNING) << "Unable to upgrade top sites database to version 3.";
415 return false;
416 }
417 }
418
419 // Version check.
420 if (meta_table_.GetVersionNumber() != kVersionNumber)
421 return false;
422
423 // Initialization is complete.
424 if (!transaction.Commit())
425 return false;
426
427 return true;
428 }
429
430 bool TopSitesDatabase::UpgradeToVersion3() {
431 // Add 'last_forced' column.
432 if (!db_->Execute(
433 "ALTER TABLE thumbnails ADD last_forced INTEGER DEFAULT 0")) {
434 NOTREACHED();
435 return false;
436 }
437 meta_table_.SetVersionNumber(3);
438 return true;
439 }
440
441 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
442 URLToImagesMap* thumbnails) {
443 sql::Statement statement(db_->GetCachedStatement(
444 SQL_FROM_HERE,
445 "SELECT url, url_rank, title, thumbnail, redirects, "
446 "boring_score, good_clipping, at_top, last_updated, load_completed, "
447 "last_forced FROM thumbnails ORDER BY url_rank, last_forced"));
448
449 if (!statement.is_valid()) {
450 LOG(WARNING) << db_->GetErrorMessage();
451 return;
452 }
453
454 urls->clear();
455 thumbnails->clear();
456
457 while (statement.Step()) {
458 // Results are sorted by url_rank. For forced thumbnails with url_rank = -1,
459 // thumbnails are sorted by last_forced.
460 MostVisitedURL url;
461 GURL gurl(statement.ColumnString(0));
462 url.url = gurl;
463 url.title = statement.ColumnString16(2);
464 url.last_forced_time =
465 base::Time::FromInternalValue(statement.ColumnInt64(10));
466 std::string redirects = statement.ColumnString(4);
467 SetRedirects(redirects, &url);
468 urls->push_back(url);
469
470 std::vector<unsigned char> data;
471 statement.ColumnBlobAsVector(3, &data);
472 Images thumbnail;
473 if (!data.empty())
474 thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data);
475 thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5);
476 thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6);
477 thumbnail.thumbnail_score.at_top = statement.ColumnBool(7);
478 thumbnail.thumbnail_score.time_at_snapshot =
479 base::Time::FromInternalValue(statement.ColumnInt64(8));
480 thumbnail.thumbnail_score.load_completed = statement.ColumnBool(9);
481 (*thumbnails)[gurl] = thumbnail;
482 }
483 }
484
485 void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url,
486 int new_rank,
487 const Images& thumbnail) {
488 sql::Transaction transaction(db_.get());
489 transaction.Begin();
490
491 int rank = GetURLRank(url);
492 if (rank == kRankOfNonExistingURL) {
493 AddPageThumbnail(url, new_rank, thumbnail);
494 } else {
495 UpdatePageRankNoTransaction(url, new_rank);
496 UpdatePageThumbnail(url, thumbnail);
497 }
498
499 transaction.Commit();
500 }
501
502 bool TopSitesDatabase::UpdatePageThumbnail(
503 const MostVisitedURL& url, const Images& thumbnail) {
504 sql::Statement statement(db_->GetCachedStatement(
505 SQL_FROM_HERE,
506 "UPDATE thumbnails SET "
507 "title = ?, thumbnail = ?, redirects = ?, "
508 "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, "
509 "load_completed = ?, last_forced = ?"
510 "WHERE url = ? "));
511 statement.BindString16(0, url.title);
512 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
513 statement.BindBlob(1, thumbnail.thumbnail->front(),
514 static_cast<int>(thumbnail.thumbnail->size()));
515 }
516 statement.BindString(2, GetRedirects(url));
517 const ThumbnailScore& score = thumbnail.thumbnail_score;
518 statement.BindDouble(3, score.boring_score);
519 statement.BindBool(4, score.good_clipping);
520 statement.BindBool(5, score.at_top);
521 statement.BindInt64(6, score.time_at_snapshot.ToInternalValue());
522 statement.BindBool(7, score.load_completed);
523 statement.BindInt64(8, url.last_forced_time.ToInternalValue());
524 statement.BindString(9, url.url.spec());
525
526 return statement.Run();
527 }
528
529 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
530 int new_rank,
531 const Images& thumbnail) {
532 sql::Statement statement(db_->GetCachedStatement(
533 SQL_FROM_HERE,
534 "INSERT OR REPLACE INTO thumbnails "
535 "(url, url_rank, title, thumbnail, redirects, "
536 "boring_score, good_clipping, at_top, last_updated, load_completed, "
537 "last_forced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
538 statement.BindString(0, url.url.spec());
539 statement.BindInt(1, kRankOfForcedURL); // Fist make it a forced thumbnail.
540 statement.BindString16(2, url.title);
541 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
542 statement.BindBlob(3, thumbnail.thumbnail->front(),
543 static_cast<int>(thumbnail.thumbnail->size()));
544 }
545 statement.BindString(4, GetRedirects(url));
546 const ThumbnailScore& score = thumbnail.thumbnail_score;
547 statement.BindDouble(5, score.boring_score);
548 statement.BindBool(6, score.good_clipping);
549 statement.BindBool(7, score.at_top);
550 statement.BindInt64(8, score.time_at_snapshot.ToInternalValue());
551 statement.BindBool(9, score.load_completed);
552 int64 last_forced = url.last_forced_time.ToInternalValue();
553 DCHECK((last_forced == 0) == (new_rank != kRankOfForcedURL))
554 << "Thumbnail without a forced time stamp has a forced rank, or the "
555 << "opposite.";
556 statement.BindInt64(10, last_forced);
557 if (!statement.Run())
558 return;
559
560 // Update rank if this is not a forced thumbnail.
561 if (new_rank != kRankOfForcedURL)
562 UpdatePageRankNoTransaction(url, new_rank);
563 }
564
565 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
566 int new_rank) {
567 DCHECK((url.last_forced_time.ToInternalValue() == 0) ==
568 (new_rank != kRankOfForcedURL))
569 << "Thumbnail without a forced time stamp has a forced rank, or the "
570 << "opposite.";
571 sql::Transaction transaction(db_.get());
572 transaction.Begin();
573 UpdatePageRankNoTransaction(url, new_rank);
574 transaction.Commit();
575 }
576
577 // Caller should have a transaction open.
578 void TopSitesDatabase::UpdatePageRankNoTransaction(
579 const MostVisitedURL& url, int new_rank) {
580 DCHECK_GT(db_->transaction_nesting(), 0);
581 DCHECK((url.last_forced_time.is_null()) == (new_rank != kRankOfForcedURL))
582 << "Thumbnail without a forced time stamp has a forced rank, or the "
583 << "opposite.";
584
585 int prev_rank = GetURLRank(url);
586 if (prev_rank == kRankOfNonExistingURL) {
587 LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec();
588 return;
589 }
590
591 // Shift the ranks.
592 if (prev_rank > new_rank) {
593 if (new_rank == kRankOfForcedURL) {
594 // From non-forced to forced, shift down.
595 // Example: 2 -> -1
596 // -1, -1, -1, 0, 1, [2 -> -1], [3 -> 2], [4 -> 3]
597 sql::Statement shift_statement(db_->GetCachedStatement(
598 SQL_FROM_HERE,
599 "UPDATE thumbnails "
600 "SET url_rank = url_rank - 1 "
601 "WHERE url_rank > ?"));
602 shift_statement.BindInt(0, prev_rank);
603 shift_statement.Run();
604 } else {
605 // From non-forced to non-forced, shift up.
606 // Example: 3 -> 1
607 // -1, -1, -1, 0, [1 -> 2], [2 -> 3], [3 -> 1], 4
608 sql::Statement shift_statement(db_->GetCachedStatement(
609 SQL_FROM_HERE,
610 "UPDATE thumbnails "
611 "SET url_rank = url_rank + 1 "
612 "WHERE url_rank >= ? AND url_rank < ?"));
613 shift_statement.BindInt(0, new_rank);
614 shift_statement.BindInt(1, prev_rank);
615 shift_statement.Run();
616 }
617 } else if (prev_rank < new_rank) {
618 if (prev_rank == kRankOfForcedURL) {
619 // From non-forced to forced, shift up.
620 // Example: -1 -> 2
621 // -1, [-1 -> 2], -1, 0, 1, [2 -> 3], [3 -> 4], [4 -> 5]
622 sql::Statement shift_statement(db_->GetCachedStatement(
623 SQL_FROM_HERE,
624 "UPDATE thumbnails "
625 "SET url_rank = url_rank + 1 "
626 "WHERE url_rank >= ?"));
627 shift_statement.BindInt(0, new_rank);
628 shift_statement.Run();
629 } else {
630 // From non-forced to non-forced, shift down.
631 // Example: 1 -> 3.
632 // -1, -1, -1, 0, [1 -> 3], [2 -> 1], [3 -> 2], 4
633 sql::Statement shift_statement(db_->GetCachedStatement(
634 SQL_FROM_HERE,
635 "UPDATE thumbnails "
636 "SET url_rank = url_rank - 1 "
637 "WHERE url_rank > ? AND url_rank <= ?"));
638 shift_statement.BindInt(0, prev_rank);
639 shift_statement.BindInt(1, new_rank);
640 shift_statement.Run();
641 }
642 }
643
644 // Set the url's rank and last_forced, since the latter changes when a URL
645 // goes from forced to non-forced and vice-versa.
646 sql::Statement set_statement(db_->GetCachedStatement(
647 SQL_FROM_HERE,
648 "UPDATE thumbnails "
649 "SET url_rank = ?, last_forced = ? "
650 "WHERE url == ?"));
651 set_statement.BindInt(0, new_rank);
652 set_statement.BindInt64(1, url.last_forced_time.ToInternalValue());
653 set_statement.BindString(2, url.url.spec());
654 set_statement.Run();
655 }
656
657 bool TopSitesDatabase::GetPageThumbnail(const GURL& url,
658 Images* thumbnail) {
659 sql::Statement statement(db_->GetCachedStatement(
660 SQL_FROM_HERE,
661 "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated "
662 "FROM thumbnails WHERE url=?"));
663 statement.BindString(0, url.spec());
664 if (!statement.Step())
665 return false;
666
667 std::vector<unsigned char> data;
668 statement.ColumnBlobAsVector(0, &data);
669 thumbnail->thumbnail = base::RefCountedBytes::TakeVector(&data);
670 thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1);
671 thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2);
672 thumbnail->thumbnail_score.at_top = statement.ColumnBool(3);
673 thumbnail->thumbnail_score.time_at_snapshot =
674 base::Time::FromInternalValue(statement.ColumnInt64(4));
675 return true;
676 }
677
678 int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) {
679 sql::Statement select_statement(db_->GetCachedStatement(
680 SQL_FROM_HERE,
681 "SELECT url_rank "
682 "FROM thumbnails WHERE url=?"));
683 select_statement.BindString(0, url.url.spec());
684 if (select_statement.Step())
685 return select_statement.ColumnInt(0);
686
687 return kRankOfNonExistingURL;
688 }
689
690 // Remove the record for this URL. Returns true iff removed successfully.
691 bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) {
692 int old_rank = GetURLRank(url);
693 if (old_rank == kRankOfNonExistingURL)
694 return false;
695
696 sql::Transaction transaction(db_.get());
697 transaction.Begin();
698 if (old_rank != kRankOfForcedURL) {
699 // Decrement all following ranks.
700 sql::Statement shift_statement(db_->GetCachedStatement(
701 SQL_FROM_HERE,
702 "UPDATE thumbnails "
703 "SET url_rank = url_rank - 1 "
704 "WHERE url_rank > ?"));
705 shift_statement.BindInt(0, old_rank);
706
707 if (!shift_statement.Run())
708 return false;
709 }
710
711 sql::Statement delete_statement(
712 db_->GetCachedStatement(SQL_FROM_HERE,
713 "DELETE FROM thumbnails WHERE url = ?"));
714 delete_statement.BindString(0, url.url.spec());
715
716 if (!delete_statement.Run())
717 return false;
718
719 return transaction.Commit();
720 }
721
722 sql::Connection* TopSitesDatabase::CreateDB(const base::FilePath& db_name) {
723 scoped_ptr<sql::Connection> db(new sql::Connection());
724 // Settings copied from ThumbnailDatabase.
725 db->set_histogram_tag("TopSites");
726 db->set_error_callback(base::Bind(&DatabaseErrorCallback,
727 db.get(), db_name));
728 db->set_page_size(4096);
729 db->set_cache_size(32);
730
731 if (!db->Open(db_name))
732 return NULL;
733 return db.release();
734 }
735
736 } // namespace history
OLDNEW
« no previous file with comments | « chrome/browser/history/top_sites_database.h ('k') | chrome/browser/history/top_sites_database_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698