• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
InitTables(sql::Connection & db)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 
InitIndices(sql::Connection & db)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 
OpenDatabaseImpl(sql::Connection & db,const base::FilePath & db_path)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 
PersistentImageStore(const base::FilePath & path)73 PersistentImageStore::PersistentImageStore(const base::FilePath& path)
74     : ImageStore(),
75       path_(path.Append(
76           base::FilePath::FromUTF8Unsafe("BookmarkImageAndUrlStore.db"))) {
77 }
78 
HasKey(const GURL & page_url)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 
Insert(const GURL & page_url,const GURL & image_url,const gfx::Image & image)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 
Erase(const GURL & page_url)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 
Get(const GURL & page_url)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 
GetSize(const GURL & page_url)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 
GetAllPageUrls(std::set<GURL> * urls)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 
ClearAll()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 
GetStoreSizeInBytes()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 
~PersistentImageStore()206 PersistentImageStore::~PersistentImageStore() {
207   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
208 }
209 
OpenDatabase()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