Home | History | Annotate | Download | only in core
      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 
     13 namespace dom_distiller {
     14 
     15 ViewerHandle::ViewerHandle(CancelCallback callback)
     16     : cancel_callback_(callback) {}
     17 
     18 ViewerHandle::~ViewerHandle() {
     19   if (!cancel_callback_.is_null()) {
     20     cancel_callback_.Run();
     21   }
     22 }
     23 
     24 TaskTracker::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 
     36 TaskTracker::~TaskTracker() {
     37   DCHECK(destruction_allowed_);
     38   DCHECK(viewers_.empty());
     39 }
     40 
     41 void 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 
     61 void 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 
     70 void 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 
     80 scoped_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 
     95 const std::string& TaskTracker::GetEntryId() const { return entry_.entry_id(); }
     96 
     97 bool TaskTracker::HasEntryId(const std::string& entry_id) const {
     98   return entry_.entry_id() == entry_id;
     99 }
    100 
    101 bool 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 
    110 void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) {
    111   viewers_.erase(std::remove(viewers_.begin(), viewers_.end(), delegate));
    112   if (viewers_.empty()) {
    113     MaybeCancel();
    114   }
    115 }
    116 
    117 void 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 
    130 void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); }
    131 
    132 void 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 
    140 void 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 
    158 void TaskTracker::CancelPendingSources() {
    159   if (distiller_) {
    160     base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release());
    161   }
    162 }
    163 
    164 void 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 
    178 bool TaskTracker::IsAnySourceRunning() const {
    179   return distiller_ || blob_fetcher_running_;
    180 }
    181 
    182 void TaskTracker::ContentSourceFinished() {
    183   if (content_ready_) {
    184     CancelPendingSources();
    185   } else if (!IsAnySourceRunning()) {
    186     distilled_article_.reset(new DistilledArticleProto());
    187     NotifyViewersAndCallbacks();
    188   }
    189 }
    190 
    191 void 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 
    212 void 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 
    221 void TaskTracker::NotifyViewer(ViewRequestDelegate* delegate) {
    222   delegate->OnArticleReady(distilled_article_.get());
    223 }
    224 
    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);
    231     }
    232 
    233     save_callbacks_.clear();
    234     MaybeCancel();
    235   }
    236 }
    237 
    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);
    242   }
    243 }
    244 
    245 void 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