OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 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. |
| 4 #include "chrome/browser/bookmarks/enhanced/image_store.h" |
| 5 |
| 6 #include "base/logging.h" |
| 7 #include "base/memory/ref_counted_memory.h" |
| 8 #include "sql/statement.h" |
| 9 #include "sql/transaction.h" |
| 10 |
| 11 #if defined(OS_IOS) |
| 12 #import "chrome/browser/bookmarks/enhanced/image_store_ios.h" |
| 13 #endif // defined(OS_IOS) |
| 14 |
| 15 namespace { |
| 16 // The two methods below archive and unarchive an image to and from a bag of |
| 17 // bytes. There is no API on gfx::Image capable of doing it while preserving the |
| 18 // scale of the image. On iOS instead of the 1x representation (that may lose |
| 19 // resolution) the archive contains a iOS specific archive of the UIImage |
| 20 // representation. |
| 21 scoped_refptr<base::RefCountedMemory> bytesForImage(gfx::Image image) { |
| 22 #if defined(OS_IOS) |
| 23 // Forces the image to have a UIImage representation. |
| 24 image.ToUIImage(); |
| 25 return image_store_ios::bytesForImage(image); |
| 26 #else |
| 27 return image.As1xPNGBytes(); |
| 28 #endif // defined(OS_IOS) |
| 29 } // namespace |
| 30 |
| 31 gfx::Image imageForBytes(scoped_refptr<base::RefCountedMemory> bytes) { |
| 32 #if defined(OS_IOS) |
| 33 return image_store_ios::imageForBytes(bytes); |
| 34 #else |
| 35 return gfx::Image::CreateFrom1xPNGBytes(bytes->front(), bytes->size()); |
| 36 #endif // defined(OS_IOS) |
| 37 } |
| 38 } // namespace |
| 39 |
| 40 void ImageStore::ChangeImageURL(const GURL& from, const GURL& to) { |
| 41 if (!HasKey(from)) |
| 42 return; |
| 43 GURL image_url; |
| 44 gfx::Image image(Get(from, image_url)); |
| 45 |
| 46 Erase(from); |
| 47 Insert(to, image_url, image); |
| 48 } |
| 49 |
| 50 // Removes all images. |
| 51 void ImageStore::ClearAll() { |
| 52 std::vector<GURL> all_keys = AllKeys(); |
| 53 for (std::vector<GURL>::const_iterator it = all_keys.begin(); |
| 54 it != all_keys.end(); |
| 55 ++it) { |
| 56 Erase(*it); |
| 57 } |
| 58 } |
| 59 |
| 60 MemoryImageStore::MemoryImageStore() : ImageStore() {} |
| 61 MemoryImageStore::~MemoryImageStore() {} |
| 62 |
| 63 bool MemoryImageStore::HasKey(const GURL& page_url) { |
| 64 return store_.find(page_url) != store_.end(); |
| 65 } |
| 66 |
| 67 void MemoryImageStore::Insert(const GURL& page_url, |
| 68 const GURL& image_url, |
| 69 const gfx::Image& image) { |
| 70 Erase(page_url); |
| 71 store_.insert(std::make_pair( |
| 72 page_url, |
| 73 std::make_pair(image, |
| 74 image_url))); |
| 75 } |
| 76 |
| 77 void MemoryImageStore::Erase(const GURL& page_url) { |
| 78 store_.erase(page_url); |
| 79 } |
| 80 |
| 81 gfx::Image MemoryImageStore::Get(const GURL& page_url, GURL& image_url) { |
| 82 ImageMap::const_iterator pair_enumerator = store_.find(page_url); |
| 83 if (pair_enumerator == store_.end()) |
| 84 return gfx::Image(); |
| 85 |
| 86 image_url = store_[page_url].second; |
| 87 return store_[page_url].first; |
| 88 } |
| 89 |
| 90 gfx::Size MemoryImageStore::GetSize(const GURL& page_url) { |
| 91 ImageMap::const_iterator pair_enumerator = store_.find(page_url); |
| 92 if (pair_enumerator == store_.end()) |
| 93 return gfx::Size(); |
| 94 |
| 95 return store_[page_url].first.Size(); |
| 96 } |
| 97 |
| 98 std::vector<GURL> MemoryImageStore::AllKeys() { |
| 99 std::vector<GURL> result; |
| 100 |
| 101 for (ImageMap::const_iterator it = store_.begin(); it != store_.end(); ++it) { |
| 102 result.push_back(it->first); |
| 103 } |
| 104 return result; |
| 105 } |
| 106 |
| 107 namespace { |
| 108 bool InitTables(sql::Connection& db) { |
| 109 const char kTableSql[] = |
| 110 "CREATE TABLE IF NOT EXISTS images_by_url (" |
| 111 " page_url LONGVARCHAR NOT NULL," |
| 112 " image_url LONGVARCHAR NOT NULL," |
| 113 " image_data BLOB," |
| 114 " width INTEGER," |
| 115 " height INTEGER" |
| 116 ")"; |
| 117 if (!db.Execute(kTableSql)) |
| 118 return false; |
| 119 return true; |
| 120 } |
| 121 |
| 122 bool InitIndices(sql::Connection& db) { |
| 123 const char kIndexSql[] = |
| 124 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)"; |
| 125 if (!db.Execute(kIndexSql)) |
| 126 return false; |
| 127 return true; |
| 128 } |
| 129 |
| 130 sql::InitStatus OpenDatabaseImpl(sql::Connection& db, |
| 131 const base::FilePath& db_path) { |
| 132 DCHECK(!db.is_open()); |
| 133 |
| 134 db.set_histogram_tag("BookmarkImages"); |
| 135 // TODO(noyau): Set page and cache sizes? |
| 136 // TODO(noyau): Set error callback? |
| 137 |
| 138 // Run the database in exclusive mode. Nobody else should be accessing the |
| 139 // database while we're running, and this will give somewhat improved perf. |
| 140 db.set_exclusive_locking(); |
| 141 |
| 142 if (!db.Open(db_path)) |
| 143 return sql::INIT_FAILURE; |
| 144 |
| 145 // Scope initialization in a transaction so we can't be partially initialized. |
| 146 sql::Transaction transaction(&db); |
| 147 if (!transaction.Begin()) |
| 148 return sql::INIT_FAILURE; |
| 149 |
| 150 // Create the tables. |
| 151 if (!InitTables(db) || |
| 152 !InitIndices(db)) { |
| 153 return sql::INIT_FAILURE; |
| 154 } |
| 155 |
| 156 // Initialization is complete. |
| 157 if (!transaction.Commit()) |
| 158 return sql::INIT_FAILURE; |
| 159 |
| 160 return sql::INIT_OK; |
| 161 } |
| 162 |
| 163 } // namespace |
| 164 |
| 165 PersistentImageStore::PersistentImageStore(const base::FilePath& path) |
| 166 : ImageStore(), path_(path.Append("BookmarkImageAndUrlStore.db")) {} |
| 167 |
| 168 PersistentImageStore::~PersistentImageStore() {} |
| 169 |
| 170 bool PersistentImageStore::HasKey(const GURL& page_url) { |
| 171 OpenDatabase(); |
| 172 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 173 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?")); |
| 174 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 175 |
| 176 int count = statement.Step() ? statement.ColumnInt(0) : 0; |
| 177 |
| 178 return !!count; |
| 179 } |
| 180 |
| 181 void PersistentImageStore::Insert(const GURL& page_url, |
| 182 const GURL& image_url, |
| 183 const gfx::Image& image) { |
| 184 Erase(page_url); // Remove previous image for this url, if any. |
| 185 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 186 "INSERT INTO images_by_url " |
| 187 " (page_url, image_url, image_data, width, height)" |
| 188 " VALUES (?, ?, ?, ?, ?)")); |
| 189 |
| 190 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 191 statement.BindString(1, image_url.possibly_invalid_spec()); |
| 192 |
| 193 scoped_refptr<base::RefCountedMemory> data = bytesForImage(image); |
| 194 statement.BindBlob(2, data->front(), data->size()); |
| 195 |
| 196 statement.BindInt64(3, image.Size().width()); |
| 197 statement.BindInt64(4, image.Size().height()); |
| 198 statement.Run(); |
| 199 } |
| 200 |
| 201 void PersistentImageStore::Erase(const GURL& page_url) { |
| 202 OpenDatabase(); |
| 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(); |
| 207 } |
| 208 |
| 209 gfx::Image PersistentImageStore::Get(const GURL& page_url, GURL& image_url) { |
| 210 OpenDatabase(); |
| 211 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 212 "SELECT image_data, image_url FROM images_by_url WHERE page_url = ?")); |
| 213 |
| 214 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 215 |
| 216 while (statement.Step()) { |
| 217 if (statement.ColumnByteLength(0) > 0) { |
| 218 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); |
| 219 statement.ColumnBlobAsVector(0, &data->data()); |
| 220 image_url = GURL(statement.ColumnString(1)); |
| 221 return imageForBytes(data); |
| 222 } |
| 223 } |
| 224 return gfx::Image(); |
| 225 } |
| 226 |
| 227 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) { |
| 228 OpenDatabase(); |
| 229 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 230 "SELECT width, height FROM images_by_url WHERE page_url = ?")); |
| 231 |
| 232 statement.BindString(0, page_url.possibly_invalid_spec()); |
| 233 |
| 234 while (statement.Step()) { |
| 235 if (statement.ColumnByteLength(0) > 0) { |
| 236 int64 width = statement.ColumnInt64(0); |
| 237 int64 height = statement.ColumnInt64(1); |
| 238 return gfx::Size(width, height); |
| 239 } |
| 240 } |
| 241 return gfx::Size(); |
| 242 } |
| 243 |
| 244 std::vector<GURL> PersistentImageStore::AllKeys() { |
| 245 OpenDatabase(); |
| 246 std::vector<GURL> result; |
| 247 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| 248 "SELECT page_url FROM images_by_url")); |
| 249 while (statement.Step()) |
| 250 result.push_back(GURL(statement.ColumnString(0))); |
| 251 |
| 252 return result; |
| 253 } |
| 254 |
| 255 sql::InitStatus PersistentImageStore::OpenDatabase() { |
| 256 if (db_.is_open()) |
| 257 return sql::INIT_OK; |
| 258 |
| 259 const size_t kAttempts = 2; |
| 260 |
| 261 sql::InitStatus status = sql::INIT_FAILURE; |
| 262 for (size_t i = 0; i < kAttempts; ++i) { |
| 263 status = OpenDatabaseImpl(db_, path_); |
| 264 if (status == sql::INIT_OK) |
| 265 return status; |
| 266 |
| 267 // Can't open, raze(). |
| 268 if (db_.is_open()) |
| 269 db_.Raze(); |
| 270 db_.Close(); |
| 271 } |
| 272 CHECK(false) << "Can't open image DB"; |
| 273 return sql::INIT_FAILURE; |
| 274 } |
OLD | NEW |