blob: ef51030eae8c4117f302d13bb487b8f71ec295be [file] [log] [blame]
// Copyright 2017 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/prefetch/tasks/add_unique_urls_task.h"
#include <map>
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/offline_pages/core/offline_clock.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
#include "components/offline_pages/core/prefetch/prefetch_types.h"
#include "components/offline_pages/core/prefetch/store/prefetch_store.h"
#include "components/offline_pages/core/prefetch/store/prefetch_store_utils.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "url/gurl.h"
namespace offline_pages {
using Result = AddUniqueUrlsTask::Result;
namespace {
std::map<std::string, std::pair<int64_t, PrefetchItemState>>
FindExistingPrefetchItemsInNamespaceSync(sql::Database* db,
const std::string& name_space) {
static const char kSql[] =
"SELECT offline_id, state, requested_url FROM prefetch_items"
" WHERE client_namespace = ?";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindString(0, name_space);
std::map<std::string, std::pair<int64_t, PrefetchItemState>> result;
while (statement.Step()) {
result.emplace(
statement.ColumnString(2),
std::make_pair(statement.ColumnInt64(0),
static_cast<PrefetchItemState>(statement.ColumnInt(1))));
}
return result;
}
bool CreatePrefetchItemSync(sql::Database* db,
const std::string& name_space,
const PrefetchURL& prefetch_url,
int64_t now_db_time) {
static const char kSql[] =
"INSERT INTO prefetch_items"
" (offline_id, requested_url, client_namespace, client_id, creation_time,"
" freshness_time, title)"
" VALUES"
" (?, ?, ?, ?, ?, ?, ?)";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindInt64(0, store_utils::GenerateOfflineId());
statement.BindString(1, prefetch_url.url.spec());
statement.BindString(2, name_space);
statement.BindString(3, prefetch_url.id);
statement.BindInt64(4, now_db_time);
statement.BindInt64(5, now_db_time);
statement.BindString16(6, prefetch_url.title);
return statement.Run();
}
// Adds new prefetch item entries to the store using the URLs and client IDs
// from |candidate_prefetch_urls| and the client's |name_space|. Also cleans up
// entries in the Zombie state from the client's |name_space| except for the
// ones whose URL is contained in |candidate_prefetch_urls|.
// Returns the number of added prefecth items.
Result AddUrlsAndCleanupZombiesSync(
const std::string& name_space,
const std::vector<PrefetchURL>& candidate_prefetch_urls,
sql::Database* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return Result::STORE_ERROR;
std::map<std::string, std::pair<int64_t, PrefetchItemState>> existing_items =
FindExistingPrefetchItemsInNamespaceSync(db, name_space);
int added_row_count = 0;
base::Time now = OfflineClock()->Now();
// Insert rows in reverse order to ensure that the beginning of the list has
// the newest timestamp. This will cause it to be prefetched first.
for (auto candidate_iter = candidate_prefetch_urls.rbegin();
candidate_iter != candidate_prefetch_urls.rend(); ++candidate_iter) {
PrefetchURL prefetch_url = *candidate_iter;
auto iter = existing_items.find(prefetch_url.url.spec());
if (iter == existing_items.end()) {
if (!CreatePrefetchItemSync(db, name_space, prefetch_url,
store_utils::ToDatabaseTime(now)))
return Result::STORE_ERROR; // Transaction rollback.
added_row_count++;
// We artificially add a microsecond to ensure that the timestamp is
// different (and guarantee a particular order when sorting by timestamp).
now += base::TimeDelta::FromMicroseconds(1);
} else {
// Removing from the list of existing items if it was requested again, to
// prevent it from being removed in the next step.
existing_items.erase(iter);
}
}
// Purge remaining zombie IDs.
for (const auto& existing_item : existing_items) {
if (existing_item.second.second != PrefetchItemState::ZOMBIE)
continue;
if (!PrefetchStoreUtils::DeletePrefetchItemByOfflineIdSync(
db, existing_item.second.first)) {
return Result::STORE_ERROR; // Transaction rollback.
}
}
if (!transaction.Commit())
return Result::STORE_ERROR; // Transaction rollback.
UMA_HISTOGRAM_COUNTS_100("OfflinePages.Prefetching.UniqueUrlsAddedCount",
added_row_count);
return added_row_count > 0 ? Result::URLS_ADDED : Result::NOTHING_ADDED;
}
} // namespace
AddUniqueUrlsTask::AddUniqueUrlsTask(
PrefetchDispatcher* prefetch_dispatcher,
PrefetchStore* prefetch_store,
const std::string& name_space,
const std::vector<PrefetchURL>& prefetch_urls)
: prefetch_dispatcher_(prefetch_dispatcher),
prefetch_store_(prefetch_store),
name_space_(name_space),
prefetch_urls_(prefetch_urls),
weak_ptr_factory_(this) {
DCHECK(prefetch_dispatcher_);
DCHECK(prefetch_store_);
}
AddUniqueUrlsTask::~AddUniqueUrlsTask() {}
void AddUniqueUrlsTask::Run() {
prefetch_store_->Execute(base::BindOnce(&AddUrlsAndCleanupZombiesSync,
name_space_, prefetch_urls_),
base::BindOnce(&AddUniqueUrlsTask::OnUrlsAdded,
weak_ptr_factory_.GetWeakPtr()),
Result::STORE_ERROR);
}
void AddUniqueUrlsTask::OnUrlsAdded(Result result) {
if (result == Result::URLS_ADDED) {
prefetch_dispatcher_->EnsureTaskScheduled();
prefetch_dispatcher_->SchedulePipelineProcessing();
}
TaskComplete();
}
} // namespace offline_pages