- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / history / top_sites_database.cc
1 // Copyright (c) 2012 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 "base/file_util.h"
6 #include "base/memory/ref_counted.h"
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/history/history_types.h"
10 #include "chrome/browser/history/top_sites.h"
11 #include "chrome/browser/history/top_sites_database.h"
12 #include "chrome/common/thumbnail_score.h"
13 #include "sql/connection.h"
14 #include "sql/transaction.h"
15
16 // Description of database table:
17 //
18 // thumbnails
19 //   url              URL of the sites for which we have a thumbnail.
20 //   url_rank         Index of the URL in that thumbnail, 0-based. The thumbnail
21 //                    with the highest rank will be the next one evicted. Forced
22 //                    thumbnails have a rank of -1.
23 //   title            The title to display under that thumbnail.
24 //   redirects        A space separated list of URLs that are known to redirect
25 //                    to this url.
26 //   boring_score     How "boring" that thumbnail is. See ThumbnailScore.
27 //   good_clipping    True if the thumbnail was clipped from the bottom, keeping
28 //                    the entire width of the window. See ThumbnailScore.
29 //   at_top           True if the thumbnail was captured at the top of the
30 //                    website.
31 //   last_updated     The time at which this thumbnail was last updated.
32 //   load_completed   True if the thumbnail was captured after the page load was
33 //                    completed.
34 //   last_forced      If this is a forced thumbnail, records the last time it
35 //                    was forced. If it's not a forced thumbnail, 0.
36
37 namespace history {
38
39 // TODO(beaudoin): Fill revision/date details of Version 3 after landing.
40 // Version 3:  by beaudoin@chromium.org
41 // Version 2: eb0b24e6/r87284 by satorux@chromium.org on 2011-05-31
42 // Version 1: 809cc4d8/r64072 by sky@chromium.org on 2010-10-27
43
44 // From the version 1 to 2, one column was added. Old versions of Chrome
45 // should be able to read version 2 files just fine. Same thing for version 2
46 // to 3.
47 // NOTE(shess): When changing the version, add a new golden file for
48 // the new version and a test to verify that Init() works with it.
49 static const int kVersionNumber = 3;
50
51 TopSitesDatabase::TopSitesDatabase() {
52 }
53
54 TopSitesDatabase::~TopSitesDatabase() {
55 }
56
57 bool TopSitesDatabase::Init(const base::FilePath& db_name) {
58   bool file_existed = base::PathExists(db_name);
59
60   db_.reset(CreateDB(db_name));
61   if (!db_)
62     return false;
63
64   bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get());
65   if (!does_meta_exist && file_existed) {
66     // If the meta file doesn't exist, this version is old. We could remove all
67     // the entries as they are no longer applicable, but it's safest to just
68     // remove the file and start over.
69     db_.reset(NULL);
70     if (!sql::Connection::Delete(db_name)) {
71       LOG(ERROR) << "unable to delete old TopSites file";
72       return false;
73     }
74     db_.reset(CreateDB(db_name));
75     if (!db_)
76       return false;
77   }
78
79   // Scope initialization in a transaction so we can't be partially
80   // initialized.
81   sql::Transaction transaction(db_.get());
82   transaction.Begin();
83
84   if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber))
85     return false;
86
87   if (!InitThumbnailTable())
88     return false;
89
90   if (meta_table_.GetVersionNumber() == 1) {
91     if (!UpgradeToVersion2()) {
92       LOG(WARNING) << "Unable to upgrade top sites database to version 2.";
93       return false;
94     }
95   }
96
97   if (meta_table_.GetVersionNumber() == 2) {
98     if (!UpgradeToVersion3()) {
99       LOG(WARNING) << "Unable to upgrade top sites database to version 3.";
100       return false;
101     }
102   }
103
104   // Version check.
105   if (meta_table_.GetVersionNumber() != kVersionNumber)
106     return false;
107
108   // Initialization is complete.
109   if (!transaction.Commit())
110     return false;
111
112   return true;
113 }
114
115 bool TopSitesDatabase::InitThumbnailTable() {
116   if (!db_->DoesTableExist("thumbnails")) {
117     if (!db_->Execute("CREATE TABLE thumbnails ("
118                       "url LONGVARCHAR PRIMARY KEY,"
119                       "url_rank INTEGER,"
120                       "title LONGVARCHAR,"
121                       "thumbnail BLOB,"
122                       "redirects LONGVARCHAR,"
123                       "boring_score DOUBLE DEFAULT 1.0,"
124                       "good_clipping INTEGER DEFAULT 0,"
125                       "at_top INTEGER DEFAULT 0,"
126                       "last_updated INTEGER DEFAULT 0,"
127                       "load_completed INTEGER DEFAULT 0,"
128                       "last_forced INTEGER DEFAULT 0)")) {
129       LOG(WARNING) << db_->GetErrorMessage();
130       return false;
131     }
132   }
133   return true;
134 }
135
136 bool TopSitesDatabase::UpgradeToVersion2() {
137   // Add 'load_completed' column.
138   if (!db_->Execute(
139           "ALTER TABLE thumbnails ADD load_completed INTEGER DEFAULT 0")) {
140     NOTREACHED();
141     return false;
142   }
143   meta_table_.SetVersionNumber(2);
144   return true;
145 }
146
147 bool TopSitesDatabase::UpgradeToVersion3() {
148   // Add 'last_forced' column.
149   if (!db_->Execute(
150           "ALTER TABLE thumbnails ADD last_forced INTEGER DEFAULT 0")) {
151     NOTREACHED();
152     return false;
153   }
154   meta_table_.SetVersionNumber(3);
155   return true;
156 }
157
158 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
159                                          URLToImagesMap* thumbnails) {
160   sql::Statement statement(db_->GetCachedStatement(
161       SQL_FROM_HERE,
162       "SELECT url, url_rank, title, thumbnail, redirects, "
163       "boring_score, good_clipping, at_top, last_updated, load_completed, "
164       "last_forced FROM thumbnails ORDER BY url_rank, last_forced"));
165
166   if (!statement.is_valid()) {
167     LOG(WARNING) << db_->GetErrorMessage();
168     return;
169   }
170
171   urls->clear();
172   thumbnails->clear();
173
174   while (statement.Step()) {
175     // Results are sorted by url_rank. For forced thumbnails with url_rank = -1,
176     // thumbnails are sorted by last_forced.
177     MostVisitedURL url;
178     GURL gurl(statement.ColumnString(0));
179     url.url = gurl;
180     url.title = statement.ColumnString16(2);
181     url.last_forced_time =
182         base::Time::FromInternalValue(statement.ColumnInt64(10));
183     std::string redirects = statement.ColumnString(4);
184     SetRedirects(redirects, &url);
185     urls->push_back(url);
186
187     std::vector<unsigned char> data;
188     statement.ColumnBlobAsVector(3, &data);
189     Images thumbnail;
190     if (!data.empty())
191       thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data);
192     thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5);
193     thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6);
194     thumbnail.thumbnail_score.at_top = statement.ColumnBool(7);
195     thumbnail.thumbnail_score.time_at_snapshot =
196         base::Time::FromInternalValue(statement.ColumnInt64(8));
197     thumbnail.thumbnail_score.load_completed = statement.ColumnBool(9);
198     (*thumbnails)[gurl] = thumbnail;
199   }
200 }
201
202 // static
203 std::string TopSitesDatabase::GetRedirects(const MostVisitedURL& url) {
204   std::vector<std::string> redirects;
205   for (size_t i = 0; i < url.redirects.size(); i++)
206     redirects.push_back(url.redirects[i].spec());
207   return JoinString(redirects, ' ');
208 }
209
210 // static
211 void TopSitesDatabase::SetRedirects(const std::string& redirects,
212                                     MostVisitedURL* url) {
213   std::vector<std::string> redirects_vector;
214   base::SplitStringAlongWhitespace(redirects, &redirects_vector);
215   for (size_t i = 0; i < redirects_vector.size(); ++i)
216     url->redirects.push_back(GURL(redirects_vector[i]));
217 }
218
219 void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url,
220                                         int new_rank,
221                                         const Images& thumbnail) {
222   sql::Transaction transaction(db_.get());
223   transaction.Begin();
224
225   int rank = GetURLRank(url);
226   if (rank == kRankOfNonExistingURL) {
227     AddPageThumbnail(url, new_rank, thumbnail);
228   } else {
229     UpdatePageRankNoTransaction(url, new_rank);
230     UpdatePageThumbnail(url, thumbnail);
231   }
232
233   transaction.Commit();
234 }
235
236 bool TopSitesDatabase::UpdatePageThumbnail(
237     const MostVisitedURL& url, const Images& thumbnail) {
238   sql::Statement statement(db_->GetCachedStatement(
239       SQL_FROM_HERE,
240       "UPDATE thumbnails SET "
241       "title = ?, thumbnail = ?, redirects = ?, "
242       "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, "
243       "load_completed = ?, last_forced = ?"
244       "WHERE url = ? "));
245   statement.BindString16(0, url.title);
246   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
247     statement.BindBlob(1, thumbnail.thumbnail->front(),
248                        static_cast<int>(thumbnail.thumbnail->size()));
249   }
250   statement.BindString(2, GetRedirects(url));
251   const ThumbnailScore& score = thumbnail.thumbnail_score;
252   statement.BindDouble(3, score.boring_score);
253   statement.BindBool(4, score.good_clipping);
254   statement.BindBool(5, score.at_top);
255   statement.BindInt64(6, score.time_at_snapshot.ToInternalValue());
256   statement.BindBool(7, score.load_completed);
257   statement.BindInt64(8, url.last_forced_time.ToInternalValue());
258   statement.BindString(9, url.url.spec());
259
260   return statement.Run();
261 }
262
263 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
264                                         int new_rank,
265                                         const Images& thumbnail) {
266   sql::Statement statement(db_->GetCachedStatement(
267       SQL_FROM_HERE,
268       "INSERT OR REPLACE INTO thumbnails "
269       "(url, url_rank, title, thumbnail, redirects, "
270       "boring_score, good_clipping, at_top, last_updated, load_completed, "
271       "last_forced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
272   statement.BindString(0, url.url.spec());
273   statement.BindInt(1, kRankOfForcedURL);  // Fist make it a forced thumbnail.
274   statement.BindString16(2, url.title);
275   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
276     statement.BindBlob(3, thumbnail.thumbnail->front(),
277                        static_cast<int>(thumbnail.thumbnail->size()));
278   }
279   statement.BindString(4, GetRedirects(url));
280   const ThumbnailScore& score = thumbnail.thumbnail_score;
281   statement.BindDouble(5, score.boring_score);
282   statement.BindBool(6, score.good_clipping);
283   statement.BindBool(7, score.at_top);
284   statement.BindInt64(8, score.time_at_snapshot.ToInternalValue());
285   statement.BindBool(9, score.load_completed);
286   int64 last_forced = url.last_forced_time.ToInternalValue();
287   DCHECK((last_forced == 0) == (new_rank != kRankOfForcedURL))
288       << "Thumbnail without a forced time stamp has a forced rank, or the "
289       << "opposite.";
290   statement.BindInt64(10, last_forced);
291   if (!statement.Run())
292     return;
293
294   // Update rank if this is not a forced thumbnail.
295   if (new_rank != kRankOfForcedURL)
296     UpdatePageRankNoTransaction(url, new_rank);
297 }
298
299 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
300                                       int new_rank) {
301   DCHECK((url.last_forced_time.ToInternalValue() == 0) ==
302          (new_rank != kRankOfForcedURL))
303       << "Thumbnail without a forced time stamp has a forced rank, or the "
304       << "opposite.";
305   sql::Transaction transaction(db_.get());
306   transaction.Begin();
307   UpdatePageRankNoTransaction(url, new_rank);
308   transaction.Commit();
309 }
310
311 // Caller should have a transaction open.
312 void TopSitesDatabase::UpdatePageRankNoTransaction(
313     const MostVisitedURL& url, int new_rank) {
314   DCHECK_GT(db_->transaction_nesting(), 0);
315
316   int prev_rank = GetURLRank(url);
317   if (prev_rank == kRankOfNonExistingURL) {
318     LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec();
319     return;
320   }
321
322   // Shift the ranks.
323   if (prev_rank > new_rank) {
324     if (new_rank == kRankOfForcedURL) {
325       // From non-forced to forced, shift down.
326       // Example: 2 -> -1
327       // -1, -1, -1, 0, 1, [2 -> -1], [3 -> 2], [4 -> 3]
328       sql::Statement shift_statement(db_->GetCachedStatement(
329           SQL_FROM_HERE,
330           "UPDATE thumbnails "
331           "SET url_rank = url_rank - 1 "
332           "WHERE url_rank > ?"));
333       shift_statement.BindInt(0, prev_rank);
334       shift_statement.Run();
335     } else {
336       // From non-forced to non-forced, shift up.
337       // Example: 3 -> 1
338       // -1, -1, -1, 0, [1 -> 2], [2 -> 3], [3 -> 1], 4
339       sql::Statement shift_statement(db_->GetCachedStatement(
340           SQL_FROM_HERE,
341           "UPDATE thumbnails "
342           "SET url_rank = url_rank + 1 "
343           "WHERE url_rank >= ? AND url_rank < ?"));
344       shift_statement.BindInt(0, new_rank);
345       shift_statement.BindInt(1, prev_rank);
346       shift_statement.Run();
347     }
348   } else if (prev_rank < new_rank) {
349     if (prev_rank == kRankOfForcedURL) {
350       // From non-forced to forced, shift up.
351       // Example: -1 -> 2
352       // -1, [-1 -> 2], -1, 0, 1, [2 -> 3], [3 -> 4], [4 -> 5]
353       sql::Statement shift_statement(db_->GetCachedStatement(
354           SQL_FROM_HERE,
355           "UPDATE thumbnails "
356           "SET url_rank = url_rank + 1 "
357           "WHERE url_rank >= ?"));
358       shift_statement.BindInt(0, new_rank);
359       shift_statement.Run();
360     } else {
361       // From non-forced to non-forced, shift down.
362       // Example: 1 -> 3.
363       // -1, -1, -1, 0, [1 -> 3], [2 -> 1], [3 -> 2], 4
364       sql::Statement shift_statement(db_->GetCachedStatement(
365           SQL_FROM_HERE,
366           "UPDATE thumbnails "
367           "SET url_rank = url_rank - 1 "
368           "WHERE url_rank > ? AND url_rank <= ?"));
369       shift_statement.BindInt(0, prev_rank);
370       shift_statement.BindInt(1, new_rank);
371       shift_statement.Run();
372     }
373   }
374
375   // Set the url's rank and last_forced, since the latter changes when a URL
376   // goes from forced to non-forced and vice-versa.
377   sql::Statement set_statement(db_->GetCachedStatement(
378       SQL_FROM_HERE,
379       "UPDATE thumbnails "
380       "SET url_rank = ?, last_forced = ? "
381       "WHERE url == ?"));
382   set_statement.BindInt(0, new_rank);
383   set_statement.BindInt64(1, url.last_forced_time.ToInternalValue());
384   set_statement.BindString(2, url.url.spec());
385   set_statement.Run();
386 }
387
388 bool TopSitesDatabase::GetPageThumbnail(const GURL& url,
389                                         Images* thumbnail) {
390   sql::Statement statement(db_->GetCachedStatement(
391       SQL_FROM_HERE,
392       "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated "
393       "FROM thumbnails WHERE url=?"));
394   statement.BindString(0, url.spec());
395   if (!statement.Step())
396     return false;
397
398   std::vector<unsigned char> data;
399   statement.ColumnBlobAsVector(0, &data);
400   thumbnail->thumbnail = base::RefCountedBytes::TakeVector(&data);
401   thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1);
402   thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2);
403   thumbnail->thumbnail_score.at_top = statement.ColumnBool(3);
404   thumbnail->thumbnail_score.time_at_snapshot =
405       base::Time::FromInternalValue(statement.ColumnInt64(4));
406   return true;
407 }
408
409 int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) {
410   sql::Statement select_statement(db_->GetCachedStatement(
411       SQL_FROM_HERE,
412       "SELECT url_rank "
413       "FROM thumbnails WHERE url=?"));
414   select_statement.BindString(0, url.url.spec());
415   if (select_statement.Step())
416     return select_statement.ColumnInt(0);
417
418   return kRankOfNonExistingURL;
419 }
420
421 // Remove the record for this URL. Returns true iff removed successfully.
422 bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) {
423   int old_rank = GetURLRank(url);
424   if (old_rank == kRankOfNonExistingURL)
425     return false;
426
427   sql::Transaction transaction(db_.get());
428   transaction.Begin();
429   if (old_rank != kRankOfForcedURL) {
430     // Decrement all following ranks.
431     sql::Statement shift_statement(db_->GetCachedStatement(
432         SQL_FROM_HERE,
433         "UPDATE thumbnails "
434         "SET url_rank = url_rank - 1 "
435         "WHERE url_rank > ?"));
436     shift_statement.BindInt(0, old_rank);
437
438     if (!shift_statement.Run())
439       return false;
440   }
441
442   sql::Statement delete_statement(
443       db_->GetCachedStatement(SQL_FROM_HERE,
444                               "DELETE FROM thumbnails WHERE url = ?"));
445   delete_statement.BindString(0, url.url.spec());
446
447   if (!delete_statement.Run())
448     return false;
449
450   return transaction.Commit();
451 }
452
453 sql::Connection* TopSitesDatabase::CreateDB(const base::FilePath& db_name) {
454   scoped_ptr<sql::Connection> db(new sql::Connection());
455   // Settings copied from ThumbnailDatabase.
456   db->set_histogram_tag("TopSites");
457   db->set_page_size(4096);
458   db->set_cache_size(32);
459
460   if (!db->Open(db_name)) {
461     LOG(ERROR) << db->GetErrorMessage();
462     return NULL;
463   }
464
465   return db.release();
466 }
467
468 }  // namespace history