blob: 3566829cebbcc6b98da5bdc2390c8b3f2888fe8f [file] [log] [blame]
// Copyright 2013 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/dom_distiller/core/task_tracker.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/location.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "components/dom_distiller/core/distilled_content_store.h"
#include "components/dom_distiller/core/proto/distilled_article.pb.h"
#include "components/dom_distiller/core/proto/distilled_page.pb.h"
namespace dom_distiller {
ViewerHandle::ViewerHandle(CancelCallback callback)
: cancel_callback_(std::move(callback)) {}
ViewerHandle::~ViewerHandle() {
if (!cancel_callback_.is_null()) {
std::move(cancel_callback_).Run();
}
}
TaskTracker::TaskTracker(const ArticleEntry& entry,
CancelCallback callback,
DistilledContentStore* content_store)
: cancel_callback_(std::move(callback)),
content_store_(content_store),
blob_fetcher_running_(false),
entry_(entry),
distilled_article_(),
content_ready_(false),
destruction_allowed_(true) {}
TaskTracker::~TaskTracker() {
DCHECK(destruction_allowed_);
DCHECK(viewers_.empty());
}
void TaskTracker::StartDistiller(
DistillerFactory* factory,
std::unique_ptr<DistillerPage> distiller_page) {
if (distiller_) {
return;
}
if (entry_.pages.empty()) {
return;
}
GURL url(entry_.pages[0]);
DCHECK(url.is_valid());
distiller_ = factory->CreateDistillerForUrl(url);
distiller_->DistillPage(
url, std::move(distiller_page),
base::BindOnce(&TaskTracker::OnDistillerFinished,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&TaskTracker::OnArticleDistillationUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
void TaskTracker::StartBlobFetcher() {
if (content_store_) {
blob_fetcher_running_ = true;
content_store_->LoadContent(entry_,
base::BindOnce(&TaskTracker::OnBlobFetched,
weak_ptr_factory_.GetWeakPtr()));
}
}
void TaskTracker::AddSaveCallback(SaveCallback callback) {
DCHECK(!callback.is_null());
save_callbacks_.push_back(std::move(callback));
if (content_ready_) {
// Distillation for this task has already completed, and so it can be
// immediately saved.
ScheduleSaveCallbacks(true);
}
}
std::unique_ptr<ViewerHandle> TaskTracker::AddViewer(
ViewRequestDelegate* delegate) {
viewers_.AddObserver(delegate);
if (content_ready_) {
// Distillation for this task has already completed, and so the delegate can
// be immediately told of the result.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&TaskTracker::NotifyViewer,
weak_ptr_factory_.GetWeakPtr(), delegate));
}
return std::make_unique<ViewerHandle>(base::BindOnce(
&TaskTracker::RemoveViewer, weak_ptr_factory_.GetWeakPtr(), delegate));
}
const std::string& TaskTracker::GetEntryId() const {
return entry_.entry_id;
}
bool TaskTracker::HasEntryId(const std::string& entry_id) const {
return entry_.entry_id == entry_id;
}
bool TaskTracker::HasUrl(const GURL& url) const {
for (const GURL& page : entry_.pages) {
if (page == url) {
return true;
}
}
return false;
}
void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) {
viewers_.RemoveObserver(delegate);
if (viewers_.empty()) {
MaybeCancel();
}
}
void TaskTracker::MaybeCancel() {
if (!save_callbacks_.empty() || !viewers_.empty()) {
// There's still work to be done.
return;
}
CancelPendingSources();
base::AutoReset<bool> dont_delete_this_in_callback(&destruction_allowed_,
false);
std::move(cancel_callback_).Run(this);
}
void TaskTracker::CancelSaveCallbacks() {
ScheduleSaveCallbacks(false);
}
void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&TaskTracker::DoSaveCallbacks,
weak_ptr_factory_.GetWeakPtr(), distillation_succeeded));
}
void TaskTracker::OnDistillerFinished(
std::unique_ptr<DistilledArticleProto> distilled_article) {
if (content_ready_) {
return;
}
DistilledArticleReady(std::move(distilled_article));
if (content_ready_) {
AddDistilledContentToStore(*distilled_article_);
}
// 'distiller_ != null' is used as a signal that distillation is in progress,
// so it needs to be released so that we know distillation is done.
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, distiller_.release());
ContentSourceFinished();
}
void TaskTracker::CancelPendingSources() {
if (distiller_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, distiller_.release());
}
}
void TaskTracker::OnBlobFetched(
bool success,
std::unique_ptr<DistilledArticleProto> distilled_article) {
blob_fetcher_running_ = false;
if (content_ready_) {
return;
}
DistilledArticleReady(std::move(distilled_article));
ContentSourceFinished();
}
bool TaskTracker::IsAnySourceRunning() const {
return distiller_ || blob_fetcher_running_;
}
void TaskTracker::ContentSourceFinished() {
if (content_ready_) {
CancelPendingSources();
} else if (!IsAnySourceRunning()) {
distilled_article_ = std::make_unique<DistilledArticleProto>();
NotifyViewersAndCallbacks();
}
}
void TaskTracker::DistilledArticleReady(
std::unique_ptr<DistilledArticleProto> distilled_article) {
DCHECK(!content_ready_);
if (distilled_article->pages().empty()) {
return;
}
content_ready_ = true;
distilled_article_ = std::move(distilled_article);
entry_.title = distilled_article_->title();
entry_.pages.clear();
for (const auto& page : distilled_article_->pages()) {
entry_.pages.push_back(GURL(page.url()));
}
NotifyViewersAndCallbacks();
}
void TaskTracker::NotifyViewersAndCallbacks() {
for (auto& viewer : viewers_) {
NotifyViewer(&viewer);
}
// Already inside a callback run SaveCallbacks directly.
DoSaveCallbacks(content_ready_);
}
void TaskTracker::NotifyViewer(ViewRequestDelegate* delegate) {
delegate->OnArticleReady(distilled_article_.get());
}
void TaskTracker::DoSaveCallbacks(bool success) {
if (!save_callbacks_.empty()) {
for (auto& callback : save_callbacks_)
std::move(callback).Run(entry_, distilled_article_.get(), success);
save_callbacks_.clear();
MaybeCancel();
}
}
void TaskTracker::OnArticleDistillationUpdated(
const ArticleDistillationUpdate& article_update) {
for (auto& viewer : viewers_) {
viewer.OnArticleUpdated(article_update);
}
}
void TaskTracker::AddDistilledContentToStore(
const DistilledArticleProto& content) {
if (content_store_) {
content_store_->SaveContent(entry_, content,
DistilledContentStore::SaveCallback());
}
}
} // namespace dom_distiller