Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / components / enhanced_bookmarks / persistent_image_store.cc
blob69365abfe4c44fcd93a886eb0f8bfcc8eaba41a2
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 scoped_refptr<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 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)
215 return image_record;
217 bool stored_image_record_needs_update = false;
219 // Scope the SELECT statement.
221 sql::Statement statement(db_.GetCachedStatement(
222 SQL_FROM_HERE,
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())
229 return image_record;
231 // Image.
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);
238 // URL.
239 image_record->url = GURL(statement.ColumnString(1));
241 // Dominant color.
242 if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL) {
243 image_record->dominant_color = SkColor(statement.ColumnInt(2));
244 } else {
245 // The dominant color was not computed when the image was first
246 // stored.
247 // Compute it now.
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) {
258 Erase(page_url);
259 Insert(page_url, image_record);
262 return image_record;
265 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) {
266 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
267 if (OpenDatabase() != sql::INIT_OK)
268 return gfx::Size();
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);
282 return gfx::Size();
285 void PersistentImageStore::GetAllPageUrls(std::set<GURL>* urls) {
286 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
287 DCHECK(urls->empty());
288 if (OpenDatabase() != sql::INIT_OK)
289 return;
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)
300 return;
302 sql::Statement statement(db_.GetCachedStatement(
303 SQL_FROM_HERE, "DELETE FROM images_by_url"));
304 statement.Run();
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());
319 if (db_.is_open())
320 return sql::INIT_OK;
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)
328 return status;
330 // Can't open, raze().
331 if (db_.is_open())
332 db_.Raze();
333 db_.Close();
336 DCHECK(false) << "Can't open image DB";
337 return sql::INIT_FAILURE;