Cast: Add an option to turn on non-blocking IO on Windows
[chromium-blink-merge.git] / components / enhanced_bookmarks / persistent_image_store.cc
blob1061b69f8b9fd01632589160f90269666e5353a7
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 PersistentImageStore::PersistentImageStore(const base::FilePath& path)
145 : ImageStore(),
146 path_(path.Append(
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)
153 return false;
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;
161 return !!count;
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)
169 return;
171 Erase(page_url); // Remove previous image for this url, if any.
172 sql::Statement statement(db_.GetCachedStatement(
173 SQL_FROM_HERE,
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);
195 statement.Run();
198 void PersistentImageStore::Erase(const GURL& page_url) {
199 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
200 if (OpenDatabase() != sql::INIT_OK)
201 return;
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());
206 statement.Run();
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)
214 return image_record;
216 bool stored_image_record_needs_update = false;
218 // Scope the SELECT statement.
220 sql::Statement statement(db_.GetCachedStatement(
221 SQL_FROM_HERE,
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())
228 return image_record;
230 // Image.
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);
237 // URL.
238 image_record.url = GURL(statement.ColumnString(1));
240 // Dominant color.
241 if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL) {
242 image_record.dominant_color = SkColor(statement.ColumnInt(2));
243 } else {
244 // The dominant color was not computed when the image was first
245 // stored.
246 // Compute it now.
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) {
257 Erase(page_url);
258 Insert(page_url, image_record);
261 return image_record;
264 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) {
265 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
266 if (OpenDatabase() != sql::INIT_OK)
267 return gfx::Size();
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);
281 return gfx::Size();
284 void PersistentImageStore::GetAllPageUrls(std::set<GURL>* urls) {
285 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
286 DCHECK(urls->empty());
287 if (OpenDatabase() != sql::INIT_OK)
288 return;
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)
299 return;
301 sql::Statement statement(db_.GetCachedStatement(
302 SQL_FROM_HERE, "DELETE FROM images_by_url"));
303 statement.Run();
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());
318 if (db_.is_open())
319 return sql::INIT_OK;
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)
327 return status;
329 // Can't open, raze().
330 if (db_.is_open())
331 db_.Raze();
332 db_.Close();
335 DCHECK(false) << "Can't open image DB";
336 return sql::INIT_FAILURE;