| // Copyright 2013 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/dom_distiller/core/task_tracker.h" |
| |
| #include "base/auto_reset.h" |
| #include "base/message_loop/message_loop.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_(callback) {} |
| |
| ViewerHandle::~ViewerHandle() { |
| if (!cancel_callback_.is_null()) { |
| cancel_callback_.Run(); |
| } |
| } |
| |
| TaskTracker::TaskTracker(const ArticleEntry& entry, |
| CancelCallback callback, |
| DistilledContentStore* content_store) |
| : cancel_callback_(callback), |
| content_store_(content_store), |
| blob_fetcher_running_(false), |
| entry_(entry), |
| distilled_article_(), |
| content_ready_(false), |
| destruction_allowed_(true), |
| weak_ptr_factory_(this) {} |
| |
| TaskTracker::~TaskTracker() { |
| DCHECK(destruction_allowed_); |
| DCHECK(viewers_.empty()); |
| } |
| |
| void TaskTracker::StartDistiller(DistillerFactory* factory, |
| scoped_ptr<DistillerPage> distiller_page) { |
| if (distiller_) { |
| return; |
| } |
| if (entry_.pages_size() == 0) { |
| return; |
| } |
| GURL url(entry_.pages(0).url()); |
| DCHECK(url.is_valid()); |
| |
| distiller_ = factory->CreateDistiller(); |
| distiller_->DistillPage(url, |
| distiller_page.Pass(), |
| base::Bind(&TaskTracker::OnDistillerFinished, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&TaskTracker::OnArticleDistillationUpdated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void TaskTracker::StartBlobFetcher() { |
| if (content_store_) { |
| content_store_->LoadContent(entry_, |
| base::Bind(&TaskTracker::OnBlobFetched, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void TaskTracker::AddSaveCallback(const SaveCallback& callback) { |
| DCHECK(!callback.is_null()); |
| save_callbacks_.push_back(callback); |
| if (content_ready_) { |
| // Distillation for this task has already completed, and so it can be |
| // immediately saved. |
| ScheduleSaveCallbacks(true); |
| } |
| } |
| |
| scoped_ptr<ViewerHandle> TaskTracker::AddViewer(ViewRequestDelegate* delegate) { |
| viewers_.push_back(delegate); |
| if (content_ready_) { |
| // Distillation for this task has already completed, and so the delegate can |
| // be immediately told of the result. |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&TaskTracker::NotifyViewer, |
| weak_ptr_factory_.GetWeakPtr(), |
| delegate)); |
| } |
| return scoped_ptr<ViewerHandle>(new ViewerHandle(base::Bind( |
| &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 (int i = 0; i < entry_.pages_size(); ++i) { |
| if (entry_.pages(i).url() == url.spec()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) { |
| viewers_.erase(std::remove(viewers_.begin(), viewers_.end(), 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); |
| cancel_callback_.Run(this); |
| } |
| |
| void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); } |
| |
| void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&TaskTracker::DoSaveCallbacks, |
| weak_ptr_factory_.GetWeakPtr(), |
| distillation_succeeded)); |
| } |
| |
| void TaskTracker::OnDistillerFinished( |
| scoped_ptr<DistilledArticleProto> distilled_article) { |
| if (content_ready_) { |
| return; |
| } |
| |
| DistilledArticleReady(distilled_article.Pass()); |
| if (content_ready_) { |
| AddDistilledContentToStore(*distilled_article_); |
| } |
| |
| ContentSourceFinished(); |
| } |
| |
| void TaskTracker::CancelPendingSources() { |
| base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release()); |
| } |
| |
| void TaskTracker::OnBlobFetched( |
| bool success, |
| scoped_ptr<DistilledArticleProto> distilled_article) { |
| blob_fetcher_running_ = false; |
| |
| if (content_ready_) { |
| return; |
| } |
| |
| DistilledArticleReady(distilled_article.Pass()); |
| |
| ContentSourceFinished(); |
| } |
| |
| bool TaskTracker::IsAnySourceRunning() const { |
| return distiller_ || blob_fetcher_running_; |
| } |
| |
| void TaskTracker::ContentSourceFinished() { |
| if (content_ready_) { |
| CancelPendingSources(); |
| } else if (!IsAnySourceRunning()) { |
| distilled_article_.reset(new DistilledArticleProto()); |
| NotifyViewersAndCallbacks(); |
| } |
| } |
| |
| void TaskTracker::DistilledArticleReady( |
| scoped_ptr<DistilledArticleProto> distilled_article) { |
| DCHECK(!content_ready_); |
| |
| if (distilled_article->pages_size() == 0) { |
| return; |
| } |
| |
| content_ready_ = true; |
| |
| distilled_article_ = distilled_article.Pass(); |
| entry_.set_title(distilled_article_->title()); |
| entry_.clear_pages(); |
| for (int i = 0; i < distilled_article_->pages_size(); ++i) { |
| sync_pb::ArticlePage* page = entry_.add_pages(); |
| page->set_url(distilled_article_->pages(i).url()); |
| } |
| |
| NotifyViewersAndCallbacks(); |
| } |
| |
| void TaskTracker::NotifyViewersAndCallbacks() { |
| for (size_t i = 0; i < viewers_.size(); ++i) { |
| NotifyViewer(viewers_[i]); |
| } |
| |
| // 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 (size_t i = 0; i < save_callbacks_.size(); ++i) { |
| DCHECK(!save_callbacks_[i].is_null()); |
| save_callbacks_[i].Run( |
| entry_, distilled_article_.get(), success); |
| } |
| |
| save_callbacks_.clear(); |
| MaybeCancel(); |
| } |
| } |
| |
| void TaskTracker::OnArticleDistillationUpdated( |
| const ArticleDistillationUpdate& article_update) { |
| for (size_t i = 0; i < viewers_.size(); ++i) { |
| viewers_[i]->OnArticleUpdated(article_update); |
| } |
| } |
| |
| void TaskTracker::AddDistilledContentToStore( |
| const DistilledArticleProto& content) { |
| if (content_store_) { |
| content_store_->SaveContent( |
| entry_, content, DistilledContentStore::SaveCallback()); |
| } |
| } |
| |
| |
| } // namespace dom_distiller |