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 "components/enhanced_bookmarks/image_store_util.h"
9 #include "sql/statement.h"
10 #include "sql/transaction.h"
11 #include "ui/gfx/geometry/size.h"
16 bool InitTables(sql::Connection
& db
) {
17 const char kTableSql
[] =
18 "CREATE TABLE IF NOT EXISTS images_by_url ("
19 "page_url LONGVARCHAR NOT NULL,"
20 "image_url LONGVARCHAR NOT NULL,"
25 if (!db
.Execute(kTableSql
))
30 bool InitIndices(sql::Connection
& db
) {
31 const char kIndexSql
[] =
32 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)";
33 if (!db
.Execute(kIndexSql
))
38 sql::InitStatus
OpenDatabaseImpl(sql::Connection
& db
,
39 const base::FilePath
& db_path
) {
40 DCHECK(!db
.is_open());
42 db
.set_histogram_tag("BookmarkImages");
43 // TODO(noyau): Set page and cache sizes?
44 // TODO(noyau): Set error callback?
46 // Run the database in exclusive mode. Nobody else should be accessing the
47 // database while we're running, and this will give somewhat improved perf.
48 db
.set_exclusive_locking();
50 if (!db
.Open(db_path
))
51 return sql::INIT_FAILURE
;
53 // Scope initialization in a transaction so we can't be partially initialized.
54 sql::Transaction
transaction(&db
);
55 if (!transaction
.Begin())
56 return sql::INIT_FAILURE
;
59 if (!InitTables(db
) ||
61 return sql::INIT_FAILURE
;
64 // Initialization is complete.
65 if (!transaction
.Commit())
66 return sql::INIT_FAILURE
;
73 PersistentImageStore::PersistentImageStore(const base::FilePath
& path
)
76 base::FilePath::FromUTF8Unsafe("BookmarkImageAndUrlStore.db"))) {
79 bool PersistentImageStore::HasKey(const GURL
& page_url
) {
80 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
81 if (OpenDatabase() != sql::INIT_OK
)
84 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
85 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?"));
86 statement
.BindString(0, page_url
.possibly_invalid_spec());
88 int count
= statement
.Step() ? statement
.ColumnInt(0) : 0;
93 void PersistentImageStore::Insert(const GURL
& page_url
,
94 const GURL
& image_url
,
95 const gfx::Image
& image
) {
96 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
97 if (OpenDatabase() != sql::INIT_OK
)
100 Erase(page_url
); // Remove previous image for this url, if any.
101 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
102 "INSERT INTO images_by_url "
103 "(page_url, image_url, image_data, width, height)"
104 "VALUES (?, ?, ?, ?, ?)"));
106 statement
.BindString(0, page_url
.possibly_invalid_spec());
107 statement
.BindString(1, image_url
.possibly_invalid_spec());
109 scoped_refptr
<base::RefCountedMemory
> image_bytes
=
110 enhanced_bookmarks::BytesForImage(image
);
112 // Insert an empty image in case encoding fails.
113 if (!image_bytes
.get())
114 image_bytes
= enhanced_bookmarks::BytesForImage(gfx::Image());
116 CHECK(image_bytes
.get());
118 statement
.BindBlob(2, image_bytes
->front(), (int)image_bytes
->size());
120 statement
.BindInt(3, image
.Size().width());
121 statement
.BindInt(4, image
.Size().height());
125 void PersistentImageStore::Erase(const GURL
& page_url
) {
126 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
127 if (OpenDatabase() != sql::INIT_OK
)
130 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
131 "DELETE FROM images_by_url WHERE page_url = ?"));
132 statement
.BindString(0, page_url
.possibly_invalid_spec());
136 std::pair
<gfx::Image
, GURL
> PersistentImageStore::Get(const GURL
& page_url
) {
137 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
138 if (OpenDatabase() != sql::INIT_OK
)
139 return std::make_pair(gfx::Image(), GURL());
141 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
142 "SELECT image_data, image_url FROM images_by_url WHERE page_url = ?"));
144 statement
.BindString(0, page_url
.possibly_invalid_spec());
146 while (statement
.Step()) {
147 if (statement
.ColumnByteLength(0) > 0) {
148 scoped_refptr
<base::RefCountedBytes
> data(new base::RefCountedBytes());
149 statement
.ColumnBlobAsVector(0, &data
->data());
151 return std::make_pair(enhanced_bookmarks::ImageForBytes(data
),
152 GURL(statement
.ColumnString(1)));
156 return std::make_pair(gfx::Image(), GURL());
159 gfx::Size
PersistentImageStore::GetSize(const GURL
& page_url
) {
160 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
161 if (OpenDatabase() != sql::INIT_OK
)
164 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
165 "SELECT width, height FROM images_by_url WHERE page_url = ?"));
167 statement
.BindString(0, page_url
.possibly_invalid_spec());
169 while (statement
.Step()) {
170 if (statement
.ColumnByteLength(0) > 0) {
171 int width
= statement
.ColumnInt(0);
172 int height
= statement
.ColumnInt(1);
173 return gfx::Size(width
, height
);
179 void PersistentImageStore::GetAllPageUrls(std::set
<GURL
>* urls
) {
180 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
181 DCHECK(urls
->empty());
182 if (OpenDatabase() != sql::INIT_OK
)
185 sql::Statement
statement(db_
.GetCachedStatement(SQL_FROM_HERE
,
186 "SELECT page_url FROM images_by_url"));
187 while (statement
.Step())
188 urls
->insert(GURL(statement
.ColumnString(0)));
191 void PersistentImageStore::ClearAll() {
192 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
193 if (OpenDatabase() != sql::INIT_OK
)
196 sql::Statement
statement(db_
.GetCachedStatement(
197 SQL_FROM_HERE
, "DELETE FROM images_by_url"));
201 int64
PersistentImageStore::GetStoreSizeInBytes() {
202 base::File
file(path_
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
203 return file
.IsValid() ? file
.GetLength() : -1;
206 PersistentImageStore::~PersistentImageStore() {
207 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
210 sql::InitStatus
PersistentImageStore::OpenDatabase() {
211 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
216 const size_t kAttempts
= 2;
218 sql::InitStatus status
= sql::INIT_FAILURE
;
219 for (size_t i
= 0; i
< kAttempts
; ++i
) {
220 status
= OpenDatabaseImpl(db_
, path_
);
221 if (status
== sql::INIT_OK
)
224 // Can't open, raze().
230 DCHECK(false) << "Can't open image DB";
231 return sql::INIT_FAILURE
;