blob: 02f0ddeca3a0e87bb3df01f76c32fa2db1ff0af0 [file] [log] [blame]
// Copyright 2016 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/background/pick_request_task.h"
#include <memory>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/offline_pages/core/background/device_conditions.h"
#include "components/offline_pages/core/background/offliner_policy.h"
#include "components/offline_pages/core/background/offliner_policy_utils.h"
#include "components/offline_pages/core/background/request_coordinator_event_logger.h"
#include "components/offline_pages/core/background/request_notifier.h"
#include "components/offline_pages/core/background/request_queue_store.h"
#include "components/offline_pages/core/background/save_page_request.h"
#include "components/offline_pages/core/client_policy_controller.h"
#include "components/offline_pages/core/offline_clock.h"
namespace {
template <typename T>
int signum(T t) {
return (T(0) < t) - (t < T(0));
}
bool kCleanupNeeded = true;
bool kNonUserRequestsFound = true;
#define CALL_MEMBER_FUNCTION(object, ptrToMember) ((object)->*(ptrToMember))
} // namespace
namespace offline_pages {
const base::TimeDelta PickRequestTask::kDeferInterval =
base::TimeDelta::FromMinutes(1);
PickRequestTask::PickRequestTask(
RequestQueueStore* store,
OfflinerPolicy* policy,
ClientPolicyController* policy_controller,
RequestPickedCallback picked_callback,
RequestNotPickedCallback not_picked_callback,
RequestCountCallback request_count_callback,
DeviceConditions device_conditions,
const std::set<int64_t>& disabled_requests,
base::circular_deque<int64_t>* prioritized_requests)
: store_(store),
policy_(policy),
policy_controller_(policy_controller),
picked_callback_(std::move(picked_callback)),
not_picked_callback_(std::move(not_picked_callback)),
request_count_callback_(std::move(request_count_callback)),
device_conditions_(std::move(device_conditions)),
disabled_requests_(disabled_requests),
prioritized_requests_(prioritized_requests),
weak_ptr_factory_(this) {}
PickRequestTask::~PickRequestTask() {}
void PickRequestTask::Run() {
GetRequests();
}
void PickRequestTask::GetRequests() {
// Get all the requests from the queue, we will classify them in the callback.
store_->GetRequests(
base::BindOnce(&PickRequestTask::Choose, weak_ptr_factory_.GetWeakPtr()));
}
void PickRequestTask::Choose(
bool success,
std::vector<std::unique_ptr<SavePageRequest>> requests) {
// If there is nothing to do, return right away.
if (requests.empty()) {
std::move(request_count_callback_).Run(requests.size(), 0);
std::move(not_picked_callback_)
.Run(!kNonUserRequestsFound, !kCleanupNeeded, base::Time());
TaskComplete();
return;
}
// All available requests
std::unique_ptr<std::vector<SavePageRequest>> available_requests =
std::make_unique<std::vector<SavePageRequest>>();
// Pick the most deserving request for our conditions.
const SavePageRequest* picked_request = nullptr;
RequestCompareFunction comparator = nullptr;
// Choose which comparison function to use based on policy.
if (policy_->RetryCountIsMoreImportantThanRecency())
comparator = &PickRequestTask::RetryCountFirstCompareFunction;
else
comparator = &PickRequestTask::RecencyFirstCompareFunction;
bool non_user_requested_tasks_remaining = false;
bool cleanup_needed = false;
size_t total_request_count = requests.size();
// Request ids which are available for picking.
std::unordered_set<int64_t> available_request_ids;
// If there was a deferred task, this records the earliest time a task will
// become available.
base::Time defer_available_time;
// Iterate through the requests, filter out unavailable requests and get other
// information (if cleanup is needed and number of non-user-requested
// requests).
for (const auto& request : requests) {
// If the request is expired or has exceeded the retry count, skip it.
if (OfflinerPolicyUtils::CheckRequestExpirationStatus(request.get(),
policy_) !=
OfflinerPolicyUtils::RequestExpirationStatus::VALID) {
cleanup_needed = true;
continue;
}
// If the request is on the disabled list, skip it.
auto search = disabled_requests_.find(request->request_id());
if (search != disabled_requests_.end())
continue;
// If there are non-user-requested tasks remaining, we need to make sure
// that they are scheduled after we run out of user requested tasks. Here we
// detect if any exist. If we don't find any user-requested tasks, we will
// inform the "not_picked_callback_" that it needs to schedule a task for
// non-user-requested items, which have different network and power needs.
if (!request->user_requested())
non_user_requested_tasks_remaining = true;
if (request->request_state() == SavePageRequest::RequestState::AVAILABLE)
available_requests->push_back(*request);
if (!RequestConditionsSatisfied(*request))
continue;
if (policy_controller_->GetPolicy(request->client_id().name_space)
.defer_background_fetch_while_page_is_active) {
if (!request->last_attempt_time().is_null() &&
OfflineClock()->Now() - request->last_attempt_time() <
kDeferInterval) {
defer_available_time = request->last_attempt_time() + kDeferInterval;
continue;
}
}
available_request_ids.insert(request->request_id());
}
// Report the request queue counts.
std::move(request_count_callback_)
.Run(total_request_count, available_requests->size());
// Search for and pick the prioritized request which is available for picking
// from |available_request_ids|, the closer to the end means higher priority.
// Also if a request in |prioritized_requests_| doesn't exist in |requests|
// we're going to remove it.
// For every ID in |available_request_ids|, there exists a corresponding
// request in |requests|, so this won't be an infinite loop: either we pick a
// request, or there's a request being poped from |prioritized_requests_|.
while (!picked_request && !prioritized_requests_->empty()) {
if (available_request_ids.count(prioritized_requests_->back()) > 0) {
for (const auto& request : *available_requests) {
if (request.request_id() == prioritized_requests_->back()) {
picked_request = &request;
break;
}
}
DCHECK(picked_request);
} else {
prioritized_requests_->pop_back();
}
}
// If no request was found from the priority list, find the best request
// according to current policies.
if (!picked_request) {
for (const auto& request : *available_requests) {
if ((available_request_ids.count(request.request_id()) > 0) &&
(!picked_request ||
IsNewRequestBetter(*picked_request, request, comparator))) {
picked_request = &request;
}
}
}
// If we have a best request to try next, get the request coodinator to
// start it. Otherwise return that we have no candidates.
if (picked_request != nullptr) {
std::move(picked_callback_)
.Run(*picked_request, std::move(available_requests), cleanup_needed);
} else {
std::move(not_picked_callback_)
.Run(non_user_requested_tasks_remaining, cleanup_needed,
defer_available_time);
}
TaskComplete();
}
// Filter out requests that don't meet the current conditions. For instance, if
// this is a predictive request, and we are not on WiFi, it should be ignored
// this round.
bool PickRequestTask::RequestConditionsSatisfied(
const SavePageRequest& request) {
// If the user did not request the page directly, make sure we are connected
// to power and have WiFi and sufficient battery remaining before we take this
// request.
if (!device_conditions_.IsPowerConnected() &&
policy_->PowerRequired(request.user_requested())) {
return false;
}
if (device_conditions_.GetNetConnectionType() !=
net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI &&
policy_->UnmeteredNetworkRequired(request.user_requested())) {
return false;
}
if (device_conditions_.GetBatteryPercentage() <
policy_->BatteryPercentageRequired(request.user_requested())) {
return false;
}
// If the request is paused, do not consider it.
if (request.request_state() == SavePageRequest::RequestState::PAUSED)
return false;
return true;
}
// Look at policies to decide which requests to prefer.
bool PickRequestTask::IsNewRequestBetter(const SavePageRequest& oldRequest,
const SavePageRequest& newRequest,
RequestCompareFunction comparator) {
// User requested pages get priority.
if (newRequest.user_requested() && !oldRequest.user_requested())
return true;
// Otherwise, use the comparison function for the current policy, which
// returns true if the older request is better.
return !(CALL_MEMBER_FUNCTION(this, comparator)(oldRequest, newRequest));
}
// Compare the results, checking request count before recency. Returns true if
// left hand side is better, false otherwise.
bool PickRequestTask::RetryCountFirstCompareFunction(
const SavePageRequest& left,
const SavePageRequest& right) {
// Check the attempt count.
int result = CompareRetryCount(left, right);
if (result != 0)
return (result > 0);
// If we get here, the attempt counts were the same, so check recency.
result = CompareCreationTime(left, right);
return (result > 0);
}
// Compare the results, checking recency before request count. Returns true if
// left hand side is better, false otherwise.
bool PickRequestTask::RecencyFirstCompareFunction(
const SavePageRequest& left,
const SavePageRequest& right) {
// Check the recency.
int result = CompareCreationTime(left, right);
if (result != 0)
return (result > 0);
// If we get here, the recency was the same, so check the attempt count.
result = CompareRetryCount(left, right);
return (result > 0);
}
// Compare left and right side, returning 1 if the left side is better
// (preferred by policy), 0 if the same, and -1 if the right side is better.
int PickRequestTask::CompareRetryCount(const SavePageRequest& left,
const SavePageRequest& right) {
// Check the attempt count.
int result =
signum(left.completed_attempt_count() - right.completed_attempt_count());
// Flip the direction of comparison if policy prefers fewer retries.
if (policy_->ShouldPreferUntriedRequests())
result *= -1;
return result;
}
// Compare left and right side, returning 1 if the left side is better
// (preferred by policy), 0 if the same, and -1 if the right side is better.
int PickRequestTask::CompareCreationTime(const SavePageRequest& left,
const SavePageRequest& right) {
// Check the recency.
base::TimeDelta difference = left.creation_time() - right.creation_time();
int result = signum(difference.InMilliseconds());
// Flip the direction of comparison if policy prefers fewer retries.
if (policy_->ShouldPreferEarlierRequests())
result *= -1;
return result;
}
} // namespace offline_pages