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. 4 5 #include "components/enhanced_bookmarks/persistent_image_store.h" 6 7 #include "base/files/file.h" 8 #include "components/enhanced_bookmarks/image_store_util.h" 9 #include "sql/statement.h" 10 #include "sql/transaction.h" 11 #include "ui/gfx/geometry/size.h" 12 #include "url/gurl.h" 13 14 namespace { 15 16 bool InitTables(sql::Connection& db) { 17 const char kTableSql[] = 18 "CREATE TABLE IF NOT EXISTS images_by_url (" 19 "page_url LONGVARCHAR NOT NULL," 20 "image_url LONGVARCHAR NOT NULL," 21 "image_data BLOB," 22 "width INTEGER," 23 "height INTEGER" 24 ")"; 25 if (!db.Execute(kTableSql)) 26 return false; 27 return true; 28 } 29 30 bool InitIndices(sql::Connection& db) { 31 const char kIndexSql[] = 32 "CREATE INDEX IF NOT EXISTS images_by_url_idx ON images_by_url(page_url)"; 33 if (!db.Execute(kIndexSql)) 34 return false; 35 return true; 36 } 37 38 sql::InitStatus OpenDatabaseImpl(sql::Connection& db, 39 const base::FilePath& db_path) { 40 DCHECK(!db.is_open()); 41 42 db.set_histogram_tag("BookmarkImages"); 43 // TODO(noyau): Set page and cache sizes? 44 // TODO(noyau): Set error callback? 45 46 // 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. 48 db.set_exclusive_locking(); 49 50 if (!db.Open(db_path)) 51 return sql::INIT_FAILURE; 52 53 // Scope initialization in a transaction so we can't be partially initialized. 54 sql::Transaction transaction(&db); 55 if (!transaction.Begin()) 56 return sql::INIT_FAILURE; 57 58 // Create the tables. 59 if (!InitTables(db) || 60 !InitIndices(db)) { 61 return sql::INIT_FAILURE; 62 } 63 64 // Initialization is complete. 65 if (!transaction.Commit()) 66 return sql::INIT_FAILURE; 67 68 return sql::INIT_OK; 69 } 70 71 } // namespace 72 73 PersistentImageStore::PersistentImageStore(const base::FilePath& path) 74 : ImageStore(), 75 path_(path.Append( 76 base::FilePath::FromUTF8Unsafe("BookmarkImageAndUrlStore.db"))) { 77 } 78 79 bool PersistentImageStore::HasKey(const GURL& page_url) { 80 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 81 if (OpenDatabase() != sql::INIT_OK) 82 return false; 83 84 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 85 "SELECT COUNT(*) FROM images_by_url WHERE page_url = ?")); 86 statement.BindString(0, page_url.possibly_invalid_spec()); 87 88 int count = statement.Step() ? statement.ColumnInt(0) : 0; 89 90 return !!count; 91 } 92 93 void PersistentImageStore::Insert(const GURL& page_url, 94 const GURL& image_url, 95 const gfx::Image& image) { 96 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 97 if (OpenDatabase() != sql::INIT_OK) 98 return; 99 100 Erase(page_url); // Remove previous image for this url, if any. 101 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 102 "INSERT INTO images_by_url " 103 "(page_url, image_url, image_data, width, height)" 104 "VALUES (?, ?, ?, ?, ?)")); 105 106 statement.BindString(0, page_url.possibly_invalid_spec()); 107 statement.BindString(1, image_url.possibly_invalid_spec()); 108 109 scoped_refptr<base::RefCountedMemory> image_bytes = 110 enhanced_bookmarks::BytesForImage(image); 111 112 // Insert an empty image in case encoding fails. 113 if (!image_bytes.get()) 114 image_bytes = enhanced_bookmarks::BytesForImage(gfx::Image()); 115 116 CHECK(image_bytes.get()); 117 118 statement.BindBlob(2, image_bytes->front(), (int)image_bytes->size()); 119 120 statement.BindInt(3, image.Size().width()); 121 statement.BindInt(4, image.Size().height()); 122 statement.Run(); 123 } 124 125 void PersistentImageStore::Erase(const GURL& page_url) { 126 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 127 if (OpenDatabase() != sql::INIT_OK) 128 return; 129 130 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 131 "DELETE FROM images_by_url WHERE page_url = ?")); 132 statement.BindString(0, page_url.possibly_invalid_spec()); 133 statement.Run(); 134 } 135 136 std::pair<gfx::Image, GURL> PersistentImageStore::Get(const GURL& page_url) { 137 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 138 if (OpenDatabase() != sql::INIT_OK) 139 return std::make_pair(gfx::Image(), GURL()); 140 141 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 142 "SELECT image_data, image_url FROM images_by_url WHERE page_url = ?")); 143 144 statement.BindString(0, page_url.possibly_invalid_spec()); 145 146 while (statement.Step()) { 147 if (statement.ColumnByteLength(0) > 0) { 148 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 149 statement.ColumnBlobAsVector(0, &data->data()); 150 151 return std::make_pair(enhanced_bookmarks::ImageForBytes(data), 152 GURL(statement.ColumnString(1))); 153 } 154 } 155 156 return std::make_pair(gfx::Image(), GURL()); 157 } 158 159 gfx::Size PersistentImageStore::GetSize(const GURL& page_url) { 160 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 161 if (OpenDatabase() != sql::INIT_OK) 162 return gfx::Size(); 163 164 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 165 "SELECT width, height FROM images_by_url WHERE page_url = ?")); 166 167 statement.BindString(0, page_url.possibly_invalid_spec()); 168 169 while (statement.Step()) { 170 if (statement.ColumnByteLength(0) > 0) { 171 int width = statement.ColumnInt(0); 172 int height = statement.ColumnInt(1); 173 return gfx::Size(width, height); 174 } 175 } 176 return gfx::Size(); 177 } 178 179 void PersistentImageStore::GetAllPageUrls(std::set<GURL>* urls) { 180 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 181 DCHECK(urls->empty()); 182 if (OpenDatabase() != sql::INIT_OK) 183 return; 184 185 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 186 "SELECT page_url FROM images_by_url")); 187 while (statement.Step()) 188 urls->insert(GURL(statement.ColumnString(0))); 189 } 190 191 void PersistentImageStore::ClearAll() { 192 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 193 if (OpenDatabase() != sql::INIT_OK) 194 return; 195 196 sql::Statement statement(db_.GetCachedStatement( 197 SQL_FROM_HERE, "DELETE FROM images_by_url")); 198 statement.Run(); 199 } 200 201 int64 PersistentImageStore::GetStoreSizeInBytes() { 202 base::File file(path_, base::File::FLAG_OPEN | base::File::FLAG_READ); 203 return file.IsValid() ? file.GetLength() : -1; 204 } 205 206 PersistentImageStore::~PersistentImageStore() { 207 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 208 } 209 210 sql::InitStatus PersistentImageStore::OpenDatabase() { 211 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 212 213 if (db_.is_open()) 214 return sql::INIT_OK; 215 216 const size_t kAttempts = 2; 217 218 sql::InitStatus status = sql::INIT_FAILURE; 219 for (size_t i = 0; i < kAttempts; ++i) { 220 status = OpenDatabaseImpl(db_, path_); 221 if (status == sql::INIT_OK) 222 return status; 223 224 // Can't open, raze(). 225 if (db_.is_open()) 226 db_.Raze(); 227 db_.Close(); 228 } 229 230 DCHECK(false) << "Can't open image DB"; 231 return sql::INIT_FAILURE; 232 } 233