1 // Copyright 2014 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.
5 #include "components/enhanced_bookmarks/persistent_image_store.h"
7 #include "base/files/file.h"
8 #include "base/logging.h"
9 #include "components/enhanced_bookmarks/image_store_util.h"
10 #include "sql/statement.h"
11 #include "sql/transaction.h"
12 #include "ui/gfx/geometry/size.h"
16 // Current version number. Databases are written at the "current" version
17 // number, but any previous version that can read the "compatible" one can make
18 // do with our database without *too* many bad effects.
19 const int kCurrentVersionNumber
= 2;
20 const int kCompatibleVersionNumber
= 1;
22 bool InitTables(sql::Connection
& db
) {
23 const char kTableSql
[] =
24 "CREATE TABLE IF NOT EXISTS images_by_url ("
25 "page_url LONGVARCHAR NOT NULL,"
26 "image_url LONGVARCHAR NOT NULL,"
30 "image_dominant_color INTEGER"
32 if (!db
.Execute(kTableSql
))
37 bool InitIndices(sql::Connection
& db
) {
38 const char kIndexSql
[] =
39 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)";
40 if (!db
.Execute(kIndexSql
))
45 // V1 didn't store the dominant color of an image. Creates the column to store
46 // a dominant color in the database. The value will be filled when queried for a
48 bool MigrateImagesWithNoDominantColor(sql::Connection
& db
) {
49 if (!db
.DoesTableExist("images_by_url")) {
50 NOTREACHED() << "images_by_url table should exist before migration.";
54 if (!db
.DoesColumnExist("images_by_url", "image_dominant_color")) {
55 // The initial version doesn't have the image_dominant_color column, it is
56 // added to the table here.
58 "ALTER TABLE images_by_url "
59 "ADD COLUMN image_dominant_color INTEGER")) {
66 sql::InitStatus
EnsureCurrentVersion(sql::Connection
& db
,
67 sql::MetaTable
& meta_table
) {
68 // 1- Newer databases than designed for can't be read.
69 if (meta_table
.GetCompatibleVersionNumber() > kCurrentVersionNumber
) {
70 LOG(WARNING
) << "Image DB is too new.";
71 return sql::INIT_TOO_NEW
;
74 int cur_version
= meta_table
.GetVersionNumber();
76 // 2- Put migration code here.
78 if (cur_version
== 1) {
79 if (!MigrateImagesWithNoDominantColor(db
)) {
80 LOG(WARNING
) << "Unable to update image DB to version 1.";
81 return sql::INIT_FAILURE
;
84 meta_table
.SetVersionNumber(cur_version
);
85 meta_table
.SetCompatibleVersionNumber(
86 std::min(cur_version
, kCompatibleVersionNumber
));
89 // 3- When the version is too old, just try to continue anyway, there should
90 // not be a released product that makes a database too old to handle.
91 LOG_IF(WARNING
, cur_version
< kCurrentVersionNumber
)
92 << "Image DB version " << cur_version
<< " is too old to handle.";
97 sql::InitStatus
OpenDatabaseImpl(sql::Connection
& db
,
98 sql::MetaTable
& meta_table
,
99 const base::FilePath
& db_path
) {
100 DCHECK(!db
.is_open());
102 db
.set_histogram_tag("BookmarkImages");
103 // TODO(noyau): Set page and cache sizes?
104 // TODO(noyau): Set error callback?
106 // Run the database in exclusive mode. Nobody else should be accessing the
107 // database while running, and this will give somewhat improved performance.
108 db
.set_exclusive_locking();
110 if (!db
.Open(db_path
))
111 return sql::INIT_FAILURE
;
113 // Scope initialization in a transaction so it can't be partially initialized.
114 sql::Transaction
transaction(&db
);
115 if (!transaction
.Begin())
116 return sql::INIT_FAILURE
;
118 // Initialize the meta table.
119 int cur_version
= meta_table
.DoesTableExist(&db
)
120 ? kCurrentVersionNumber
121 : 1; // Only v1 didn't have a meta table.
122 if (!meta_table
.Init(&db
, cur_version
,
123 std::min(cur_version
, kCompatibleVersionNumber
)))
124 return sql::INIT_FAILURE
;
126 // Create the tables.
127 if (!InitTables(db
) || !InitIndices(db
))
128 return sql::INIT_FAILURE
;
130 // Check the version.
131 sql::InitStatus version_status
= EnsureCurrentVersion(db
, meta_table
);
132 if (version_status
!= sql::INIT_OK
)
133 return version_status
;
135 // Initialization is complete.
136 if (!transaction
.Commit())
137 return sql::INIT_FAILURE
;
144 const char PersistentImageStore::kBookmarkImageStoreDb
[] =
145 "BookmarkImageAndUrlStore.db";
147 PersistentImageStore::PersistentImageStore(const base::FilePath
& path
)
150 base::FilePath::FromUTF8Unsafe(kBookmarkImageStoreDb
))) {
153 bool PersistentImageStore::HasKey(const GURL
& page_url
) {
154 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
155 if (OpenDatabase() != sql::INIT_OK
)
158 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
159 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?"));
160 statement
.BindString(0, page_url
.possibly_invalid_spec());
162 int count
= statement
.Step() ? statement
.ColumnInt(0) : 0;
167 void PersistentImageStore::Insert(
168 const GURL
& page_url
,
169 scoped_refptr
<enhanced_bookmarks::ImageRecord
> record
) {
170 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
171 if (OpenDatabase() != sql::INIT_OK
)
174 Erase(page_url
); // Remove previous image for this url, if any.
175 sql::Statement
statement(db_
.GetCachedStatement(
177 "INSERT INTO images_by_url "
178 "(page_url, image_url, image_data, width, height, image_dominant_color)"
179 "VALUES (?, ?, ?, ?, ?, ?)"));
181 statement
.BindString(0, page_url
.possibly_invalid_spec());
182 statement
.BindString(1, record
->url
.possibly_invalid_spec());
184 scoped_refptr
<base::RefCountedMemory
> image_bytes
=
185 enhanced_bookmarks::BytesForImage(*record
->image
);
187 // Insert an empty image in case encoding fails.
188 if (!image_bytes
.get())
189 image_bytes
= enhanced_bookmarks::BytesForImage(gfx::Image());
191 CHECK(image_bytes
.get());
193 statement
.BindBlob(2, image_bytes
->front(), (int)image_bytes
->size());
195 statement
.BindInt(3, record
->image
->Size().width());
196 statement
.BindInt(4, record
->image
->Size().height());
197 statement
.BindInt(5, record
->dominant_color
);
201 void PersistentImageStore::Erase(const GURL
& page_url
) {
202 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
203 if (OpenDatabase() != sql::INIT_OK
)
206 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
207 "DELETE FROM images_by_url WHERE page_url = ?"));
208 statement
.BindString(0, page_url
.possibly_invalid_spec());
212 scoped_refptr
<enhanced_bookmarks::ImageRecord
> PersistentImageStore::Get(
213 const GURL
& page_url
) {
214 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
215 scoped_refptr
<enhanced_bookmarks::ImageRecord
> image_record(
216 new enhanced_bookmarks::ImageRecord());
217 if (OpenDatabase() != sql::INIT_OK
)
220 bool stored_image_record_needs_update
= false;
222 // Scope the SELECT statement.
224 sql::Statement
statement(db_
.GetCachedStatement(
226 "SELECT image_data, image_url, image_dominant_color FROM images_by_url "
227 "WHERE page_url = ?"));
229 statement
.BindString(0, page_url
.possibly_invalid_spec());
231 if (!statement
.Step())
235 if (statement
.ColumnByteLength(0) > 0) {
236 scoped_refptr
<base::RefCountedBytes
> data(new base::RefCountedBytes());
237 statement
.ColumnBlobAsVector(0, &data
->data());
238 *image_record
->image
= enhanced_bookmarks::ImageForBytes(data
);
242 image_record
->url
= GURL(statement
.ColumnString(1));
245 if (statement
.ColumnType(2) != sql::COLUMN_TYPE_NULL
) {
246 image_record
->dominant_color
= SkColor(statement
.ColumnInt(2));
248 // The dominant color was not computed when the image was first
251 image_record
->dominant_color
=
252 enhanced_bookmarks::DominantColorForImage(*image_record
->image
);
253 stored_image_record_needs_update
= true;
256 // Make sure there is only one record for page_url.
257 DCHECK(!statement
.Step());
260 if (stored_image_record_needs_update
) {
262 Insert(page_url
, image_record
);
268 gfx::Size
PersistentImageStore::GetSize(const GURL
& page_url
) {
269 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
270 if (OpenDatabase() != sql::INIT_OK
)
273 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
274 "SELECT width, height FROM images_by_url WHERE page_url = ?"));
276 statement
.BindString(0, page_url
.possibly_invalid_spec());
278 while (statement
.Step()) {
279 if (statement
.ColumnByteLength(0) > 0) {
280 int width
= statement
.ColumnInt(0);
281 int height
= statement
.ColumnInt(1);
282 return gfx::Size(width
, height
);
288 void PersistentImageStore::GetAllPageUrls(std::set
<GURL
>* urls
) {
289 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
290 DCHECK(urls
->empty());
291 if (OpenDatabase() != sql::INIT_OK
)
294 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
295 "SELECT page_url FROM images_by_url"));
296 while (statement
.Step())
297 urls
->insert(GURL(statement
.ColumnString(0)));
300 void PersistentImageStore::ClearAll() {
301 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
302 if (OpenDatabase() != sql::INIT_OK
)
305 sql::Statement
statement(db_
.GetCachedStatement(
306 SQL_FROM_HERE
, "DELETE FROM images_by_url"));
310 int64
PersistentImageStore::GetStoreSizeInBytes() {
311 base::File
file(path_
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
312 return file
.IsValid() ? file
.GetLength() : -1;
315 PersistentImageStore::~PersistentImageStore() {
316 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
319 sql::InitStatus
PersistentImageStore::OpenDatabase() {
320 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
325 const size_t kAttempts
= 2;
327 sql::InitStatus status
= sql::INIT_FAILURE
;
328 for (size_t i
= 0; i
< kAttempts
; ++i
) {
329 status
= OpenDatabaseImpl(db_
, meta_table_
, path_
);
330 if (status
== sql::INIT_OK
)
333 // Can't open, raze().
339 DCHECK(false) << "Can't open image DB";
340 return sql::INIT_FAILURE
;