| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/enhanced_bookmarks/persistent_image_store.h" | 5 #include "components/enhanced_bookmarks/persistent_image_store.h" |
| 6 | 6 |
| 7 #include "base/files/file.h" | 7 #include "base/files/file.h" |
| 8 #include "base/logging.h" |
| 8 #include "components/enhanced_bookmarks/image_store_util.h" | 9 #include "components/enhanced_bookmarks/image_store_util.h" |
| 9 #include "sql/statement.h" | 10 #include "sql/statement.h" |
| 10 #include "sql/transaction.h" | 11 #include "sql/transaction.h" |
| 11 #include "ui/gfx/geometry/size.h" | 12 #include "ui/gfx/geometry/size.h" |
| 12 #include "url/gurl.h" | 13 #include "url/gurl.h" |
| 13 | 14 |
| 14 namespace { | 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; |
| 15 | 21 |
| 16 bool InitTables(sql::Connection& db) { | 22 bool InitTables(sql::Connection& db) { |
| 17 const char kTableSql[] = | 23 const char kTableSql[] = |
| 18 "CREATE TABLE IF NOT EXISTS images_by_url (" | 24 "CREATE TABLE IF NOT EXISTS images_by_url (" |
| 19 "page_url LONGVARCHAR NOT NULL," | 25 "page_url LONGVARCHAR NOT NULL," |
| 20 "image_url LONGVARCHAR NOT NULL," | 26 "image_url LONGVARCHAR NOT NULL," |
| 21 "image_data BLOB," | 27 "image_data BLOB," |
| 22 "width INTEGER," | 28 "width INTEGER," |
| 23 "height INTEGER" | 29 "height INTEGER," |
| 30 "image_dominant_color INTEGER" |
| 24 ")"; | 31 ")"; |
| 25 if (!db.Execute(kTableSql)) | 32 if (!db.Execute(kTableSql)) |
| 26 return false; | 33 return false; |
| 27 return true; | 34 return true; |
| 28 } | 35 } |
| 29 | 36 |
| 30 bool InitIndices(sql::Connection& db) { | 37 bool InitIndices(sql::Connection& db) { |
| 31 const char kIndexSql[] = | 38 const char kIndexSql[] = |
| 32 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)"; | 39 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)"; |
| 33 if (!db.Execute(kIndexSql)) | 40 if (!db.Execute(kIndexSql)) |
| 34 return false; | 41 return false; |
| 35 return true; | 42 return true; |
| 36 } | 43 } |
| 37 | 44 |
| 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; |
| 52 } |
| 53 |
| 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; |
| 61 } |
| 62 } |
| 63 return true; |
| 64 } |
| 65 |
| 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; |
| 72 } |
| 73 |
| 74 int cur_version = meta_table.GetVersionNumber(); |
| 75 |
| 76 // 2- Put migration code here. |
| 77 |
| 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; |
| 82 } |
| 83 ++cur_version; |
| 84 meta_table.SetVersionNumber(cur_version); |
| 85 meta_table.SetCompatibleVersionNumber( |
| 86 std::min(cur_version, kCompatibleVersionNumber)); |
| 87 } |
| 88 |
| 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."; |
| 93 |
| 94 return sql::INIT_OK; |
| 95 } |
| 96 |
| 38 sql::InitStatus OpenDatabaseImpl(sql::Connection& db, | 97 sql::InitStatus OpenDatabaseImpl(sql::Connection& db, |
| 98 sql::MetaTable& meta_table, |
| 39 const base::FilePath& db_path) { | 99 const base::FilePath& db_path) { |
| 40 DCHECK(!db.is_open()); | 100 DCHECK(!db.is_open()); |
| 41 | 101 |
| 42 db.set_histogram_tag("BookmarkImages"); | 102 db.set_histogram_tag("BookmarkImages"); |
| 43 // TODO(noyau): Set page and cache sizes? | 103 // TODO(noyau): Set page and cache sizes? |
| 44 // TODO(noyau): Set error callback? | 104 // TODO(noyau): Set error callback? |
| 45 | 105 |
| 46 // Run the database in exclusive mode. Nobody else should be accessing the | 106 // 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. | 107 // database while running, and this will give somewhat improved performance. |
| 48 db.set_exclusive_locking(); | 108 db.set_exclusive_locking(); |
| 49 | 109 |
| 50 if (!db.Open(db_path)) | 110 if (!db.Open(db_path)) |
| 51 return sql::INIT_FAILURE; | 111 return sql::INIT_FAILURE; |
| 52 | 112 |
| 53 // Scope initialization in a transaction so we can't be partially initialized. | 113 // Scope initialization in a transaction so it can't be partially initialized. |
| 54 sql::Transaction transaction(&db); | 114 sql::Transaction transaction(&db); |
| 55 if (!transaction.Begin()) | 115 if (!transaction.Begin()) |
| 56 return sql::INIT_FAILURE; | 116 return sql::INIT_FAILURE; |
| 57 | 117 |
| 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; |
| 125 |
| 58 // Create the tables. | 126 // Create the tables. |
| 59 if (!InitTables(db) || | 127 if (!InitTables(db) || !InitIndices(db)) |
| 60 !InitIndices(db)) { | |
| 61 return sql::INIT_FAILURE; | 128 return sql::INIT_FAILURE; |
| 62 } | 129 |
| 130 // Check the version. |
| 131 sql::InitStatus version_status = EnsureCurrentVersion(db, meta_table); |
| 132 if (version_status != sql::INIT_OK) |
| 133 return version_status; |
| 63 | 134 |
| 64 // Initialization is complete. | 135 // Initialization is complete. |
| 65 if (!transaction.Commit()) | 136 if (!transaction.Commit()) |
| 66 return sql::INIT_FAILURE; | 137 return sql::INIT_FAILURE; |
| 67 | 138 |
| 68 return sql::INIT_OK; | 139 return sql::INIT_OK; |
| 69 } | 140 } |
| 70 | 141 |
| 71 } // namespace | 142 } // namespace |
| 72 | 143 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 83 | 154 |
| 84 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, | 155 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 85 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?")); | 156 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?")); |
| 86 statement.BindString(0, page_url.possibly_invalid_spec()); | 157 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 87 | 158 |
| 88 int count = statement.Step() ? statement.ColumnInt(0) : 0; | 159 int count = statement.Step() ? statement.ColumnInt(0) : 0; |
| 89 | 160 |
| 90 return !!count; | 161 return !!count; |
| 91 } | 162 } |
| 92 | 163 |
| 93 void PersistentImageStore::Insert(const GURL& page_url, | 164 void PersistentImageStore::Insert( |
| 94 const GURL& image_url, | 165 const GURL& page_url, |
| 95 const gfx::Image& image) { | 166 const enhanced_bookmarks::ImageRecord& record) { |
| 96 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 167 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| 97 if (OpenDatabase() != sql::INIT_OK) | 168 if (OpenDatabase() != sql::INIT_OK) |
| 98 return; | 169 return; |
| 99 | 170 |
| 100 Erase(page_url); // Remove previous image for this url, if any. | 171 Erase(page_url); // Remove previous image for this url, if any. |
| 101 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, | 172 sql::Statement statement(db_.GetCachedStatement( |
| 173 SQL_FROM_HERE, |
| 102 "INSERT INTO images_by_url " | 174 "INSERT INTO images_by_url " |
| 103 "(page_url, image_url, image_data, width, height)" | 175 "(page_url, image_url, image_data, width, height, image_dominant_color)" |
| 104 "VALUES (?, ?, ?, ?, ?)")); | 176 "VALUES (?, ?, ?, ?, ?, ?)")); |
| 105 | 177 |
| 106 statement.BindString(0, page_url.possibly_invalid_spec()); | 178 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 107 statement.BindString(1, image_url.possibly_invalid_spec()); | 179 statement.BindString(1, record.url.possibly_invalid_spec()); |
| 108 | 180 |
| 109 scoped_refptr<base::RefCountedMemory> image_bytes = | 181 scoped_refptr<base::RefCountedMemory> image_bytes = |
| 110 enhanced_bookmarks::BytesForImage(image); | 182 enhanced_bookmarks::BytesForImage(record.image); |
| 111 | 183 |
| 112 // Insert an empty image in case encoding fails. | 184 // Insert an empty image in case encoding fails. |
| 113 if (!image_bytes.get()) | 185 if (!image_bytes.get()) |
| 114 image_bytes = enhanced_bookmarks::BytesForImage(gfx::Image()); | 186 image_bytes = enhanced_bookmarks::BytesForImage(gfx::Image()); |
| 115 | 187 |
| 116 CHECK(image_bytes.get()); | 188 CHECK(image_bytes.get()); |
| 117 | 189 |
| 118 statement.BindBlob(2, image_bytes->front(), (int)image_bytes->size()); | 190 statement.BindBlob(2, image_bytes->front(), (int)image_bytes->size()); |
| 119 | 191 |
| 120 statement.BindInt(3, image.Size().width()); | 192 statement.BindInt(3, record.image.Size().width()); |
| 121 statement.BindInt(4, image.Size().height()); | 193 statement.BindInt(4, record.image.Size().height()); |
| 194 statement.BindInt(5, record.dominant_color); |
| 122 statement.Run(); | 195 statement.Run(); |
| 123 } | 196 } |
| 124 | 197 |
| 125 void PersistentImageStore::Erase(const GURL& page_url) { | 198 void PersistentImageStore::Erase(const GURL& page_url) { |
| 126 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 199 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| 127 if (OpenDatabase() != sql::INIT_OK) | 200 if (OpenDatabase() != sql::INIT_OK) |
| 128 return; | 201 return; |
| 129 | 202 |
| 130 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, | 203 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 131 "DELETE FROM images_by_url WHERE page_url = ?")); | 204 "DELETE FROM images_by_url WHERE page_url = ?")); |
| 132 statement.BindString(0, page_url.possibly_invalid_spec()); | 205 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 133 statement.Run(); | 206 statement.Run(); |
| 134 } | 207 } |
| 135 | 208 |
| 136 std::pair<gfx::Image, GURL> PersistentImageStore::Get(const GURL& page_url) { | 209 enhanced_bookmarks::ImageRecord PersistentImageStore::Get( |
| 210 const GURL& page_url) { |
| 137 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 211 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| 212 enhanced_bookmarks::ImageRecord image_record; |
| 138 if (OpenDatabase() != sql::INIT_OK) | 213 if (OpenDatabase() != sql::INIT_OK) |
| 139 return std::make_pair(gfx::Image(), GURL()); | 214 return image_record; |
| 140 | 215 |
| 141 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, | 216 bool stored_image_record_needs_update = false; |
| 142 "SELECT image_data, image_url FROM images_by_url WHERE page_url = ?")); | |
| 143 | 217 |
| 144 statement.BindString(0, page_url.possibly_invalid_spec()); | 218 // Scope the SELECT statement. |
| 219 { |
| 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 = ?")); |
| 145 | 224 |
| 146 while (statement.Step()) { | 225 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 226 |
| 227 if (!statement.Step()) |
| 228 return image_record; |
| 229 |
| 230 // Image. |
| 147 if (statement.ColumnByteLength(0) > 0) { | 231 if (statement.ColumnByteLength(0) > 0) { |
| 148 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); | 232 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); |
| 149 statement.ColumnBlobAsVector(0, &data->data()); | 233 statement.ColumnBlobAsVector(0, &data->data()); |
| 234 image_record.image = enhanced_bookmarks::ImageForBytes(data); |
| 235 } |
| 150 | 236 |
| 151 return std::make_pair(enhanced_bookmarks::ImageForBytes(data), | 237 // URL. |
| 152 GURL(statement.ColumnString(1))); | 238 image_record.url = GURL(statement.ColumnString(1)); |
| 239 |
| 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; |
| 153 } | 250 } |
| 251 |
| 252 // Make sure there is only one record for page_url. |
| 253 DCHECK(!statement.Step()); |
| 154 } | 254 } |
| 155 | 255 |
| 156 return std::make_pair(gfx::Image(), GURL()); | 256 if (stored_image_record_needs_update) { |
| 257 Erase(page_url); |
| 258 Insert(page_url, image_record); |
| 259 } |
| 260 |
| 261 return image_record; |
| 157 } | 262 } |
| 158 | 263 |
| 159 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) { | 264 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) { |
| 160 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 265 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| 161 if (OpenDatabase() != sql::INIT_OK) | 266 if (OpenDatabase() != sql::INIT_OK) |
| 162 return gfx::Size(); | 267 return gfx::Size(); |
| 163 | 268 |
| 164 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, | 269 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 165 "SELECT width, height FROM images_by_url WHERE page_url = ?")); | 270 "SELECT width, height FROM images_by_url WHERE page_url = ?")); |
| 166 | 271 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 210 sql::InitStatus PersistentImageStore::OpenDatabase() { | 315 sql::InitStatus PersistentImageStore::OpenDatabase() { |
| 211 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 316 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| 212 | 317 |
| 213 if (db_.is_open()) | 318 if (db_.is_open()) |
| 214 return sql::INIT_OK; | 319 return sql::INIT_OK; |
| 215 | 320 |
| 216 const size_t kAttempts = 2; | 321 const size_t kAttempts = 2; |
| 217 | 322 |
| 218 sql::InitStatus status = sql::INIT_FAILURE; | 323 sql::InitStatus status = sql::INIT_FAILURE; |
| 219 for (size_t i = 0; i < kAttempts; ++i) { | 324 for (size_t i = 0; i < kAttempts; ++i) { |
| 220 status = OpenDatabaseImpl(db_, path_); | 325 status = OpenDatabaseImpl(db_, meta_table_, path_); |
| 221 if (status == sql::INIT_OK) | 326 if (status == sql::INIT_OK) |
| 222 return status; | 327 return status; |
| 223 | 328 |
| 224 // Can't open, raze(). | 329 // Can't open, raze(). |
| 225 if (db_.is_open()) | 330 if (db_.is_open()) |
| 226 db_.Raze(); | 331 db_.Raze(); |
| 227 db_.Close(); | 332 db_.Close(); |
| 228 } | 333 } |
| 229 | 334 |
| 230 DCHECK(false) << "Can't open image DB"; | 335 DCHECK(false) << "Can't open image DB"; |
| 231 return sql::INIT_FAILURE; | 336 return sql::INIT_FAILURE; |
| 232 } | 337 } |
| OLD | NEW |