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