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 |