blob: ca88c5ee02550bd332ebb0d1b32260b7b5f58960 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/commerce/core/bookmark_update_manager.h"
#include <algorithm>
#include <vector>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/pref_names.h"
#include "components/commerce/core/price_tracking_utils.h"
#include "components/commerce/core/shopping_service.h"
#include "components/power_bookmarks/core/power_bookmark_utils.h"
#include "components/power_bookmarks/core/proto/shopping_specifics.pb.h"
#include "url/gurl.h"
namespace commerce {
BookmarkUpdateManager::BookmarkUpdateManager(ShoppingService* service,
bookmarks::BookmarkModel* model,
PrefService* prefs)
: shopping_service_(service),
bookmark_model_(model),
pref_service_(prefs) {}
BookmarkUpdateManager::~BookmarkUpdateManager() = default;
void BookmarkUpdateManager::ScheduleUpdate() {
// Check the kill switch. This enabled by default, but can be turned off in
// case we accidentally flood the backend with requests.
if (!base::FeatureList::IsEnabled(kCommerceAllowOnDemandBookmarkUpdates))
return;
// Make sure we don't double-schedule.
if (scheduled_task_)
return;
// By default, time is "null" meaning it is set to 0. In this state, read the
// preference once and then use the in-memory version from this point on.
if (last_update_time_.is_null()) {
last_update_time_ =
pref_service_->GetTime(kShoppingListBookmarkLastUpdateTime);
}
base::TimeDelta time_since_last = base::Time::Now() - last_update_time_;
base::TimeDelta interval = kShoppingListBookmarkpdateIntervalParam.Get();
int64_t ms_delay =
std::clamp((interval - time_since_last).InMilliseconds(),
base::Seconds(0L).InMilliseconds(), interval.InMilliseconds());
scheduled_task_ =
std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
&BookmarkUpdateManager::RunUpdate, weak_ptr_factory_.GetWeakPtr()));
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, scheduled_task_->callback(), base::Milliseconds(ms_delay));
}
void BookmarkUpdateManager::CancelUpdates() {
if (scheduled_task_) {
scheduled_task_->Cancel();
scheduled_task_ = nullptr;
}
}
void BookmarkUpdateManager::RunUpdate() {
// Record the current time as last updated time and immediately schedule the
// next update.
last_update_time_ = base::Time::Now();
pref_service_->SetTime(kShoppingListBookmarkLastUpdateTime,
last_update_time_);
// If something like the enterprise policy was turned off, simply block the
// update logic. In the future we can observe the preference and remove or
// re-add the scheduled update, but this is easier for now.
if (!shopping_service_->IsShoppingListEligible())
return;
scheduled_task_ = nullptr;
ScheduleUpdate();
std::vector<const bookmarks::BookmarkNode*> nodes =
shopping_service_->GetAllShoppingBookmarks();
if (nodes.empty()) {
return;
}
size_t current_batch_count = 0;
size_t total_bookmarks_processed = 0;
pending_update_batches_.emplace();
for (auto* node : nodes) {
// If we've reached the max for a batch, push a new vector onto the queue
// and continue.
if (current_batch_count >=
shopping_service_->GetMaxProductBookmarkUpdatesPerBatch()) {
pending_update_batches_.emplace();
current_batch_count = 0;
}
pending_update_batches_.back().push_back(node->id());
current_batch_count++;
total_bookmarks_processed++;
// If we've reached the maximum number of bookmarks we're willing to
// update, stop.
if (total_bookmarks_processed >= kShoppingListBookmarkUpdateBatchMaxParam) {
break;
}
}
StartNextBatch();
}
void BookmarkUpdateManager::StartNextBatch() {
if (pending_update_batches_.empty()) {
return;
}
expected_bookmark_updates_ = pending_update_batches_.front().size();
received_bookmark_updates_ = 0;
std::vector<int64_t> ids = std::move(pending_update_batches_.front());
pending_update_batches_.pop();
shopping_service_->GetUpdatedProductInfoForBookmarks(
std::move(ids),
base::BindRepeating(&BookmarkUpdateManager::HandleOnDemandResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void BookmarkUpdateManager::HandleOnDemandResponse(
const int64_t bookmark_id,
const GURL& url,
std::optional<ProductInfo> info) {
received_bookmark_updates_++;
if (received_bookmark_updates_ >= expected_bookmark_updates_) {
StartNextBatch();
}
if (!info.has_value())
return;
const bookmarks::BookmarkNode* node =
bookmarks::GetBookmarkNodeByID(bookmark_model_, bookmark_id);
std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
power_bookmarks::GetNodePowerBookmarkMeta(bookmark_model_, node);
if (!meta || !meta->has_shopping_specifics())
return;
if (PopulateOrUpdateBookmarkMetaIfNeeded(meta.get(), info.value())) {
power_bookmarks::SetNodePowerBookmarkMeta(bookmark_model_, node,
std::move(meta));
}
}
} // namespace commerce