Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / enhanced_bookmarks / persistent_image_store.cc
blobc35900e77c4832147781527af239d9e8e40d1c15
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"
13 #include "url/gurl.h"
15 namespace {
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,"
27 "image_data BLOB,"
28 "width INTEGER,"
29 "height INTEGER,"
30 "image_dominant_color INTEGER"
31 ")";
32 if (!db.Execute(kTableSql))
33 return false;
34 return true;
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))
41 return false;
42 return true;
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
47 // one time cost.
48 bool MigrateImagesWithNoDominantColor(sql::Connection& db) {
49 if (!db.DoesTableExist("images_by_url")) {
50 NOTREACHED() << "images_by_url table should exist before migration.";
51 return false;
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.
57 if (!db.Execute(
58 "ALTER TABLE images_by_url "
59 "ADD COLUMN image_dominant_color INTEGER")) {
60 return false;
63 return true;
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;
83 ++cur_version;
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.";
94 return sql::INIT_OK;
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;
139 return sql::INIT_OK;
142 } // namespace
144 const char PersistentImageStore::kBookmarkImageStoreDb[] =
145 "BookmarkImageAndUrlStore.db";
147 PersistentImageStore::PersistentImageStore(const base::FilePath& path)
148 : ImageStore(),
149 path_(path.Append(
150 base::FilePath::FromUTF8Unsafe(kBookmarkImageStoreDb))) {
153 bool PersistentImageStore::HasKey(const GURL& page_url) {
154 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
155 if (OpenDatabase() != sql::INIT_OK)
156 return false;
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;
164 return !!count;
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)
172 return;
174 Erase(page_url); // Remove previous image for this url, if any.
175 sql::Statement statement(db_.GetCachedStatement(
176 SQL_FROM_HERE,
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);
198 statement.Run();
201 void PersistentImageStore::Erase(const GURL& page_url) {
202 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
203 if (OpenDatabase() != sql::INIT_OK)
204 return;
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());
209 statement.Run();
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)
218 return image_record;
220 bool stored_image_record_needs_update = false;
222 // Scope the SELECT statement.
224 sql::Statement statement(db_.GetCachedStatement(
225 SQL_FROM_HERE,
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())
232 return image_record;
234 // Image.
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);
241 // URL.
242 image_record->url = GURL(statement.ColumnString(1));
244 // Dominant color.
245 if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL) {
246 image_record->dominant_color = SkColor(statement.ColumnInt(2));
247 } else {
248 // The dominant color was not computed when the image was first
249 // stored.
250 // Compute it now.
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) {
261 Erase(page_url);
262 Insert(page_url, image_record);
265 return image_record;
268 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) {
269 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
270 if (OpenDatabase() != sql::INIT_OK)
271 return gfx::Size();
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);
285 return gfx::Size();
288 void PersistentImageStore::GetAllPageUrls(std::set<GURL>* urls) {
289 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
290 DCHECK(urls->empty());
291 if (OpenDatabase() != sql::INIT_OK)
292 return;
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)
303 return;
305 sql::Statement statement(db_.GetCachedStatement(
306 SQL_FROM_HERE, "DELETE FROM images_by_url"));
307 statement.Run();
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());
322 if (db_.is_open())
323 return sql::INIT_OK;
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)
331 return status;
333 // Can't open, raze().
334 if (db_.is_open())
335 db_.Raze();
336 db_.Close();
339 DCHECK(false) << "Can't open image DB";
340 return sql::INIT_FAILURE;