blob: baca61c67a6778f2e0ec54a988a0b09fdcfe2f9a [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/model/delete_page_task.h"
#include <iterator>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/offline_pages/core/client_policy_controller.h"
#include "components/offline_pages/core/model/get_pages_task.h"
#include "components/offline_pages/core/model/offline_page_model_utils.h"
#include "components/offline_pages/core/offline_clock.h"
#include "components/offline_pages/core/offline_page_client_policy.h"
#include "components/offline_pages/core/offline_page_metadata_store.h"
#include "components/offline_pages/core/offline_page_model.h"
#include "components/offline_pages/core/offline_page_types.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace offline_pages {
struct DeletePageTask::DeletePageTaskResult {
DeletePageTaskResult() = default;
DeletePageTaskResult(DeletePageResult result,
const std::vector<OfflinePageItem>& deleted_pages)
: result(result), deleted_pages(deleted_pages) {}
DeletePageTaskResult(DeletePageTaskResult&& other) = default;
DeletePageTaskResult(const DeletePageTaskResult& other) = delete;
~DeletePageTaskResult() = default;
DeletePageResult result;
std::vector<OfflinePageItem> deleted_pages;
};
using DeletePageTaskResult = DeletePageTask::DeletePageTaskResult;
namespace {
void ReportDeletePageHistograms(
const std::vector<OfflinePageItem>& deleted_pages) {
const int max_minutes = base::TimeDelta::FromDays(365).InMinutes();
base::Time delete_time = OfflineTimeNow();
for (const auto& item : deleted_pages) {
base::UmaHistogramCustomCounts(
model_utils::AddHistogramSuffix(item.client_id.name_space,
"OfflinePages.PageLifetime"),
(delete_time - item.creation_time).InMinutes(), 1, max_minutes, 100);
base::UmaHistogramCustomCounts(
model_utils::AddHistogramSuffix(item.client_id.name_space,
"OfflinePages.AccessCount"),
item.access_count, 1, 1000000, 50);
}
}
bool DeleteArchiveSync(const base::FilePath& file_path) {
// Delete the file only, |false| for recursive.
return base::DeleteFile(file_path, false);
}
// Deletes pages. This will return a DeletePageTaskResult which contains the
// deleted pages (which are successfully deleted from the disk and the store)
// and a DeletePageResult. For each page to be deleted, the deletion will delete
// the archive file first, then database entry, in order to avoid the potential
// issue of leaving archive files behind (and they may be imported later).
// Since the database entry will only be deleted while the associated archive
// file is deleted successfully, there will be no such issue.
DeletePageTaskResult DeletePagesSync(
sql::Database* db,
std::vector<OfflinePageItem> pages_to_delete) {
std::vector<OfflinePageItem> deleted_pages;
// If there's no page to delete, return an empty list with SUCCESS.
if (pages_to_delete.size() == 0)
return {DeletePageResult::SUCCESS, deleted_pages};
ReportDeletePageHistograms(pages_to_delete);
bool any_archive_deleted = false;
for (auto& item : pages_to_delete) {
if (DeleteArchiveSync(item.file_path)) {
any_archive_deleted = true;
if (DeletePageTask::DeletePageFromDbSync(item.offline_id, db))
deleted_pages.push_back(std::move(item));
}
}
// If there're no files deleted, return DEVICE_FAILURE.
if (!any_archive_deleted)
return {DeletePageResult::DEVICE_FAILURE, std::move(deleted_pages)};
return {DeletePageResult::SUCCESS, std::move(deleted_pages)};
}
DeletePageTaskResult DeletePagesWithCriteria(
const ClientPolicyController* policy_controller,
const PageCriteria& criteria,
sql::Database* db) {
// If you create a transaction but dont Commit() it is automatically
// rolled back by its destructor when it falls out of scope.
sql::Transaction transaction(db);
if (!transaction.Begin())
return {DeletePageResult::STORE_FAILURE, {}};
GetPagesTask::ReadResult read_result =
GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db);
if (!read_result.success)
return {DeletePageResult::STORE_FAILURE, {}};
DeletePageTaskResult result =
DeletePagesSync(db, std::move(read_result.pages));
if (!transaction.Commit())
return {DeletePageResult::STORE_FAILURE, {}};
return result;
}
// Deletes all but |limit| pages that match |criteria|, in the order specified
// by |criteria|.
DeletePageTaskResult DeletePagesForPageLimit(
const ClientPolicyController* policy_controller,
const PageCriteria& criteria,
size_t limit,
sql::Database* db) {
// If the namespace can have unlimited pages per url, just return success. In
// practice, we shouldn't hit this condition.
if (limit == kUnlimitedPages) {
DLOG(ERROR) << "DeletePagesForPageLimit called with unlimited limit";
return {DeletePageResult::SUCCESS, {}};
}
// If you create a transaction but dont Commit() it is automatically
// rolled back by its destructor when it falls out of scope.
sql::Transaction transaction(db);
if (!transaction.Begin())
return {DeletePageResult::STORE_FAILURE, {}};
GetPagesTask::ReadResult read_result =
GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db);
if (!read_result.success)
return {DeletePageResult::STORE_FAILURE, {}};
if (read_result.pages.size() > limit)
read_result.pages.resize(read_result.pages.size() - limit);
else
read_result.pages.clear();
DeletePageTaskResult result =
DeletePagesSync(db, std::move(read_result.pages));
if (!transaction.Commit())
return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
return result;
}
} // namespace
// static
std::unique_ptr<DeletePageTask> DeletePageTask::CreateTaskWithCriteria(
OfflinePageMetadataStore* store,
const ClientPolicyController& policy_controller,
const PageCriteria& criteria,
DeletePageTask::DeletePageTaskCallback callback) {
return std::unique_ptr<DeletePageTask>(new DeletePageTask(
store,
base::BindOnce(&DeletePagesWithCriteria, &policy_controller, criteria),
std::move(callback)));
}
// static
std::unique_ptr<DeletePageTask>
DeletePageTask::CreateTaskMatchingUrlPredicateForCachedPages(
OfflinePageMetadataStore* store,
const ClientPolicyController& policy_controller,
DeletePageTask::DeletePageTaskCallback callback,
const UrlPredicate& predicate) {
PageCriteria criteria;
criteria.client_namespaces =
policy_controller.GetNamespacesRemovedOnCacheReset();
criteria.additional_criteria = base::BindRepeating(
[](const UrlPredicate& predicate, const OfflinePageItem& item) {
return predicate.Run(item.url);
},
predicate);
return CreateTaskWithCriteria(store, policy_controller, criteria,
std::move(callback));
}
// static
std::unique_ptr<DeletePageTask> DeletePageTask::CreateTaskDeletingForPageLimit(
OfflinePageMetadataStore* store,
const ClientPolicyController& policy_controller,
DeletePageTask::DeletePageTaskCallback callback,
const OfflinePageItem& page) {
std::string name_space = page.client_id.name_space;
size_t limit = policy_controller.GetPolicy(name_space).pages_allowed_per_url;
PageCriteria criteria;
criteria.url = page.url;
criteria.client_namespaces = std::vector<std::string>{name_space};
// Sorting is important here. DeletePagesForPageLimit will delete the results
// in order, leaving only the last |limit| pages.
criteria.result_order = PageCriteria::kAscendingAccessTime;
return base::WrapUnique(
new DeletePageTask(store,
base::BindOnce(&DeletePagesForPageLimit,
&policy_controller, criteria, limit),
std::move(callback)));
}
DeletePageTask::DeletePageTask(OfflinePageMetadataStore* store,
DeleteFunction func,
DeletePageTaskCallback callback)
: store_(store),
func_(std::move(func)),
callback_(std::move(callback)),
weak_ptr_factory_(this) {
DCHECK(store_);
DCHECK(!callback_.is_null());
}
DeletePageTask::~DeletePageTask() {}
void DeletePageTask::Run() {
store_->Execute(std::move(func_),
base::BindOnce(&DeletePageTask::OnDeletePageDone,
weak_ptr_factory_.GetWeakPtr()),
DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {}));
}
void DeletePageTask::OnDeletePageDone(DeletePageTaskResult result) {
std::move(callback_).Run(result.result, std::move(result.deleted_pages));
TaskComplete();
}
// static
bool DeletePageTask::DeletePageFromDbSync(int64_t offline_id,
sql::Database* db) {
static const char kSql[] = "DELETE FROM offlinepages_v1 WHERE offline_id=?";
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindInt64(0, offline_id);
return statement.Run();
}
bool DeletePageTask::DeletePagesFromDbSync(
const std::vector<int64_t>& offline_ids,
sql::Database* db) {
for (const auto& offline_id : offline_ids) {
if (!DeletePageTask::DeletePageFromDbSync(offline_id, db))
return false;
}
return true;
}
} // namespace offline_pages