1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/dom_distiller/core/task_tracker.h"
7 #include "base/auto_reset.h"
8 #include "base/location.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "components/dom_distiller/core/distilled_content_store.h"
13 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
14 #include "components/dom_distiller/core/proto/distilled_page.pb.h"
16 namespace dom_distiller
{
18 ViewerHandle::ViewerHandle(CancelCallback callback
)
19 : cancel_callback_(callback
) {}
21 ViewerHandle::~ViewerHandle() {
22 if (!cancel_callback_
.is_null()) {
23 cancel_callback_
.Run();
27 TaskTracker::TaskTracker(const ArticleEntry
& entry
,
28 CancelCallback callback
,
29 DistilledContentStore
* content_store
)
30 : cancel_callback_(callback
),
31 content_store_(content_store
),
32 blob_fetcher_running_(false),
35 content_ready_(false),
36 destruction_allowed_(true),
37 weak_ptr_factory_(this) {}
39 TaskTracker::~TaskTracker() {
40 DCHECK(destruction_allowed_
);
41 DCHECK(viewers_
.empty());
44 void TaskTracker::StartDistiller(DistillerFactory
* factory
,
45 scoped_ptr
<DistillerPage
> distiller_page
) {
49 if (entry_
.pages_size() == 0) {
52 GURL
url(entry_
.pages(0).url());
53 DCHECK(url
.is_valid());
55 distiller_
= factory
->CreateDistillerForUrl(url
);
56 distiller_
->DistillPage(url
,
57 distiller_page
.Pass(),
58 base::Bind(&TaskTracker::OnDistillerFinished
,
59 weak_ptr_factory_
.GetWeakPtr()),
60 base::Bind(&TaskTracker::OnArticleDistillationUpdated
,
61 weak_ptr_factory_
.GetWeakPtr()));
64 void TaskTracker::StartBlobFetcher() {
66 blob_fetcher_running_
= true;
67 content_store_
->LoadContent(entry_
,
68 base::Bind(&TaskTracker::OnBlobFetched
,
69 weak_ptr_factory_
.GetWeakPtr()));
73 void TaskTracker::AddSaveCallback(const SaveCallback
& callback
) {
74 DCHECK(!callback
.is_null());
75 save_callbacks_
.push_back(callback
);
77 // Distillation for this task has already completed, and so it can be
79 ScheduleSaveCallbacks(true);
83 scoped_ptr
<ViewerHandle
> TaskTracker::AddViewer(ViewRequestDelegate
* delegate
) {
84 viewers_
.push_back(delegate
);
86 // Distillation for this task has already completed, and so the delegate can
87 // be immediately told of the result.
88 base::ThreadTaskRunnerHandle::Get()->PostTask(
89 FROM_HERE
, base::Bind(&TaskTracker::NotifyViewer
,
90 weak_ptr_factory_
.GetWeakPtr(), delegate
));
92 return scoped_ptr
<ViewerHandle
>(new ViewerHandle(base::Bind(
93 &TaskTracker::RemoveViewer
, weak_ptr_factory_
.GetWeakPtr(), delegate
)));
96 const std::string
& TaskTracker::GetEntryId() const { return entry_
.entry_id(); }
98 bool TaskTracker::HasEntryId(const std::string
& entry_id
) const {
99 return entry_
.entry_id() == entry_id
;
102 bool TaskTracker::HasUrl(const GURL
& url
) const {
103 for (int i
= 0; i
< entry_
.pages_size(); ++i
) {
104 if (entry_
.pages(i
).url() == url
.spec()) {
111 void TaskTracker::RemoveViewer(ViewRequestDelegate
* delegate
) {
112 viewers_
.erase(std::remove(viewers_
.begin(), viewers_
.end(), delegate
));
113 if (viewers_
.empty()) {
118 void TaskTracker::MaybeCancel() {
119 if (!save_callbacks_
.empty() || !viewers_
.empty()) {
120 // There's still work to be done.
124 CancelPendingSources();
126 base::AutoReset
<bool> dont_delete_this_in_callback(&destruction_allowed_
,
128 cancel_callback_
.Run(this);
131 void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); }
133 void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded
) {
134 base::ThreadTaskRunnerHandle::Get()->PostTask(
136 base::Bind(&TaskTracker::DoSaveCallbacks
, weak_ptr_factory_
.GetWeakPtr(),
137 distillation_succeeded
));
140 void TaskTracker::OnDistillerFinished(
141 scoped_ptr
<DistilledArticleProto
> distilled_article
) {
142 if (content_ready_
) {
146 DistilledArticleReady(distilled_article
.Pass());
147 if (content_ready_
) {
148 AddDistilledContentToStore(*distilled_article_
);
151 // 'distiller_ != null' is used as a signal that distillation is in progress,
152 // so it needs to be released so that we know distillation is done.
153 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, distiller_
.release());
155 ContentSourceFinished();
158 void TaskTracker::CancelPendingSources() {
160 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, distiller_
.release());
164 void TaskTracker::OnBlobFetched(
166 scoped_ptr
<DistilledArticleProto
> distilled_article
) {
167 blob_fetcher_running_
= false;
169 if (content_ready_
) {
173 DistilledArticleReady(distilled_article
.Pass());
175 ContentSourceFinished();
178 bool TaskTracker::IsAnySourceRunning() const {
179 return distiller_
|| blob_fetcher_running_
;
182 void TaskTracker::ContentSourceFinished() {
183 if (content_ready_
) {
184 CancelPendingSources();
185 } else if (!IsAnySourceRunning()) {
186 distilled_article_
.reset(new DistilledArticleProto());
187 NotifyViewersAndCallbacks();
191 void TaskTracker::DistilledArticleReady(
192 scoped_ptr
<DistilledArticleProto
> distilled_article
) {
193 DCHECK(!content_ready_
);
195 if (distilled_article
->pages_size() == 0) {
199 content_ready_
= true;
201 distilled_article_
= distilled_article
.Pass();
202 entry_
.set_title(distilled_article_
->title());
203 entry_
.clear_pages();
204 for (int i
= 0; i
< distilled_article_
->pages_size(); ++i
) {
205 sync_pb::ArticlePage
* page
= entry_
.add_pages();
206 page
->set_url(distilled_article_
->pages(i
).url());
209 NotifyViewersAndCallbacks();
212 void TaskTracker::NotifyViewersAndCallbacks() {
213 for (size_t i
= 0; i
< viewers_
.size(); ++i
) {
214 NotifyViewer(viewers_
[i
]);
217 // Already inside a callback run SaveCallbacks directly.
218 DoSaveCallbacks(content_ready_
);
221 void TaskTracker::NotifyViewer(ViewRequestDelegate
* delegate
) {
222 delegate
->OnArticleReady(distilled_article_
.get());
225 void TaskTracker::DoSaveCallbacks(bool success
) {
226 if (!save_callbacks_
.empty()) {
227 for (size_t i
= 0; i
< save_callbacks_
.size(); ++i
) {
228 DCHECK(!save_callbacks_
[i
].is_null());
229 save_callbacks_
[i
].Run(
230 entry_
, distilled_article_
.get(), success
);
233 save_callbacks_
.clear();
238 void TaskTracker::OnArticleDistillationUpdated(
239 const ArticleDistillationUpdate
& article_update
) {
240 for (size_t i
= 0; i
< viewers_
.size(); ++i
) {
241 viewers_
[i
]->OnArticleUpdated(article_update
);
245 void TaskTracker::AddDistilledContentToStore(
246 const DistilledArticleProto
& content
) {
247 if (content_store_
) {
248 content_store_
->SaveContent(
249 entry_
, content
, DistilledContentStore::SaveCallback());
254 } // namespace dom_distiller