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