text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "components/dom_distiller/core/task_tracker.h"
  
  #include "base/auto_reset.h"
  #include "base/message_loop/message_loop.h"
  #include "components/dom_distiller/core/distilled_content_store.h"
  #include "components/dom_distiller/core/proto/distilled_article.pb.h"
  #include "components/dom_distiller/core/proto/distilled_page.pb.h"
  
  namespace dom_distiller {
  
  ViewerHandle::ViewerHandle(CancelCallback callback)
      : cancel_callback_(callback) {}
  
  ViewerHandle::~ViewerHandle() {
    if (!cancel_callback_.is_null()) {
      cancel_callback_.Run();
    }
  }
  
  TaskTracker::TaskTracker(const ArticleEntry& entry,
                           CancelCallback callback,
                           DistilledContentStore* content_store)
      : cancel_callback_(callback),
        content_store_(content_store),
        blob_fetcher_running_(false),
        entry_(entry),
        distilled_article_(),
        content_ready_(false),
        destruction_allowed_(true),
        weak_ptr_factory_(this) {}
  
  TaskTracker::~TaskTracker() {
    DCHECK(destruction_allowed_);
    DCHECK(viewers_.empty());
  }
  
  void TaskTracker::StartDistiller(DistillerFactory* factory,
                                   scoped_ptr<DistillerPage> distiller_page) {
    if (distiller_) {
      return;
    }
    if (entry_.pages_size() == 0) {
      return;
    }
    GURL url(entry_.pages(0).url());
    DCHECK(url.is_valid());
  
    distiller_ = factory->CreateDistiller();
    distiller_->DistillPage(url,
                            distiller_page.Pass(),
                            base::Bind(&TaskTracker::OnDistillerFinished,
                                       weak_ptr_factory_.GetWeakPtr()),
                            base::Bind(&TaskTracker::OnArticleDistillationUpdated,
                                       weak_ptr_factory_.GetWeakPtr()));
  }
  
  void TaskTracker::StartBlobFetcher() {
    if (content_store_) {
      blob_fetcher_running_ = true;
      content_store_->LoadContent(entry_,
                                  base::Bind(&TaskTracker::OnBlobFetched,
                                             weak_ptr_factory_.GetWeakPtr()));
    }
  }
  
  void TaskTracker::AddSaveCallback(const SaveCallback& callback) {
    DCHECK(!callback.is_null());
    save_callbacks_.push_back(callback);
    if (content_ready_) {
      // Distillation for this task has already completed, and so it can be
      // immediately saved.
      ScheduleSaveCallbacks(true);
    }
  }
  
  scoped_ptr<ViewerHandle> TaskTracker::AddViewer(ViewRequestDelegate* delegate) {
    viewers_.push_back(delegate);
    if (content_ready_) {
      // Distillation for this task has already completed, and so the delegate can
      // be immediately told of the result.
      base::MessageLoop::current()->PostTask(
          FROM_HERE,
          base::Bind(&TaskTracker::NotifyViewer,
                     weak_ptr_factory_.GetWeakPtr(),
                     delegate));
    }
    return scoped_ptr<ViewerHandle>(new ViewerHandle(base::Bind(
        &TaskTracker::RemoveViewer, weak_ptr_factory_.GetWeakPtr(), delegate)));
  }
  
  const std::string& TaskTracker::GetEntryId() const { return entry_.entry_id(); }
  
  bool TaskTracker::HasEntryId(const std::string& entry_id) const {
    return entry_.entry_id() == entry_id;
  }
  
  bool TaskTracker::HasUrl(const GURL& url) const {
    for (int i = 0; i < entry_.pages_size(); ++i) {
      if (entry_.pages(i).url() == url.spec()) {
        return true;
      }
    }
    return false;
  }
  
  void TaskTracker::RemoveViewer(ViewRequestDelegate* delegate) {
    viewers_.erase(std::remove(viewers_.begin(), viewers_.end(), delegate));
    if (viewers_.empty()) {
      MaybeCancel();
    }
  }
  
  void TaskTracker::MaybeCancel() {
    if (!save_callbacks_.empty() || !viewers_.empty()) {
      // There's still work to be done.
      return;
    }
  
    CancelPendingSources();
  
    base::AutoReset<bool> dont_delete_this_in_callback(&destruction_allowed_,
                                                       false);
    cancel_callback_.Run(this);
  }
  
  void TaskTracker::CancelSaveCallbacks() { ScheduleSaveCallbacks(false); }
  
  void TaskTracker::ScheduleSaveCallbacks(bool distillation_succeeded) {
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&TaskTracker::DoSaveCallbacks,
                   weak_ptr_factory_.GetWeakPtr(),
                   distillation_succeeded));
  }
  
  void TaskTracker::OnDistillerFinished(
      scoped_ptr<DistilledArticleProto> distilled_article) {
    if (content_ready_) {
      return;
    }
  
    DistilledArticleReady(distilled_article.Pass());
    if (content_ready_) {
      AddDistilledContentToStore(*distilled_article_);
    }
  
    // 'distiller_ != null' is used as a signal that distillation is in progress,
    // so it needs to be released so that we know distillation is done.
    base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release());
  
    ContentSourceFinished();
  }
  
  void TaskTracker::CancelPendingSources() {
    if (distiller_) {
      base::MessageLoop::current()->DeleteSoon(FROM_HERE, distiller_.release());
    }
  }
  
  void TaskTracker::OnBlobFetched(
      bool success,
      scoped_ptr<DistilledArticleProto> distilled_article) {
    blob_fetcher_running_ = false;
  
    if (content_ready_) {
      return;
    }
  
    DistilledArticleReady(distilled_article.Pass());
  
    ContentSourceFinished();
  }
  
  bool TaskTracker::IsAnySourceRunning() const {
    return distiller_ || blob_fetcher_running_;
  }
  
  void TaskTracker::ContentSourceFinished() {
    if (content_ready_) {
      CancelPendingSources();
    } else if (!IsAnySourceRunning()) {
      distilled_article_.reset(new DistilledArticleProto());
      NotifyViewersAndCallbacks();
    }
  }
  
  void TaskTracker::DistilledArticleReady(
      scoped_ptr<DistilledArticleProto> distilled_article) {
    DCHECK(!content_ready_);
  
    if (distilled_article->pages_size() == 0) {
      return;
    }
  
    content_ready_ = true;
  
    distilled_article_ = distilled_article.Pass();
    entry_.set_title(distilled_article_->title());
    entry_.clear_pages();
    for (int i = 0; i < distilled_article_->pages_size(); ++i) {
      sync_pb::ArticlePage* page = entry_.add_pages();
      page->set_url(distilled_article_->pages(i).url());
    }
  
    NotifyViewersAndCallbacks();
  }
  
  void TaskTracker::NotifyViewersAndCallbacks() {
    for (size_t i = 0; i < viewers_.size(); ++i) {
      NotifyViewer(viewers_[i]);
    }
  
    // Already inside a callback run SaveCallbacks directly.
    DoSaveCallbacks(content_ready_);
  }
  
  void TaskTracker::NotifyViewer(ViewRequestDelegate* delegate) {
    delegate->OnArticleReady(distilled_article_.get());
  }
  
  void TaskTracker::DoSaveCallbacks(bool success) {
    if (!save_callbacks_.empty()) {
      for (size_t i = 0; i < save_callbacks_.size(); ++i) {
        DCHECK(!save_callbacks_[i].is_null());
        save_callbacks_[i].Run(
            entry_, distilled_article_.get(), success);
      }
  
      save_callbacks_.clear();
      MaybeCancel();
    }
  }
  
  void TaskTracker::OnArticleDistillationUpdated(
      const ArticleDistillationUpdate& article_update) {
    for (size_t i = 0; i < viewers_.size(); ++i) {
      viewers_[i]->OnArticleUpdated(article_update);
    }
  }
  
  void TaskTracker::AddDistilledContentToStore(
      const DistilledArticleProto& content) {
    if (content_store_) {
      content_store_->SaveContent(
          entry_, content, DistilledContentStore::SaveCallback());
    }
  }
  
  
  }  // namespace dom_distiller
  