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.
4
5#include "components/dom_distiller/core/task_tracker.h"
6
7#include "base/auto_reset.h"
8#include "base/message_loop/message_loop.h"
9#include "components/dom_distiller/core/distilled_content_store.h"
10#include "components/dom_distiller/core/proto/distilled_article.pb.h"
11#include "components/dom_distiller/core/proto/distilled_page.pb.h"
12
13namespace dom_distiller {
14
15ViewerHandle::ViewerHandle(CancelCallback callback)
16    : cancel_callback_(callback) {}
17
18ViewerHandle::~ViewerHandle() {
19  if (!cancel_callback_.is_null()) {
20    cancel_callback_.Run();
21  }
22}
23
24TaskTracker::TaskTracker(const ArticleEntry& entry,
25                         CancelCallback callback,
26                         DistilledContentStore* content_store)
27    : cancel_callback_(callback),
28      content_store_(content_store),
29      blob_fetcher_running_(false),
30      entry_(entry),
31      distilled_article_(),
32      content_ready_(false),
33      destruction_allowed_(true),
34      weak_ptr_factory_(this) {}
35
36TaskTracker::~TaskTracker() {
37  DCHECK(destruction_allowed_);
38  DCHECK(viewers_.empty());
39}
40
41void TaskTracker::StartDistiller(DistillerFactory* factory,
42                                 scoped_ptr<DistillerPage> distiller_page) {
43  if (distiller_) {
44    return;
45  }
46  if (entry_.pages_size() == 0) {
47    return;
48  }
49  GURL url(entry_.pages(0).url());
50  DCHECK(url.is_valid());
51
52  distiller_ = factory->CreateDistiller();
53  distiller_->DistillPage(url,
54                          distiller_page.Pass(),
55                          base::Bind(&TaskTracker::OnDistillerFinished,
56                                     weak_ptr_factory_.GetWeakPtr()),
57                          base::Bind(&TaskTracker::OnArticleDistillationUpdated,
58                                     weak_ptr_factory_.GetWeakPtr()));
59}
60
61void TaskTracker::StartBlobFetcher() {
62  if (content_store_) {
63    blob_fetcher_running_ = true;
64    content_store_->LoadContent(entry_,
65                                base::Bind(&TaskTracker::OnBlobFetched,
66                                           weak_ptr_factory_.GetWeakPtr()));
67  }
68}
69
70void TaskTracker::AddSaveCallback(const SaveCallback& callback) {
71  DCHECK(!callback.is_null());
72  save_callbacks_.push_back(callback);
73  if (content_ready_) {
74    // Distillation for this task has already completed, and so it can be
75    // immediately saved.
76    ScheduleSaveCallbacks(true);
77  }
78}
79
80scoped_ptr<ViewerHandle> TaskTracker::AddViewer(ViewRequestDelegate* delegate) {
81  viewers_.push_back(delegate);
82  if (content_ready_) {
83    // Distillation for this task has already completed, and so the delegate can
84    // be immediately told of the result.
85    base::MessageLoop::current()->PostTask(
86        FROM_HERE,
87        base::Bind(&TaskTracker::NotifyViewer,
88                   weak_ptr_factory_.GetWeakPtr(),
89                   delegate));
90  }
91  return scoped_ptr<ViewerHandle>(new ViewerHandle(base::Bind(
92      &TaskTracker::RemoveViewer, weak_ptr_factory_.GetWeakPtr(), delegate)));
93}
94
95const std::string& TaskTracker::GetEntryId() const { return entry_.entry_id(); }
96
97bool TaskTracker::HasEntryId(const std::string& entry_id) const {
98  return entry_.entry_id() == entry_id;
99}
100
101bool TaskTracker::HasUrl(const GURL& url) const {
102  for (int i = 0; i < entry_.pages_size(); ++i) {
103    if (entry_.pages(i).url() == url.spec()) {
104      return true;
105    }
106  }
107  return false;
108}
109
110void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) {
111  viewers_.erase(std::remove(viewers_.begin(), viewers_.end(), delegate));
112  if (viewers_.empty()) {
113    MaybeCancel();
114  }
115}
116
117void TaskTracker::MaybeCancel() {
118  if (!save_callbacks_.empty() || !viewers_.empty()) {
119    // There's still work to be done.
120    return;
121  }
122
123  CancelPendingSources();
124
125  base::AutoReset<bool> dont_delete_this_in_callback(&destruction_allowed_,
126                                                     false);
127  cancel_callback_.Run(this);
128}
129
130void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); }
131
132void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded) {
133  base::MessageLoop::current()->PostTask(
134      FROM_HERE,
135      base::Bind(&TaskTracker::DoSaveCallbacks,
136                 weak_ptr_factory_.GetWeakPtr(),
137                 distillation_succeeded));
138}
139
140void TaskTracker::OnDistillerFinished(
141    scoped_ptr<DistilledArticleProto> distilled_article) {
142  if (content_ready_) {
143    return;
144  }
145
146  DistilledArticleReady(distilled_article.Pass());
147  if (content_ready_) {
148    AddDistilledContentToStore(*distilled_article_);
149  }
150
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());
154
155  ContentSourceFinished();
156}
157
158void TaskTracker::CancelPendingSources() {
159  if (distiller_) {
160    base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release());
161  }
162}
163
164void TaskTracker::OnBlobFetched(
165    bool success,
166    scoped_ptr<DistilledArticleProto> distilled_article) {
167  blob_fetcher_running_ = false;
168
169  if (content_ready_) {
170    return;
171  }
172
173  DistilledArticleReady(distilled_article.Pass());
174
175  ContentSourceFinished();
176}
177
178bool TaskTracker::IsAnySourceRunning() const {
179  return distiller_ || blob_fetcher_running_;
180}
181
182void TaskTracker::ContentSourceFinished() {
183  if (content_ready_) {
184    CancelPendingSources();
185  } else if (!IsAnySourceRunning()) {
186    distilled_article_.reset(new DistilledArticleProto());
187    NotifyViewersAndCallbacks();
188  }
189}
190
191void TaskTracker::DistilledArticleReady(
192    scoped_ptr<DistilledArticleProto> distilled_article) {
193  DCHECK(!content_ready_);
194
195  if (distilled_article->pages_size() == 0) {
196    return;
197  }
198
199  content_ready_ = true;
200
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());
207  }
208
209  NotifyViewersAndCallbacks();
210}
211
212void TaskTracker::NotifyViewersAndCallbacks() {
213  for (size_t i = 0; i < viewers_.size(); ++i) {
214    NotifyViewer(viewers_[i]);
215  }
216
217  // Already inside a callback run SaveCallbacks directly.
218  DoSaveCallbacks(content_ready_);
219}
220
221void TaskTracker::NotifyViewer(ViewRequestDelegate* delegate) {
222  delegate->OnArticleReady(distilled_article_.get());
223}
224
225void 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);
231    }
232
233    save_callbacks_.clear();
234    MaybeCancel();
235  }
236}
237
238void TaskTracker::OnArticleDistillationUpdated(
239    const ArticleDistillationUpdate& article_update) {
240  for (size_t i = 0; i < viewers_.size(); ++i) {
241    viewers_[i]->OnArticleUpdated(article_update);
242  }
243}
244
245void TaskTracker::AddDistilledContentToStore(
246    const DistilledArticleProto& content) {
247  if (content_store_) {
248    content_store_->SaveContent(
249        entry_, content, DistilledContentStore::SaveCallback());
250  }
251}
252
253
254}  // namespace dom_distiller
255