Home | History | Annotate | Download | only in enhanced_bookmarks
      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