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 const 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 enhanced_bookmarks::ImageRecord
PersistentImageStore::Get(
210 const GURL
& page_url
) {
211 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
212 enhanced_bookmarks::ImageRecord image_record
;
213 if (OpenDatabase() != sql::INIT_OK
)
216 bool stored_image_record_needs_update
= false;
218 // Scope the SELECT statement.
220 sql::Statement
statement(db_
.GetCachedStatement(
222 "SELECT image_data, image_url, image_dominant_color FROM images_by_url "
223 "WHERE page_url = ?"));
225 statement
.BindString(0, page_url
.possibly_invalid_spec());
227 if (!statement
.Step())
231 if (statement
.ColumnByteLength(0) > 0) {
232 scoped_refptr
<base::RefCountedBytes
> data(new base::RefCountedBytes());
233 statement
.ColumnBlobAsVector(0, &data
->data());
234 image_record
.image
= enhanced_bookmarks::ImageForBytes(data
);
238 image_record
.url
= GURL(statement
.ColumnString(1));
241 if (statement
.ColumnType(2) != sql::COLUMN_TYPE_NULL
) {
242 image_record
.dominant_color
= SkColor(statement
.ColumnInt(2));
244 // The dominant color was not computed when the image was first
247 image_record
.dominant_color
=
248 enhanced_bookmarks::DominantColorForImage(image_record
.image
);
249 stored_image_record_needs_update
= true;
252 // Make sure there is only one record for page_url.
253 DCHECK(!statement
.Step());
256 if (stored_image_record_needs_update
) {
258 Insert(page_url
, image_record
);
264 gfx::Size
PersistentImageStore::GetSize(const GURL
& page_url
) {
265 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
266 if (OpenDatabase() != sql::INIT_OK
)
269 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
270 "SELECT width, height FROM images_by_url WHERE page_url = ?"));
272 statement
.BindString(0, page_url
.possibly_invalid_spec());
274 while (statement
.Step()) {
275 if (statement
.ColumnByteLength(0) > 0) {
276 int width
= statement
.ColumnInt(0);
277 int height
= statement
.ColumnInt(1);
278 return gfx::Size(width
, height
);
284 void PersistentImageStore::GetAllPageUrls(std::set
<GURL
>* urls
) {
285 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
286 DCHECK(urls
->empty());
287 if (OpenDatabase() != sql::INIT_OK
)
290 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
291 "SELECT page_url FROM images_by_url"));
292 while (statement
.Step())
293 urls
->insert(GURL(statement
.ColumnString(0)));
296 void PersistentImageStore::ClearAll() {
297 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
298 if (OpenDatabase() != sql::INIT_OK
)
301 sql::Statement
statement(db_
.GetCachedStatement(
302 SQL_FROM_HERE
, "DELETE FROM images_by_url"));
306 int64
PersistentImageStore::GetStoreSizeInBytes() {
307 base::File
file(path_
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
308 return file
.IsValid() ? file
.GetLength() : -1;
311 PersistentImageStore::~PersistentImageStore() {
312 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
315 sql::InitStatus
PersistentImageStore::OpenDatabase() {
316 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
321 const size_t kAttempts
= 2;
323 sql::InitStatus status
= sql::INIT_FAILURE
;
324 for (size_t i
= 0; i
< kAttempts
; ++i
) {
325 status
= OpenDatabaseImpl(db_
, meta_table_
, path_
);
326 if (status
== sql::INIT_OK
)
329 // Can't open, raze().
335 DCHECK(false) << "Can't open image DB";
336 return sql::INIT_FAILURE
;