Commit c68101a5 authored by Dan Harrington's avatar Dan Harrington Committed by Commit Bot

Add page_thumbnails table and OfflinePageThumbnail

These pieces will be used to store/represent page thumbnails.

Bug: 794828
Change-Id: I242ec8376a36b2b41590bf89fb166a924aa13ca7
Reviewed-on: https://chromium-review.googlesource.com/984952
Commit-Queue: Dan H <harringtond@chromium.org>
Reviewed-by: default avatarCarlos Knippschild <carlosk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547077}
parent ff57655e
......@@ -57,6 +57,8 @@ static_library("core") {
"offline_page_model.h",
"offline_page_model_event_logger.cc",
"offline_page_model_event_logger.h",
"offline_page_thumbnail.cc",
"offline_page_thumbnail.h",
"offline_page_types.h",
"offline_pages_ukm_reporter.cc",
"offline_pages_ukm_reporter.h",
......@@ -162,6 +164,7 @@ source_set("unit_tests") {
"offline_page_feature_unittest.cc",
"offline_page_metadata_store_unittest.cc",
"offline_page_model_event_logger_unittest.cc",
"offline_page_thumbnail_unittest.cc",
"offline_pages_ukm_reporter_unittest.cc",
"snapshot_controller_unittest.cc",
"task_queue_unittest.cc",
......
......@@ -23,19 +23,17 @@
#include "sql/transaction.h"
namespace offline_pages {
const int OfflinePageMetadataStoreSQL::kFirstPostLegacyVersion;
const int OfflinePageMetadataStoreSQL::kCurrentVersion;
const int OfflinePageMetadataStoreSQL::kCompatibleVersion;
namespace {
// This is a macro instead of a const so that
// it can be used inline in other SQL statements below.
#define OFFLINE_PAGES_TABLE_NAME "offlinepages_v1"
// This is the first version saved in the meta table, which was introduced in
// the store in M65. It is set once a legacy upgrade is run successfully for the
// last time in |UpgradeFromLegacyVersion|.
static const int kFirstPostLegacyVersion = 1;
static const int kCurrentVersion = 2;
static const int kCompatibleVersion = kFirstPostLegacyVersion;
void ReportStoreEvent(OfflinePagesStoreEvent event) {
UMA_HISTOGRAM_ENUMERATION("OfflinePages.SQLStorage.StoreEvent", event,
OfflinePagesStoreEvent::STORE_EVENT_COUNT);
......@@ -175,6 +173,16 @@ bool UpgradeFrom61(sql::Connection* db) {
return UpgradeWithQuery(db, kSql);
}
bool CreatePageThumbnailsTable(sql::Connection* db) {
const char kSql[] =
"CREATE TABLE IF NOT EXISTS page_thumbnails"
" (offline_id INTEGER PRIMARY KEY NOT NULL,"
" expiration INTEGER NOT NULL,"
" thumbnail BLOB NOT NULL"
")";
return db->Execute(kSql);
}
bool CreateLatestSchema(sql::Connection* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
......@@ -183,14 +191,19 @@ bool CreateLatestSchema(sql::Connection* db) {
// First time database initialization.
if (!CreateOfflinePagesTable(db))
return false;
if (!CreatePageThumbnailsTable(db))
return false;
sql::MetaTable meta_table;
if (!meta_table.Init(db, kCurrentVersion, kCompatibleVersion))
if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kCurrentVersion,
OfflinePageMetadataStoreSQL::kCompatibleVersion))
return false;
return transaction.Commit();
}
// Upgrades the database from before the database version was stored in the
// MetaTable. This function should never need to be modified.
bool UpgradeFromLegacyVersion(sql::Connection* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
......@@ -222,7 +235,8 @@ bool UpgradeFromLegacyVersion(sql::Connection* db) {
}
sql::MetaTable meta_table;
if (!meta_table.Init(db, kFirstPostLegacyVersion, kCompatibleVersion))
if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kFirstPostLegacyVersion,
OfflinePageMetadataStoreSQL::kCompatibleVersion))
return false;
return transaction.Commit();
......@@ -249,6 +263,19 @@ bool UpgradeFromVersion1ToVersion2(sql::Connection* db,
return transaction.Commit();
}
bool UpgradeFromVersion2ToVersion3(sql::Connection* db,
sql::MetaTable* meta_table) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
if (!CreatePageThumbnailsTable(db)) {
return false;
}
meta_table->SetVersionNumber(3);
return transaction.Commit();
}
bool CreateSchema(sql::Connection* db) {
if (!sql::MetaTable::DoesTableExist(db)) {
// If this looks like a completely empty DB, simply start from scratch.
......@@ -261,16 +288,26 @@ bool CreateSchema(sql::Connection* db) {
}
sql::MetaTable meta_table;
if (!meta_table.Init(db, kCurrentVersion, kCompatibleVersion))
if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kCurrentVersion,
OfflinePageMetadataStoreSQL::kCompatibleVersion))
return false;
if (meta_table.GetVersionNumber() == 1) {
if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
return false;
for (;;) {
switch (meta_table.GetVersionNumber()) {
case 1:
if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
return false;
break;
case 2:
if (!UpgradeFromVersion2ToVersion3(db, &meta_table))
return false;
break;
case OfflinePageMetadataStoreSQL::kCurrentVersion:
return true;
default:
return false;
}
}
// This would be a great place to add indices when we need them.
return true;
}
bool PrepareDirectory(const base::FilePath& path) {
......
......@@ -90,6 +90,13 @@ class OfflinePageMetadataStoreSQL {
template <typename T>
using ResultCallback = base::OnceCallback<void(T)>;
// This is the first version saved in the meta table, which was introduced in
// the store in M65. It is set once a legacy upgrade is run successfully for
// the last time in |UpgradeFromLegacyVersion|.
static const int kFirstPostLegacyVersion = 1;
static const int kCurrentVersion = 3;
static const int kCompatibleVersion = kFirstPostLegacyVersion;
// Defines inactivity time of DB after which it is going to be closed.
// TODO(fgorski): Derive appropriate value in a scientific way.
static constexpr base::TimeDelta kClosingDelay =
......
......@@ -22,6 +22,7 @@
#include "components/offline_pages/core/offline_page_item.h"
#include "components/offline_pages/core/offline_page_metadata_store_sql.h"
#include "components/offline_pages/core/offline_page_model.h"
#include "components/offline_pages/core/offline_page_thumbnail.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "sql/connection.h"
#include "sql/meta_table.h"
......@@ -47,6 +48,7 @@ int64_t kOfflineId = 12345LL;
const char kTestRequestOrigin[] = "request.origin";
int64_t kTestSystemDownloadId = 42LL;
const char kTestDigest[] = "test-digest";
const base::Time kThumbnailExpiration = store_utils::FromDatabaseTime(42);
// Build a store with outdated schema to simulate the upgrading process.
void BuildTestStoreWithSchemaFromM52(const base::FilePath& file) {
......@@ -462,6 +464,22 @@ void BuildTestStoreWithSchemaVersion1(const base::FilePath& file) {
InjectItemInM62Store(&connection, generator.CreateItem());
}
void BuildTestStoreWithSchemaVersion2(const base::FilePath& file) {
BuildTestStoreWithSchemaVersion1(file);
sql::Connection db;
ASSERT_TRUE(db.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
sql::MetaTable meta_table;
ASSERT_TRUE(meta_table.Init(&db, OfflinePageMetadataStoreSQL::kCurrentVersion,
OfflinePageMetadataStoreSQL::kCompatibleVersion));
const char kSql[] =
"CREATE TABLE page_thumbnails"
" (offline_id INTEGER PRIMARY KEY NOT NULL,"
" expiration INTEGER NOT NULL,"
" thumbnail BLOB NOT NULL"
")";
ASSERT_TRUE(db.Execute(kSql));
}
// Create an offline page item from a SQL result. Expects complete rows with
// all columns present.
OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
......@@ -578,6 +596,18 @@ class OfflinePageMetadataStoreTest : public testing::Test {
EXPECT_EQ(offline_page, pages[0]);
}
void CheckThatPageThumbnailCanBeSaved(OfflinePageMetadataStoreSQL* store) {
OfflinePageThumbnail thumbnail;
thumbnail.offline_id = kOfflineId;
thumbnail.expiration = kThumbnailExpiration;
thumbnail.thumbnail = "content";
AddThumbnail(store, thumbnail);
std::vector<OfflinePageThumbnail> thumbnails = GetThumbnails(store);
EXPECT_EQ(1UL, thumbnails.size());
EXPECT_EQ(thumbnail, thumbnails[0]);
}
void VerifyMetaVersions() {
sql::Connection connection;
ASSERT_TRUE(connection.Open(temp_directory_.GetPath().Append(
......@@ -587,14 +617,42 @@ class OfflinePageMetadataStoreTest : public testing::Test {
sql::MetaTable meta_table;
EXPECT_TRUE(meta_table.Init(&connection, 1, 1));
EXPECT_EQ(2, meta_table.GetVersionNumber());
EXPECT_EQ(1, meta_table.GetCompatibleVersionNumber());
EXPECT_EQ(OfflinePageMetadataStoreSQL::kCurrentVersion,
meta_table.GetVersionNumber());
EXPECT_EQ(OfflinePageMetadataStoreSQL::kCompatibleVersion,
meta_table.GetCompatibleVersionNumber());
}
void LoadAndCheckStore() {
auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
base::ThreadTaskRunnerHandle::Get(), TempPath());
OfflinePageItem item = CheckThatStoreHasOneItem(store.get());
CheckThatPageThumbnailCanBeSaved((OfflinePageMetadataStoreSQL*)store.get());
CheckThatOfflinePageCanBeSaved(std::move(store));
VerifyMetaVersions();
}
void LoadAndCheckStoreFromMetaVersion1AndUp() {
// At meta version 1, more items were added to the database for testing,
// which necessitates different checks.
auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
base::ThreadTaskRunnerHandle::Get(), TempPath());
std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
EXPECT_EQ(5U, pages.size());
// TODO(fgorski): Use persistent namespaces from the client policy
// controller once an appropriate method is available.
std::set<std::string> upgradeable_namespaces{
kAsyncNamespace, kDownloadNamespace, kBrowserActionsNamespace,
kNTPSuggestionsNamespace};
for (const OfflinePageItem& page : pages) {
if (upgradeable_namespaces.count(page.client_id.name_space) > 0)
EXPECT_EQ(5, page.upgrade_attempt);
else
EXPECT_EQ(0, page.upgrade_attempt);
}
CheckThatPageThumbnailCanBeSaved((OfflinePageMetadataStoreSQL*)store.get());
CheckThatOfflinePageCanBeSaved(std::move(store));
VerifyMetaVersions();
}
......@@ -675,6 +733,47 @@ class OfflinePageMetadataStoreTest : public testing::Test {
return ExecuteSync<ItemActionStatus>(store, result_callback);
}
std::vector<OfflinePageThumbnail> GetThumbnails(
OfflinePageMetadataStoreSQL* store) {
std::vector<OfflinePageThumbnail> thumbnails;
auto run_callback = base::BindLambdaForTesting([&](sql::Connection* db) {
const char kSql[] = "SELECT * FROM page_thumbnails";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
while (statement.Step()) {
OfflinePageThumbnail thumb;
thumb.offline_id = statement.ColumnInt64(0);
thumb.expiration =
store_utils::FromDatabaseTime(statement.ColumnInt64(1));
statement.ColumnBlobAsString(2, &thumb.thumbnail);
thumbnails.push_back(std::move(thumb));
}
EXPECT_TRUE(statement.Succeeded());
return thumbnails;
});
return ExecuteSync<std::vector<OfflinePageThumbnail>>(store, run_callback);
}
void AddThumbnail(OfflinePageMetadataStoreSQL* store,
const OfflinePageThumbnail& thumbnail) {
std::vector<OfflinePageThumbnail> thumbnails;
auto run_callback = base::BindLambdaForTesting([&](sql::Connection* db) {
const char kSql[] =
"INSERT INTO page_thumbnails"
" (offline_id, expiration, thumbnail) VALUES (?, ?, ?)";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindInt64(0, thumbnail.offline_id);
statement.BindInt64(1, store_utils::ToDatabaseTime(thumbnail.expiration));
statement.BindString(2, thumbnail.thumbnail);
EXPECT_TRUE(statement.Run());
return thumbnails;
});
ExecuteSync<std::vector<OfflinePageThumbnail>>(store, run_callback);
}
protected:
base::ScopedTempDir temp_directory_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::ThreadTaskRunnerHandle task_runner_handle_;
......@@ -755,26 +854,12 @@ TEST_F(OfflinePageMetadataStoreTest, LoadVersion62Store) {
TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion1) {
BuildTestStoreWithSchemaVersion1(TempPath());
auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
base::ThreadTaskRunnerHandle::Get(), TempPath());
LoadAndCheckStoreFromMetaVersion1AndUp();
}
std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
EXPECT_EQ(5U, pages.size());
// TODO(fgorski): Use persistent namespaces from the client policy controller
// once an appropriate method is available.
std::set<std::string> upgradeable_namespaces{
kAsyncNamespace, kDownloadNamespace, kBrowserActionsNamespace,
kNTPSuggestionsNamespace};
for (const OfflinePageItem& page : pages) {
if (upgradeable_namespaces.count(page.client_id.name_space) > 0)
EXPECT_EQ(5, page.upgrade_attempt);
else
EXPECT_EQ(0, page.upgrade_attempt);
}
CheckThatOfflinePageCanBeSaved(std::move(store));
VerifyMetaVersions();
TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion2) {
BuildTestStoreWithSchemaVersion2(TempPath());
LoadAndCheckStoreFromMetaVersion1AndUp();
}
// Adds metadata of an offline page into a store and then opens the store
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/offline_pages/core/offline_page_thumbnail.h"
#include <iostream>
#include "base/base64.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/offline_pages/core/offline_store_utils.h"
namespace offline_pages {
OfflinePageThumbnail::OfflinePageThumbnail() = default;
OfflinePageThumbnail::OfflinePageThumbnail(int64_t id,
base::Time in_expiration,
const std::string& in_thumbnail)
: offline_id(id), expiration(in_expiration), thumbnail(in_thumbnail) {}
OfflinePageThumbnail::OfflinePageThumbnail(const OfflinePageThumbnail& other) =
default;
OfflinePageThumbnail::OfflinePageThumbnail(OfflinePageThumbnail&& other) =
default;
OfflinePageThumbnail::~OfflinePageThumbnail() {}
bool OfflinePageThumbnail::operator==(const OfflinePageThumbnail& other) const {
return offline_id == other.offline_id && expiration == other.expiration &&
thumbnail == other.thumbnail;
}
bool OfflinePageThumbnail::operator<(const OfflinePageThumbnail& other) const {
return offline_id < other.offline_id;
}
std::string OfflinePageThumbnail::ToString() const {
std::string thumb_data_base64;
base::Base64Encode(thumbnail, &thumb_data_base64);
std::string s("OfflinePageThumbnail(");
s.append(base::Int64ToString(offline_id)).append(", ");
s.append(base::Int64ToString(store_utils::ToDatabaseTime(expiration)))
.append(", ");
s.append(thumb_data_base64).append(")");
return s;
}
std::ostream& operator<<(std::ostream& out, const OfflinePageThumbnail& thumb) {
return out << thumb.ToString();
}
} // namespace offline_pages
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
#define COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
#include <stdint.h>
#include <iosfwd>
#include <string>
#include "base/time/time.h"
namespace offline_pages {
// Thumbnail for an offline page. This maps to a row in the page_thumbnails
// table.
struct OfflinePageThumbnail {
public:
OfflinePageThumbnail();
OfflinePageThumbnail(int64_t offline_id,
base::Time expiration,
const std::string& thumbnail);
OfflinePageThumbnail(const OfflinePageThumbnail& other);
OfflinePageThumbnail(OfflinePageThumbnail&& other);
~OfflinePageThumbnail();
OfflinePageThumbnail& operator=(const OfflinePageThumbnail& other) = default;
bool operator==(const OfflinePageThumbnail& other) const;
bool operator<(const OfflinePageThumbnail& other) const;
std::string ToString() const;
// The primary key/ID for the page in offline pages internal database.
int64_t offline_id = 0;
// The time at which the thumbnail can be removed from the table, but only
// if the offline_id does not match an offline_id in the offline pages table.
base::Time expiration;
// The thumbnail raw image data.
std::string thumbnail;
};
std::ostream& operator<<(std::ostream& out, const OfflinePageThumbnail& thumb);
} // namespace offline_pages
#endif // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/offline_pages/core/offline_page_thumbnail.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
namespace {
const base::Time kTestTime = store_utils::FromDatabaseTime(42);
const char kThumbnailData[] = "abc123";
TEST(OfflinePageThumbnailTest, Construct) {
OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
EXPECT_EQ(1, thumbnail.offline_id);
EXPECT_EQ(kTestTime, thumbnail.expiration);
EXPECT_EQ(kThumbnailData, thumbnail.thumbnail);
}
TEST(OfflinePageThumbnailTest, Equal) {
OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
auto copy = thumbnail;
EXPECT_EQ(1, copy.offline_id);
EXPECT_EQ(kTestTime, copy.expiration);
EXPECT_EQ(kThumbnailData, copy.thumbnail);
EXPECT_EQ(copy, thumbnail);
}
TEST(OfflinePageThumbnailTest, Compare) {
OfflinePageThumbnail thumbnail_a(1, kTestTime, kThumbnailData);
OfflinePageThumbnail thumbnail_b(2, kTestTime, kThumbnailData);
EXPECT_TRUE(thumbnail_a < thumbnail_b);
EXPECT_FALSE(thumbnail_b < thumbnail_a);
EXPECT_FALSE(thumbnail_a < thumbnail_a);
}
TEST(OfflinePageThumbnailTest, ToString) {
OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
EXPECT_EQ("OfflinePageThumbnail(1, 42, YWJjMTIz)", thumbnail.ToString());
}
} // namespace
} // namespace offline_pages
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment