1 // Copyright (c) 2011 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/thumbnail_database.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "app/sql/statement.h"
11 #include "app/sql/transaction.h"
12 #include "base/command_line.h"
13 #include "base/file_util.h"
14 #include "base/memory/ref_counted_memory.h"
15 #include "base/time.h"
16 #include "base/string_util.h"
17 #include "base/utf_string_conversions.h"
18 #include "chrome/browser/diagnostics/sqlite_diagnostics.h"
19 #include "chrome/browser/history/history_publisher.h"
20 #include "chrome/browser/history/top_sites.h"
21 #include "chrome/browser/history/url_database.h"
22 #include "chrome/common/thumbnail_score.h"
23 #include "third_party/skia/include/core/SkBitmap.h"
24 #include "ui/gfx/codec/jpeg_codec.h"
25
26 #if defined(OS_MACOSX)
27 #include "base/mac/mac_util.h"
28 #endif
29
FillIconMapping(const sql::Statement & statement,const GURL & page_url,history::IconMapping * icon_mapping)30 static void FillIconMapping(const sql::Statement& statement,
31 const GURL& page_url,
32 history::IconMapping* icon_mapping) {
33 icon_mapping->mapping_id = statement.ColumnInt64(0);
34 icon_mapping->icon_id = statement.ColumnInt64(1);
35 icon_mapping->icon_type =
36 static_cast<history::IconType>(statement.ColumnInt(2));
37 icon_mapping->page_url = page_url;
38 }
39
40 namespace history {
41
42 // Version number of the database.
43 static const int kCurrentVersionNumber = 4;
44 static const int kCompatibleVersionNumber = 4;
45
ThumbnailDatabase()46 ThumbnailDatabase::ThumbnailDatabase()
47 : history_publisher_(NULL),
48 use_top_sites_(false) {
49 }
50
~ThumbnailDatabase()51 ThumbnailDatabase::~ThumbnailDatabase() {
52 // The DBCloseScoper will delete the DB and the cache.
53 }
54
Init(const FilePath & db_name,const HistoryPublisher * history_publisher,URLDatabase * url_db)55 sql::InitStatus ThumbnailDatabase::Init(
56 const FilePath& db_name,
57 const HistoryPublisher* history_publisher,
58 URLDatabase* url_db) {
59 history_publisher_ = history_publisher;
60 sql::InitStatus status = OpenDatabase(&db_, db_name);
61 if (status != sql::INIT_OK)
62 return status;
63
64 // Scope initialization in a transaction so we can't be partially initialized.
65 sql::Transaction transaction(&db_);
66 transaction.Begin();
67
68 #if defined(OS_MACOSX)
69 // Exclude the thumbnails file and its journal from backups.
70 base::mac::SetFileBackupExclusion(db_name, true);
71 FilePath::StringType db_name_string(db_name.value());
72 db_name_string += "-journal";
73 FilePath db_journal_name(db_name_string);
74 base::mac::SetFileBackupExclusion(db_journal_name, true);
75 #endif
76
77 // Create the tables.
78 if (!meta_table_.Init(&db_, kCurrentVersionNumber,
79 kCompatibleVersionNumber) ||
80 !InitThumbnailTable() ||
81 !InitFaviconsTable(&db_, false) ||
82 !InitIconMappingTable(&db_, false)) {
83 db_.Close();
84 return sql::INIT_FAILURE;
85 }
86 InitFaviconsIndex();
87 InitIconMappingIndex();
88
89 // Version check. We should not encounter a database too old for us to handle
90 // in the wild, so we try to continue in that case.
91 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
92 LOG(WARNING) << "Thumbnail database is too new.";
93 return sql::INIT_TOO_NEW;
94 }
95
96 int cur_version = meta_table_.GetVersionNumber();
97 if (cur_version == 2) {
98 if (!UpgradeToVersion3()) {
99 LOG(WARNING) << "Unable to update to thumbnail database to version 3.";
100 db_.Close();
101 return sql::INIT_FAILURE;
102 }
103 ++cur_version;
104 }
105
106 if (cur_version == 3) {
107 if (!UpgradeToVersion4() || !MigrateIconMappingData(url_db)) {
108 LOG(WARNING) << "Unable to update to thumbnail database to version 4.";
109 db_.Close();
110 return sql::INIT_FAILURE;
111 }
112 ++cur_version;
113 }
114
115 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
116 "Thumbnail database version " << cur_version << " is too old to handle.";
117
118 // Initialization is complete.
119 if (!transaction.Commit()) {
120 db_.Close();
121 return sql::INIT_FAILURE;
122 }
123
124 return sql::INIT_OK;
125 }
126
OpenDatabase(sql::Connection * db,const FilePath & db_name)127 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
128 const FilePath& db_name) {
129 // Set the exceptional sqlite error handler.
130 db->set_error_delegate(GetErrorHandlerForThumbnailDb());
131
132 // Thumbnails db now only stores favicons, so we don't need that big a page
133 // size or cache.
134 db->set_page_size(2048);
135 db->set_cache_size(32);
136
137 // Run the database in exclusive mode. Nobody else should be accessing the
138 // database while we're running, and this will give somewhat improved perf.
139 db->set_exclusive_locking();
140
141 if (!db->Open(db_name))
142 return sql::INIT_FAILURE;
143
144 return sql::INIT_OK;
145 }
146
InitThumbnailTable()147 bool ThumbnailDatabase::InitThumbnailTable() {
148 if (!db_.DoesTableExist("thumbnails")) {
149 use_top_sites_ = true;
150 }
151 return true;
152 }
153
UpgradeToVersion3()154 bool ThumbnailDatabase::UpgradeToVersion3() {
155 if (use_top_sites_) {
156 meta_table_.SetVersionNumber(3);
157 meta_table_.SetCompatibleVersionNumber(
158 std::min(3, kCompatibleVersionNumber));
159 return true; // Not needed after migration to TopSites.
160 }
161
162 // sqlite doesn't like the "ALTER TABLE xxx ADD (column_one, two,
163 // three)" syntax, so list out the commands we need to execute:
164 const char* alterations[] = {
165 "ALTER TABLE thumbnails ADD boring_score DOUBLE DEFAULT 1.0",
166 "ALTER TABLE thumbnails ADD good_clipping INTEGER DEFAULT 0",
167 "ALTER TABLE thumbnails ADD at_top INTEGER DEFAULT 0",
168 "ALTER TABLE thumbnails ADD last_updated INTEGER DEFAULT 0",
169 NULL
170 };
171
172 for (int i = 0; alterations[i] != NULL; ++i) {
173 if (!db_.Execute(alterations[i])) {
174 NOTREACHED();
175 return false;
176 }
177 }
178
179 meta_table_.SetVersionNumber(3);
180 meta_table_.SetCompatibleVersionNumber(std::min(3, kCompatibleVersionNumber));
181 return true;
182 }
183
RecreateThumbnailTable()184 bool ThumbnailDatabase::RecreateThumbnailTable() {
185 if (use_top_sites_)
186 return true; // Not needed after migration to TopSites.
187
188 if (!db_.Execute("DROP TABLE thumbnails"))
189 return false;
190 return InitThumbnailTable();
191 }
192
InitFaviconsTable(sql::Connection * db,bool is_temporary)193 bool ThumbnailDatabase::InitFaviconsTable(sql::Connection* db,
194 bool is_temporary) {
195 // Note: if you update the schema, don't forget to update
196 // CopyToTemporaryFaviconTable as well.
197 const char* name = is_temporary ? "temp_favicons" : "favicons";
198 if (!db->DoesTableExist(name)) {
199 std::string sql;
200 sql.append("CREATE TABLE ");
201 sql.append(name);
202 sql.append("("
203 "id INTEGER PRIMARY KEY,"
204 "url LONGVARCHAR NOT NULL,"
205 "last_updated INTEGER DEFAULT 0,"
206 "image_data BLOB,"
207 "icon_type INTEGER DEFAULT 1)"); // Set the default as FAVICON
208 // to be consistent with table
209 // upgrade in
210 // UpgradeToVersion4().
211 if (!db->Execute(sql.c_str()))
212 return false;
213 }
214 return true;
215 }
216
InitFaviconsIndex()217 void ThumbnailDatabase::InitFaviconsIndex() {
218 // Add an index on the url column. We ignore errors. Since this is always
219 // called during startup, the index will normally already exist.
220 db_.Execute("CREATE INDEX favicons_url ON favicons(url)");
221 }
222
BeginTransaction()223 void ThumbnailDatabase::BeginTransaction() {
224 db_.BeginTransaction();
225 }
226
CommitTransaction()227 void ThumbnailDatabase::CommitTransaction() {
228 db_.CommitTransaction();
229 }
230
Vacuum()231 void ThumbnailDatabase::Vacuum() {
232 DCHECK(db_.transaction_nesting() == 0) <<
233 "Can not have a transaction when vacuuming.";
234 db_.Execute("VACUUM");
235 }
236
SetPageThumbnail(const GURL & url,URLID id,const SkBitmap & thumbnail,const ThumbnailScore & score,base::Time time)237 void ThumbnailDatabase::SetPageThumbnail(
238 const GURL& url,
239 URLID id,
240 const SkBitmap& thumbnail,
241 const ThumbnailScore& score,
242 base::Time time) {
243 if (use_top_sites_) {
244 LOG(WARNING) << "Use TopSites instead.";
245 return; // Not possible after migration to TopSites.
246 }
247
248 if (!thumbnail.isNull()) {
249 bool add_thumbnail = true;
250 ThumbnailScore current_score;
251 if (ThumbnailScoreForId(id, ¤t_score)) {
252 add_thumbnail = ShouldReplaceThumbnailWith(current_score, score);
253 }
254
255 if (add_thumbnail) {
256 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
257 "INSERT OR REPLACE INTO thumbnails "
258 "(url_id, boring_score, good_clipping, at_top, last_updated, data) "
259 "VALUES (?,?,?,?,?,?)"));
260 if (!statement)
261 return;
262
263 // We use 90 quality (out of 100) which is pretty high, because
264 // we're very sensitive to artifacts for these small sized,
265 // highly detailed images.
266 std::vector<unsigned char> jpeg_data;
267 SkAutoLockPixels thumbnail_lock(thumbnail);
268 bool encoded = gfx::JPEGCodec::Encode(
269 reinterpret_cast<unsigned char*>(thumbnail.getAddr32(0, 0)),
270 gfx::JPEGCodec::FORMAT_SkBitmap, thumbnail.width(),
271 thumbnail.height(),
272 static_cast<int>(thumbnail.rowBytes()), 90,
273 &jpeg_data);
274
275 if (encoded) {
276 statement.BindInt64(0, id);
277 statement.BindDouble(1, score.boring_score);
278 statement.BindBool(2, score.good_clipping);
279 statement.BindBool(3, score.at_top);
280 statement.BindInt64(4, score.time_at_snapshot.ToTimeT());
281 statement.BindBlob(5, &jpeg_data[0],
282 static_cast<int>(jpeg_data.size()));
283 if (!statement.Run())
284 NOTREACHED() << db_.GetErrorMessage();
285 }
286
287 // Publish the thumbnail to any indexers listening to us.
288 // The tests may send an invalid url. Hence avoid publishing those.
289 if (url.is_valid() && history_publisher_ != NULL)
290 history_publisher_->PublishPageThumbnail(jpeg_data, url, time);
291 }
292 } else {
293 if (!DeleteThumbnail(id) )
294 DLOG(WARNING) << "Unable to delete thumbnail";
295 }
296 }
297
GetPageThumbnail(URLID id,std::vector<unsigned char> * data)298 bool ThumbnailDatabase::GetPageThumbnail(URLID id,
299 std::vector<unsigned char>* data) {
300 if (use_top_sites_) {
301 LOG(WARNING) << "Use TopSites instead.";
302 return false; // Not possible after migration to TopSites.
303 }
304
305 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
306 "SELECT data FROM thumbnails WHERE url_id=?"));
307 if (!statement)
308 return false;
309
310 statement.BindInt64(0, id);
311 if (!statement.Step())
312 return false; // don't have a thumbnail for this ID
313
314 statement.ColumnBlobAsVector(0, data);
315 return true;
316 }
317
DeleteThumbnail(URLID id)318 bool ThumbnailDatabase::DeleteThumbnail(URLID id) {
319 if (use_top_sites_) {
320 return true; // Not possible after migration to TopSites.
321 }
322
323 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
324 "DELETE FROM thumbnails WHERE url_id = ?"));
325 if (!statement)
326 return false;
327
328 statement.BindInt64(0, id);
329 return statement.Run();
330 }
331
ThumbnailScoreForId(URLID id,ThumbnailScore * score)332 bool ThumbnailDatabase::ThumbnailScoreForId(URLID id,
333 ThumbnailScore* score) {
334 if (use_top_sites_) {
335 LOG(WARNING) << "Use TopSites instead.";
336 return false; // Not possible after migration to TopSites.
337 }
338
339 // Fetch the current thumbnail's information to make sure we
340 // aren't replacing a good thumbnail with one that's worse.
341 sql::Statement select_statement(db_.GetCachedStatement(SQL_FROM_HERE,
342 "SELECT boring_score, good_clipping, at_top, last_updated "
343 "FROM thumbnails WHERE url_id=?"));
344 if (!select_statement) {
345 NOTREACHED() << "Couldn't build select statement!";
346 } else {
347 select_statement.BindInt64(0, id);
348 if (select_statement.Step()) {
349 double current_boring_score = select_statement.ColumnDouble(0);
350 bool current_clipping = select_statement.ColumnBool(1);
351 bool current_at_top = select_statement.ColumnBool(2);
352 base::Time last_updated =
353 base::Time::FromTimeT(select_statement.ColumnInt64(3));
354 *score = ThumbnailScore(current_boring_score, current_clipping,
355 current_at_top, last_updated);
356 return true;
357 }
358 }
359
360 return false;
361 }
362
SetFavicon(URLID icon_id,scoped_refptr<RefCountedMemory> icon_data,base::Time time)363 bool ThumbnailDatabase::SetFavicon(URLID icon_id,
364 scoped_refptr<RefCountedMemory> icon_data,
365 base::Time time) {
366 DCHECK(icon_id);
367 if (icon_data->size()) {
368 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
369 "UPDATE favicons SET image_data=?, last_updated=? WHERE id=?"));
370 if (!statement)
371 return 0;
372
373 statement.BindBlob(0, icon_data->front(),
374 static_cast<int>(icon_data->size()));
375 statement.BindInt64(1, time.ToTimeT());
376 statement.BindInt64(2, icon_id);
377 return statement.Run();
378 } else {
379 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
380 "UPDATE favicons SET image_data=NULL, last_updated=? WHERE id=?"));
381 if (!statement)
382 return 0;
383
384 statement.BindInt64(0, time.ToTimeT());
385 statement.BindInt64(1, icon_id);
386 return statement.Run();
387 }
388 }
389
SetFaviconLastUpdateTime(FaviconID icon_id,base::Time time)390 bool ThumbnailDatabase::SetFaviconLastUpdateTime(FaviconID icon_id,
391 base::Time time) {
392 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
393 "UPDATE favicons SET last_updated=? WHERE id=?"));
394 if (!statement)
395 return 0;
396
397 statement.BindInt64(0, time.ToTimeT());
398 statement.BindInt64(1, icon_id);
399 return statement.Run();
400 }
401
GetFaviconIDForFaviconURL(const GURL & icon_url,int required_icon_type,IconType * icon_type)402 FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL(const GURL& icon_url,
403 int required_icon_type,
404 IconType* icon_type) {
405 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
406 "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) "
407 "ORDER BY icon_type DESC"));
408 if (!statement)
409 return 0;
410
411 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
412 statement.BindInt(1, required_icon_type);
413 if (!statement.Step())
414 return 0; // not cached
415
416 if (icon_type)
417 *icon_type = static_cast<IconType>(statement.ColumnInt(1));
418 return statement.ColumnInt64(0);
419 }
420
GetFavicon(FaviconID icon_id,base::Time * last_updated,std::vector<unsigned char> * png_icon_data,GURL * icon_url)421 bool ThumbnailDatabase::GetFavicon(
422 FaviconID icon_id,
423 base::Time* last_updated,
424 std::vector<unsigned char>* png_icon_data,
425 GURL* icon_url) {
426 DCHECK(icon_id);
427
428 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
429 "SELECT last_updated, image_data, url FROM favicons WHERE id=?"));
430 if (!statement)
431 return 0;
432
433 statement.BindInt64(0, icon_id);
434
435 if (!statement.Step())
436 return false; // No entry for the id.
437
438 *last_updated = base::Time::FromTimeT(statement.ColumnInt64(0));
439 if (statement.ColumnByteLength(1) > 0)
440 statement.ColumnBlobAsVector(1, png_icon_data);
441 if (icon_url)
442 *icon_url = GURL(statement.ColumnString(2));
443
444 return true;
445 }
446
AddFavicon(const GURL & icon_url,IconType icon_type)447 FaviconID ThumbnailDatabase::AddFavicon(const GURL& icon_url,
448 IconType icon_type) {
449
450 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
451 "INSERT INTO favicons (url, icon_type) VALUES (?, ?)"));
452 if (!statement)
453 return 0;
454
455 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
456 statement.BindInt(1, icon_type);
457 if (!statement.Run())
458 return 0;
459 return db_.GetLastInsertRowId();
460 }
461
DeleteFavicon(FaviconID id)462 bool ThumbnailDatabase::DeleteFavicon(FaviconID id) {
463 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
464 "DELETE FROM favicons WHERE id = ?"));
465 if (!statement)
466 return false;
467
468 statement.BindInt64(0, id);
469 return statement.Run();
470 }
471
GetIconMappingForPageURL(const GURL & page_url,IconType required_icon_type,IconMapping * icon_mapping)472 bool ThumbnailDatabase::GetIconMappingForPageURL(const GURL& page_url,
473 IconType required_icon_type,
474 IconMapping* icon_mapping) {
475 std::vector<IconMapping> icon_mappings;
476 if (!GetIconMappingsForPageURL(page_url, &icon_mappings))
477 return false;
478
479 for (std::vector<IconMapping>::iterator m = icon_mappings.begin();
480 m != icon_mappings.end(); ++m) {
481 if (m->icon_type == required_icon_type) {
482 if (icon_mapping != NULL)
483 *icon_mapping = *m;
484 return true;
485 }
486 }
487
488 return false;
489 }
490
GetIconMappingsForPageURL(const GURL & page_url,std::vector<IconMapping> * mapping_data)491 bool ThumbnailDatabase::GetIconMappingsForPageURL(
492 const GURL& page_url,
493 std::vector<IconMapping>* mapping_data) {
494 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
495 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type "
496 "FROM icon_mapping "
497 "INNER JOIN favicons "
498 "ON icon_mapping.icon_id = favicons.id "
499 "WHERE icon_mapping.page_url=? "
500 "ORDER BY favicons.icon_type DESC"));
501 if (!statement)
502 return false;
503
504 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
505
506 bool result = false;
507 while (statement.Step()) {
508 result = true;
509 if (!mapping_data)
510 return result;
511
512 IconMapping icon_mapping;
513 FillIconMapping(statement, page_url, &icon_mapping);
514 mapping_data->push_back(icon_mapping);
515 }
516 return result;
517 }
518
AddIconMapping(const GURL & page_url,FaviconID icon_id)519 IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url,
520 FaviconID icon_id) {
521 return AddIconMapping(page_url, icon_id, false);
522 }
523
UpdateIconMapping(IconMappingID mapping_id,FaviconID icon_id)524 bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id,
525 FaviconID icon_id) {
526 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
527 "UPDATE icon_mapping SET icon_id=? WHERE id=?"));
528 if (!statement)
529 return 0;
530
531 statement.BindInt64(0, icon_id);
532 statement.BindInt64(1, mapping_id);
533 return statement.Run();
534 }
535
DeleteIconMappings(const GURL & page_url)536 bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) {
537 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
538 "DELETE FROM icon_mapping WHERE page_url = ?"));
539 if (!statement)
540 return false;
541
542 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
543 return statement.Run();
544 }
545
HasMappingFor(FaviconID id)546 bool ThumbnailDatabase::HasMappingFor(FaviconID id) {
547 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
548 "SELECT id FROM icon_mapping "
549 "WHERE icon_id=?"));
550 if (!statement)
551 return false;
552
553 statement.BindInt64(0, id);
554 return statement.Step();
555 }
556
MigrateIconMappingData(URLDatabase * url_db)557 bool ThumbnailDatabase::MigrateIconMappingData(URLDatabase* url_db) {
558 URLDatabase::IconMappingEnumerator e;
559 if (!url_db->InitIconMappingEnumeratorForEverything(&e))
560 return false;
561
562 IconMapping info;
563 while (e.GetNextIconMapping(&info)) {
564 // TODO: Using bulk insert to improve the performance.
565 if (!AddIconMapping(info.page_url, info.icon_id))
566 return false;
567 }
568 return true;
569 }
570
AddToTemporaryIconMappingTable(const GURL & page_url,const FaviconID icon_id)571 IconMappingID ThumbnailDatabase::AddToTemporaryIconMappingTable(
572 const GURL& page_url, const FaviconID icon_id) {
573 return AddIconMapping(page_url, icon_id, true);
574 }
575
CommitTemporaryIconMappingTable()576 bool ThumbnailDatabase::CommitTemporaryIconMappingTable() {
577 // Delete the old icon_mapping table.
578 if (!db_.Execute("DROP TABLE icon_mapping"))
579 return false;
580
581 // Rename the temporary one.
582 if (!db_.Execute("ALTER TABLE temp_icon_mapping RENAME TO icon_mapping"))
583 return false;
584
585 // The renamed table needs the index (the temporary table doesn't have one).
586 InitIconMappingIndex();
587
588 return true;
589 }
590
CopyToTemporaryFaviconTable(FaviconID source)591 FaviconID ThumbnailDatabase::CopyToTemporaryFaviconTable(FaviconID source) {
592 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
593 "INSERT INTO temp_favicons (url, last_updated, image_data, icon_type)"
594 "SELECT url, last_updated, image_data, icon_type "
595 "FROM favicons WHERE id = ?"));
596 if (!statement)
597 return 0;
598 statement.BindInt64(0, source);
599 if (!statement.Run())
600 return 0;
601
602 // We return the ID of the newly inserted favicon.
603 return db_.GetLastInsertRowId();
604 }
605
CommitTemporaryFaviconTable()606 bool ThumbnailDatabase::CommitTemporaryFaviconTable() {
607 // Delete the old favicons table.
608 if (!db_.Execute("DROP TABLE favicons"))
609 return false;
610
611 // Rename the temporary one.
612 if (!db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"))
613 return false;
614
615 // The renamed table needs the index (the temporary table doesn't have one).
616 InitFaviconsIndex();
617 return true;
618 }
619
NeedsMigrationToTopSites()620 bool ThumbnailDatabase::NeedsMigrationToTopSites() {
621 return !use_top_sites_;
622 }
623
RenameAndDropThumbnails(const FilePath & old_db_file,const FilePath & new_db_file)624 bool ThumbnailDatabase::RenameAndDropThumbnails(const FilePath& old_db_file,
625 const FilePath& new_db_file) {
626 // Init favicons table - same schema as the thumbnails.
627 sql::Connection favicons;
628 if (OpenDatabase(&favicons, new_db_file) != sql::INIT_OK)
629 return false;
630
631 if (!InitFaviconsTable(&favicons, false) ||
632 !InitIconMappingTable(&favicons, false)) {
633 NOTREACHED() << "Couldn't init favicons and icon-mapping table.";
634 favicons.Close();
635 return false;
636 }
637 favicons.Close();
638
639 // Can't attach within a transaction.
640 if (transaction_nesting())
641 CommitTransaction();
642
643 // Attach new DB.
644 {
645 // This block is needed because otherwise the attach statement is
646 // never cleared from cache and we can't close the DB :P
647 sql::Statement attach(db_.GetUniqueStatement("ATTACH ? AS new_favicons"));
648 if (!attach) {
649 NOTREACHED() << "Unable to attach database.";
650 // Keep the transaction open, even though we failed.
651 BeginTransaction();
652 return false;
653 }
654
655 #if defined(OS_POSIX)
656 attach.BindString(0, new_db_file.value());
657 #else
658 attach.BindString(0, WideToUTF8(new_db_file.value()));
659 #endif
660
661 if (!attach.Run()) {
662 NOTREACHED() << db_.GetErrorMessage();
663 BeginTransaction();
664 return false;
665 }
666 }
667
668 // Move favicons to the new DB.
669 if (!db_.Execute("INSERT OR REPLACE INTO new_favicons.favicons "
670 "SELECT * FROM favicons")) {
671 NOTREACHED() << "Unable to copy favicons.";
672 BeginTransaction();
673 return false;
674 }
675
676 if (!db_.Execute("DETACH new_favicons")) {
677 NOTREACHED() << "Unable to detach database.";
678 BeginTransaction();
679 return false;
680 }
681
682 db_.Close();
683
684 // Reset the DB to point to new file.
685 if (OpenDatabase(&db_, new_db_file) != sql::INIT_OK)
686 return false;
687
688 file_util::Delete(old_db_file, false);
689
690 InitFaviconsIndex();
691
692 // Reopen the transaction.
693 BeginTransaction();
694 use_top_sites_ = true;
695 return true;
696 }
697
InitIconMappingTable(sql::Connection * db,bool is_temporary)698 bool ThumbnailDatabase::InitIconMappingTable(sql::Connection* db,
699 bool is_temporary) {
700 const char* name = is_temporary ? "temp_icon_mapping" : "icon_mapping";
701 if (!db->DoesTableExist(name)) {
702 std::string sql;
703 sql.append("CREATE TABLE ");
704 sql.append(name);
705 sql.append("("
706 "id INTEGER PRIMARY KEY,"
707 "page_url LONGVARCHAR NOT NULL,"
708 "icon_id INTEGER)");
709 if (!db->Execute(sql.c_str()))
710 return false;
711 }
712 return true;
713 }
714
InitIconMappingIndex()715 void ThumbnailDatabase::InitIconMappingIndex() {
716 // Add an index on the url column. We ignore errors. Since this is always
717 // called during startup, the index will normally already exist.
718 db_.Execute("CREATE INDEX icon_mapping_page_url_idx"
719 " ON icon_mapping(page_url)");
720 db_.Execute("CREATE INDEX icon_mapping_icon_id_idx ON icon_mapping(icon_id)");
721 }
722
AddIconMapping(const GURL & page_url,FaviconID icon_id,bool is_temporary)723 IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url,
724 FaviconID icon_id,
725 bool is_temporary) {
726 const char* name = is_temporary ? "temp_icon_mapping" : "icon_mapping";
727 const char* statement_name =
728 is_temporary ? "add_temp_icon_mapping" : "add_icon_mapping";
729
730 std::string sql;
731 sql.append("INSERT INTO ");
732 sql.append(name);
733 sql.append("(page_url, icon_id) VALUES (?, ?)");
734
735 sql::Statement statement(
736 db_.GetCachedStatement(sql::StatementID(statement_name), sql.c_str()));
737 if (!statement)
738 return 0;
739
740 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
741 statement.BindInt64(1, icon_id);
742
743 if (!statement.Run())
744 return 0;
745
746 return db_.GetLastInsertRowId();
747 }
748
UpgradeToVersion4()749 bool ThumbnailDatabase::UpgradeToVersion4() {
750 // Set the default icon type as favicon, so the current data are set
751 // correctly.
752 if (!db_.Execute("ALTER TABLE favicons ADD icon_type INTEGER DEFAULT 1")) {
753 NOTREACHED();
754 return false;
755 }
756 meta_table_.SetVersionNumber(4);
757 meta_table_.SetCompatibleVersionNumber(std::min(4, kCompatibleVersionNumber));
758 return true;
759 }
760
761 } // namespace history
762