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 content_store_->LoadContent(entry_, 64 base::Bind(&TaskTracker::OnBlobFetched, 65 weak_ptr_factory_.GetWeakPtr())); 66 } 67 } 68 69 void TaskTracker::AddSaveCallback(const SaveCallback& callback) { 70 DCHECK(!callback.is_null()); 71 save_callbacks_.push_back(callback); 72 if (content_ready_) { 73 // Distillation for this task has already completed, and so it can be 74 // immediately saved. 75 ScheduleSaveCallbacks(true); 76 } 77 } 78 79 scoped_ptr<ViewerHandle> TaskTracker::AddViewer(ViewRequestDelegate* delegate) { 80 viewers_.push_back(delegate); 81 if (content_ready_) { 82 // Distillation for this task has already completed, and so the delegate can 83 // be immediately told of the result. 84 base::MessageLoop::current()->PostTask( 85 FROM_HERE, 86 base::Bind(&TaskTracker::NotifyViewer, 87 weak_ptr_factory_.GetWeakPtr(), 88 delegate)); 89 } 90 return scoped_ptr<ViewerHandle>(new ViewerHandle(base::Bind( 91 &TaskTracker::RemoveViewer, weak_ptr_factory_.GetWeakPtr(), delegate))); 92 } 93 94 const std::string& TaskTracker::GetEntryId() const { return entry_.entry_id(); } 95 96 bool TaskTracker::HasEntryId(const std::string& entry_id) const { 97 return entry_.entry_id() == entry_id; 98 } 99 100 bool TaskTracker::HasUrl(const GURL& url) const { 101 for (int i = 0; i < entry_.pages_size(); ++i) { 102 if (entry_.pages(i).url() == url.spec()) { 103 return true; 104 } 105 } 106 return false; 107 } 108 109 void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) { 110 viewers_.erase(std::remove(viewers_.begin(), viewers_.end(), delegate)); 111 if (viewers_.empty()) { 112 MaybeCancel(); 113 } 114 } 115 116 void TaskTracker::MaybeCancel() { 117 if (!save_callbacks_.empty() || !viewers_.empty()) { 118 // There's still work to be done. 119 return; 120 } 121 122 CancelPendingSources(); 123 124 base::AutoReset<bool> dont_delete_this_in_callback(&destruction_allowed_, 125 false); 126 cancel_callback_.Run(this); 127 } 128 129 void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); } 130 131 void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded) { 132 base::MessageLoop::current()->PostTask( 133 FROM_HERE, 134 base::Bind(&TaskTracker::DoSaveCallbacks, 135 weak_ptr_factory_.GetWeakPtr(), 136 distillation_succeeded)); 137 } 138 139 void TaskTracker::OnDistillerFinished( 140 scoped_ptr<DistilledArticleProto> distilled_article) { 141 if (content_ready_) { 142 return; 143 } 144 145 DistilledArticleReady(distilled_article.Pass()); 146 if (content_ready_) { 147 AddDistilledContentToStore(*distilled_article_); 148 } 149 150 ContentSourceFinished(); 151 } 152 153 void TaskTracker::CancelPendingSources() { 154 base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release()); 155 } 156 157 void TaskTracker::OnBlobFetched( 158 bool success, 159 scoped_ptr<DistilledArticleProto> distilled_article) { 160 blob_fetcher_running_ = false; 161 162 if (content_ready_) { 163 return; 164 } 165 166 DistilledArticleReady(distilled_article.Pass()); 167 168 ContentSourceFinished(); 169 } 170 171 bool TaskTracker::IsAnySourceRunning() const { 172 return distiller_ || blob_fetcher_running_; 173 } 174 175 void TaskTracker::ContentSourceFinished() { 176 if (content_ready_) { 177 CancelPendingSources(); 178 } else if (!IsAnySourceRunning()) { 179 distilled_article_.reset(new DistilledArticleProto()); 180 NotifyViewersAndCallbacks(); 181 } 182 } 183 184 void TaskTracker::DistilledArticleReady( 185 scoped_ptr<DistilledArticleProto> distilled_article) { 186 DCHECK(!content_ready_); 187 188 if (distilled_article->pages_size() == 0) { 189 return; 190 } 191 192 content_ready_ = true; 193 194 distilled_article_ = distilled_article.Pass(); 195 entry_.set_title(distilled_article_->title()); 196 entry_.clear_pages(); 197 for (int i = 0; i < distilled_article_->pages_size(); ++i) { 198 sync_pb::ArticlePage* page = entry_.add_pages(); 199 page->set_url(distilled_article_->pages(i).url()); 200 } 201 202 NotifyViewersAndCallbacks(); 203 } 204 205 void TaskTracker::NotifyViewersAndCallbacks() { 206 for (size_t i = 0; i < viewers_.size(); ++i) { 207 NotifyViewer(viewers_[i]); 208 } 209 210 // Already inside a callback run SaveCallbacks directly. 211 DoSaveCallbacks(content_ready_); 212 } 213 214 void TaskTracker::NotifyViewer(ViewRequestDelegate* delegate) { 215 delegate->OnArticleReady(distilled_article_.get()); 216 } 217 218 void TaskTracker::DoSaveCallbacks(bool success) { 219 if (!save_callbacks_.empty()) { 220 for (size_t i = 0; i < save_callbacks_.size(); ++i) { 221 DCHECK(!save_callbacks_[i].is_null()); 222 save_callbacks_[i].Run( 223 entry_, distilled_article_.get(), success); 224 } 225 226 save_callbacks_.clear(); 227 MaybeCancel(); 228 } 229 } 230 231 void TaskTracker::OnArticleDistillationUpdated( 232 const ArticleDistillationUpdate& article_update) { 233 for (size_t i = 0; i < viewers_.size(); ++i) { 234 viewers_[i]->OnArticleUpdated(article_update); 235 } 236 } 237 238 void TaskTracker::AddDistilledContentToStore( 239 const DistilledArticleProto& content) { 240 if (content_store_) { 241 content_store_->SaveContent( 242 entry_, content, DistilledContentStore::SaveCallback()); 243 } 244 } 245 246 247 } // namespace dom_distiller 248