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 PersistentImageStore::PersistentImageStore(const base::FilePath
& path
)
147 base::FilePath::FromUTF8Unsafe("BookmarkImageAndUrlStore.db"))) {
150 bool PersistentImageStore::HasKey(const GURL
& page_url
) {
151 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
152 if (OpenDatabase() != sql::INIT_OK
)
155 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
156 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?"));
157 statement
.BindString(0, page_url
.possibly_invalid_spec());
159 int count
= statement
.Step() ? statement
.ColumnInt(0) : 0;
164 void PersistentImageStore::Insert(
165 const GURL
& page_url
,
166 scoped_refptr
<enhanced_bookmarks::ImageRecord
> record
) {
167 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
168 if (OpenDatabase() != sql::INIT_OK
)
171 Erase(page_url
); // Remove previous image for this url, if any.
172 sql::Statement
statement(db_
.GetCachedStatement(
174 "INSERT INTO images_by_url "
175 "(page_url, image_url, image_data, width, height, image_dominant_color)"
176 "VALUES (?, ?, ?, ?, ?, ?)"));
178 statement
.BindString(0, page_url
.possibly_invalid_spec());
179 statement
.BindString(1, record
->url
.possibly_invalid_spec());
181 scoped_refptr
<base::RefCountedMemory
> image_bytes
=
182 enhanced_bookmarks::BytesForImage(*record
->image
);
184 // Insert an empty image in case encoding fails.
185 if (!image_bytes
.get())
186 image_bytes
= enhanced_bookmarks::BytesForImage(gfx::Image());
188 CHECK(image_bytes
.get());
190 statement
.BindBlob(2, image_bytes
->front(), (int)image_bytes
->size());
192 statement
.BindInt(3, record
->image
->Size().width());
193 statement
.BindInt(4, record
->image
->Size().height());
194 statement
.BindInt(5, record
->dominant_color
);
198 void PersistentImageStore::Erase(const GURL
& page_url
) {
199 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
200 if (OpenDatabase() != sql::INIT_OK
)
203 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
204 "DELETE FROM images_by_url WHERE page_url = ?"));
205 statement
.BindString(0, page_url
.possibly_invalid_spec());
209 scoped_refptr
<enhanced_bookmarks::ImageRecord
> PersistentImageStore::Get(
210 const GURL
& page_url
) {
211 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
212 scoped_refptr
<enhanced_bookmarks::ImageRecord
> image_record(
213 new enhanced_bookmarks::ImageRecord());
214 if (OpenDatabase() != sql::INIT_OK
)
217 bool stored_image_record_needs_update
= false;
219 // Scope the SELECT statement.
221 sql::Statement
statement(db_
.GetCachedStatement(
223 "SELECT image_data, image_url, image_dominant_color FROM images_by_url "
224 "WHERE page_url = ?"));
226 statement
.BindString(0, page_url
.possibly_invalid_spec());
228 if (!statement
.Step())
232 if (statement
.ColumnByteLength(0) > 0) {
233 scoped_refptr
<base::RefCountedBytes
> data(new base::RefCountedBytes());
234 statement
.ColumnBlobAsVector(0, &data
->data());
235 *image_record
->image
= enhanced_bookmarks::ImageForBytes(data
);
239 image_record
->url
= GURL(statement
.ColumnString(1));
242 if (statement
.ColumnType(2) != sql::COLUMN_TYPE_NULL
) {
243 image_record
->dominant_color
= SkColor(statement
.ColumnInt(2));
245 // The dominant color was not computed when the image was first
248 image_record
->dominant_color
=
249 enhanced_bookmarks::DominantColorForImage(*image_record
->image
);
250 stored_image_record_needs_update
= true;
253 // Make sure there is only one record for page_url.
254 DCHECK(!statement
.Step());
257 if (stored_image_record_needs_update
) {
259 Insert(page_url
, image_record
);
265 gfx::Size
PersistentImageStore::GetSize(const GURL
& page_url
) {
266 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
267 if (OpenDatabase() != sql::INIT_OK
)
270 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
271 "SELECT width, height FROM images_by_url WHERE page_url = ?"));
273 statement
.BindString(0, page_url
.possibly_invalid_spec());
275 while (statement
.Step()) {
276 if (statement
.ColumnByteLength(0) > 0) {
277 int width
= statement
.ColumnInt(0);
278 int height
= statement
.ColumnInt(1);
279 return gfx::Size(width
, height
);
285 void PersistentImageStore::GetAllPageUrls(std::set
<GURL
>* urls
) {
286 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
287 DCHECK(urls
->empty());
288 if (OpenDatabase() != sql::INIT_OK
)
291 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
292 "SELECT page_url FROM images_by_url"));
293 while (statement
.Step())
294 urls
->insert(GURL(statement
.ColumnString(0)));
297 void PersistentImageStore::ClearAll() {
298 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
299 if (OpenDatabase() != sql::INIT_OK
)
302 sql::Statement
statement(db_
.GetCachedStatement(
303 SQL_FROM_HERE
, "DELETE FROM images_by_url"));
307 int64
PersistentImageStore::GetStoreSizeInBytes() {
308 base::File
file(path_
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
309 return file
.IsValid() ? file
.GetLength() : -1;
312 PersistentImageStore::~PersistentImageStore() {
313 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
316 sql::InitStatus
PersistentImageStore::OpenDatabase() {
317 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
322 const size_t kAttempts
= 2;
324 sql::InitStatus status
= sql::INIT_FAILURE
;
325 for (size_t i
= 0; i
< kAttempts
; ++i
) {
326 status
= OpenDatabaseImpl(db_
, meta_table_
, path_
);
327 if (status
== sql::INIT_OK
)
330 // Can't open, raze().
336 DCHECK(false) << "Can't open image DB";
337 return sql::INIT_FAILURE
;