blob: 1c0ea825ab4b7930e752463876f340ecedab8576 [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/clear_storage_task.h"
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/offline_pages/core/client_policy_controller.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_store_utils.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace offline_pages {
using LifetimeType = LifetimePolicy::LifetimeType;
using ClearStorageResult = ClearStorageTask::ClearStorageResult;
namespace {
#define PAGE_INFO_PROJECTION \
" offline_id, client_namespace, client_id, file_size, creation_time," \
" last_access_time, file_path "
// This struct needs to be in sync with |PAGE_INFO_PROJECTION|.
struct PageInfo {
PageInfo();
PageInfo(const PageInfo& other);
int64_t offline_id;
ClientId client_id;
int64_t file_size;
base::Time creation_time;
base::Time last_access_time;
base::FilePath file_path;
};
PageInfo::PageInfo() = default;
PageInfo::PageInfo(const PageInfo& other) = default;
// Maximum % of total available storage that will be occupied by offline pages
// before a storage clearup.
const double kOfflinePageStorageLimit = 0.3;
// The target % of storage usage we try to reach below when expiring pages.
const double kOfflinePageStorageClearThreshold = 0.1;
// Make sure this is in sync with |PAGE_INFO_PROJECTION| above.
PageInfo MakePageInfo(sql::Statement* statement) {
PageInfo page_info;
page_info.offline_id = statement->ColumnInt64(0);
page_info.client_id =
ClientId(statement->ColumnString(1), statement->ColumnString(2));
page_info.file_size = statement->ColumnInt64(3);
page_info.creation_time =
store_utils::FromDatabaseTime(statement->ColumnInt64(4));
page_info.last_access_time =
store_utils::FromDatabaseTime(statement->ColumnInt64(5));
page_info.file_path =
store_utils::FromDatabaseFilePath(statement->ColumnString(6));
return page_info;
}
std::unique_ptr<std::vector<PageInfo>> GetAllTemporaryPageInfos(
const std::map<std::string, LifetimePolicy>& temp_namespace_policy_map,
sql::Database* db) {
auto result = std::make_unique<std::vector<PageInfo>>();
static const char kSql[] = "SELECT " PAGE_INFO_PROJECTION
" FROM offlinepages_v1"
" WHERE client_namespace = ?";
for (const auto& temp_namespace_policy : temp_namespace_policy_map) {
std::string name_space = temp_namespace_policy.first;
sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindString(0, name_space);
while (statement.Step())
result->emplace_back(MakePageInfo(&statement));
if (!statement.Succeeded()) {
result->clear();
break;
}
}
return result;
}
std::unique_ptr<std::vector<PageInfo>> GetPageInfosToClear(
const std::map<std::string, LifetimePolicy>& temp_namespace_policy_map,
const base::Time& start_time,
const ArchiveManager::StorageStats& stats,
sql::Database* db) {
std::map<std::string, int> namespace_page_count;
auto page_infos_to_delete = std::make_unique<std::vector<PageInfo>>();
std::vector<PageInfo> pages_remaining;
int64_t remaining_size = 0;
// If the cached pages exceed the storage limit, we need to clear more than
// just expired pages to make the storage usage below the threshold.
bool quota_based_clearing =
stats.temporary_archives_size >=
(stats.temporary_archives_size + stats.internal_free_disk_space) *
kOfflinePageStorageLimit;
int64_t max_allowed_size =
(stats.temporary_archives_size + stats.internal_free_disk_space) *
kOfflinePageStorageClearThreshold;
// Initialize the counting map with 0s.
for (const auto& namespace_policy : temp_namespace_policy_map)
namespace_page_count[namespace_policy.first] = 0;
// Gets all temporary pages and sort by last accessed time.
auto page_infos = GetAllTemporaryPageInfos(temp_namespace_policy_map, db);
std::sort(page_infos->begin(), page_infos->end(),
[](const PageInfo& a, const PageInfo& b) -> bool {
return a.last_access_time > b.last_access_time;
});
for (const auto& page_info : *page_infos) {
std::string name_space = page_info.client_id.name_space;
LifetimePolicy policy = temp_namespace_policy_map.at(name_space);
// If the page is expired, put it in the list to delete later.
if (start_time - page_info.last_access_time >= policy.expiration_period) {
page_infos_to_delete->push_back(page_info);
continue;
}
// If the namespace of the page already has more pages than limit, this page
// needs to be deleted.
int page_limit = policy.page_limit;
if (page_limit != kUnlimitedPages &&
namespace_page_count[name_space] >= page_limit) {
page_infos_to_delete->push_back(page_info);
continue;
}
// Pages with no file can be removed.
if (!base::PathExists(page_info.file_path)) {
page_infos_to_delete->push_back(page_info);
continue;
}
// If there's no quota, remove the pages.
if (quota_based_clearing &&
remaining_size + page_info.file_size > max_allowed_size) {
page_infos_to_delete->push_back(page_info);
continue;
}
// Otherwise the page needs to be kept, in case the storage usage is still
// higher than the threshold, and we need to clear more pages.
pages_remaining.push_back(page_info);
remaining_size += page_info.file_size;
namespace_page_count[name_space]++;
}
return page_infos_to_delete;
}
bool DeleteArchiveSync(const base::FilePath& file_path) {
// Delete the file only, |false| for recursive.
return base::DeleteFile(file_path, false);
}
// Deletes a page from the store by |offline_id|.
bool DeletePageEntryByOfflineIdSync(sql::Database* db, int64_t offline_id) {
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();
}
std::pair<size_t, DeletePageResult> ClearPagesSync(
std::map<std::string, LifetimePolicy> temp_namespace_policy_map,
const base::Time& start_time,
const ArchiveManager::StorageStats& stats,
sql::Database* db) {
std::unique_ptr<std::vector<PageInfo>> page_infos =
GetPageInfosToClear(temp_namespace_policy_map, start_time, stats, db);
size_t pages_cleared = 0;
for (const auto& page_info : *page_infos) {
if (!base::PathExists(page_info.file_path) ||
DeleteArchiveSync(page_info.file_path)) {
if (DeletePageEntryByOfflineIdSync(db, page_info.offline_id)) {
pages_cleared++;
// Reports the time since creation in minutes.
base::TimeDelta time_since_creation =
start_time - page_info.creation_time;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"OfflinePages.ClearTemporaryPages.TimeSinceCreation",
time_since_creation.InMinutes(), 1,
base::TimeDelta::FromDays(30).InMinutes(), 50);
}
}
}
return std::make_pair(pages_cleared, pages_cleared == page_infos->size()
? DeletePageResult::SUCCESS
: DeletePageResult::STORE_FAILURE);
}
std::map<std::string, LifetimePolicy> GetTempNamespacePolicyMap(
ClientPolicyController* policy_controller) {
std::map<std::string, LifetimePolicy> result;
for (const auto& name_space :
policy_controller->GetNamespacesRemovedOnCacheReset()) {
result.emplace(name_space,
policy_controller->GetPolicy(name_space).lifetime_policy);
}
return result;
}
} // namespace
ClearStorageTask::ClearStorageTask(OfflinePageMetadataStore* store,
ArchiveManager* archive_manager,
ClientPolicyController* policy_controller,
const base::Time& clearup_time,
ClearStorageCallback callback)
: store_(store),
archive_manager_(archive_manager),
policy_controller_(policy_controller),
callback_(std::move(callback)),
clearup_time_(clearup_time),
weak_ptr_factory_(this) {
DCHECK(store_);
DCHECK(archive_manager_);
DCHECK(policy_controller_);
DCHECK(!callback_.is_null());
}
ClearStorageTask::~ClearStorageTask() {}
void ClearStorageTask::Run() {
TRACE_EVENT_ASYNC_BEGIN0("offline_pages", "ClearStorageTask running", this);
archive_manager_->GetStorageStats(
base::BindOnce(&ClearStorageTask::OnGetStorageStatsDone,
weak_ptr_factory_.GetWeakPtr()));
}
void ClearStorageTask::OnGetStorageStatsDone(
const ArchiveManager::StorageStats& stats) {
std::map<std::string, LifetimePolicy> temp_namespace_policy_map =
GetTempNamespacePolicyMap(policy_controller_);
store_->Execute(base::BindOnce(&ClearPagesSync, temp_namespace_policy_map,
clearup_time_, stats),
base::BindOnce(&ClearStorageTask::OnClearPagesDone,
weak_ptr_factory_.GetWeakPtr()),
{0, DeletePageResult::STORE_FAILURE});
}
void ClearStorageTask::OnClearPagesDone(
std::pair<size_t, DeletePageResult> result) {
if (result.first == 0 && result.second == DeletePageResult::SUCCESS) {
InformClearStorageDone(result.first, ClearStorageResult::UNNECESSARY);
return;
}
ClearStorageResult clear_result = ClearStorageResult::SUCCESS;
if (result.second != DeletePageResult::SUCCESS)
clear_result = ClearStorageResult::DELETE_FAILURE;
InformClearStorageDone(result.first, clear_result);
}
void ClearStorageTask::InformClearStorageDone(size_t pages_cleared,
ClearStorageResult result) {
std::move(callback_).Run(pages_cleared, result);
TaskComplete();
TRACE_EVENT_ASYNC_END2("offline_pages", "ClearStorageTask running", this,
"result", static_cast<int>(result), "pages_cleared",
pages_cleared);
}
} // namespace offline_pages