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/dom_distiller_service.h"
6
7#include "base/guid.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/dom_distiller_store.h"
11#include "components/dom_distiller/core/proto/distilled_article.pb.h"
12#include "components/dom_distiller/core/task_tracker.h"
13#include "url/gurl.h"
14
15namespace dom_distiller {
16
17namespace {
18
19ArticleEntry CreateSkeletonEntryForUrl(const GURL& url) {
20  ArticleEntry skeleton;
21  skeleton.set_entry_id(base::GenerateGUID());
22  ArticleEntryPage* page = skeleton.add_pages();
23  page->set_url(url.spec());
24
25  DCHECK(IsEntryValid(skeleton));
26  return skeleton;
27}
28
29void RunArticleAvailableCallback(
30    const DomDistillerService::ArticleAvailableCallback& article_cb,
31    const ArticleEntry& entry,
32    const DistilledArticleProto* article_proto,
33    bool distillation_succeeded) {
34  article_cb.Run(distillation_succeeded);
35}
36
37}  // namespace
38
39DomDistillerService::DomDistillerService(
40    scoped_ptr<DomDistillerStoreInterface> store,
41    scoped_ptr<DistillerFactory> distiller_factory,
42    scoped_ptr<DistillerPageFactory> distiller_page_factory,
43    scoped_ptr<DistilledPagePrefs> distilled_page_prefs)
44    : store_(store.Pass()),
45      content_store_(new InMemoryContentStore(kDefaultMaxNumCachedEntries)),
46      distiller_factory_(distiller_factory.Pass()),
47      distiller_page_factory_(distiller_page_factory.Pass()),
48      distilled_page_prefs_(distilled_page_prefs.Pass()) {
49}
50
51DomDistillerService::~DomDistillerService() {
52}
53
54syncer::SyncableService* DomDistillerService::GetSyncableService() const {
55  return store_->GetSyncableService();
56}
57
58scoped_ptr<DistillerPage> DomDistillerService::CreateDefaultDistillerPage(
59    const gfx::Size& render_view_size) {
60  return distiller_page_factory_->CreateDistillerPage(render_view_size).Pass();
61}
62
63scoped_ptr<DistillerPage>
64DomDistillerService::CreateDefaultDistillerPageWithHandle(
65    scoped_ptr<SourcePageHandle> handle) {
66  return distiller_page_factory_->CreateDistillerPageWithHandle(handle.Pass())
67      .Pass();
68}
69
70const std::string DomDistillerService::AddToList(
71    const GURL& url,
72    scoped_ptr<DistillerPage> distiller_page,
73    const ArticleAvailableCallback& article_cb) {
74  ArticleEntry entry;
75  const bool is_already_added = store_->GetEntryByUrl(url, &entry);
76
77  TaskTracker* task_tracker;
78  if (is_already_added) {
79    task_tracker = GetTaskTrackerForEntry(entry);
80    if (task_tracker == NULL) {
81      // Entry is in the store but there is no task tracker. This could
82      // happen when distillation has already completed. For now just return
83      // true.
84      // TODO(shashishekhar): Change this to check if article is available,
85      // An article may not be available for a variety of reasons, e.g.
86      // distillation failure or blobs not available locally.
87      base::MessageLoop::current()->PostTask(FROM_HERE,
88                                             base::Bind(article_cb, true));
89      return entry.entry_id();
90    }
91  } else {
92    task_tracker = GetOrCreateTaskTrackerForUrl(url);
93  }
94
95  if (!article_cb.is_null()) {
96    task_tracker->AddSaveCallback(
97        base::Bind(&RunArticleAvailableCallback, article_cb));
98  }
99
100  if (!is_already_added) {
101    task_tracker->AddSaveCallback(base::Bind(
102        &DomDistillerService::AddDistilledPageToList, base::Unretained(this)));
103    task_tracker->StartDistiller(distiller_factory_.get(),
104                                 distiller_page.Pass());
105    task_tracker->StartBlobFetcher();
106  }
107
108  return task_tracker->GetEntryId();
109}
110
111bool DomDistillerService::HasEntry(const std::string& entry_id) {
112  return store_->GetEntryById(entry_id, NULL);
113}
114
115std::string DomDistillerService::GetUrlForEntry(const std::string& entry_id) {
116  ArticleEntry entry;
117  if (store_->GetEntryById(entry_id, &entry)) {
118    return entry.pages().Get(0).url();
119  }
120  return "";
121}
122
123std::vector<ArticleEntry> DomDistillerService::GetEntries() const {
124  return store_->GetEntries();
125}
126
127scoped_ptr<ArticleEntry> DomDistillerService::RemoveEntry(
128    const std::string& entry_id) {
129  scoped_ptr<ArticleEntry> entry(new ArticleEntry);
130  entry->set_entry_id(entry_id);
131  TaskTracker* task_tracker = GetTaskTrackerForEntry(*entry);
132  if (task_tracker != NULL) {
133    task_tracker->CancelSaveCallbacks();
134  }
135
136  if (!store_->GetEntryById(entry_id, entry.get())) {
137    return scoped_ptr<ArticleEntry>();
138  }
139
140  if (store_->RemoveEntry(*entry)) {
141    return entry.Pass();
142  }
143  return scoped_ptr<ArticleEntry>();
144}
145
146scoped_ptr<ViewerHandle> DomDistillerService::ViewEntry(
147    ViewRequestDelegate* delegate,
148    scoped_ptr<DistillerPage> distiller_page,
149    const std::string& entry_id) {
150  ArticleEntry entry;
151  if (!store_->GetEntryById(entry_id, &entry)) {
152    return scoped_ptr<ViewerHandle>();
153  }
154
155  TaskTracker* task_tracker = GetOrCreateTaskTrackerForEntry(entry);
156  scoped_ptr<ViewerHandle> viewer_handle = task_tracker->AddViewer(delegate);
157  task_tracker->StartDistiller(distiller_factory_.get(), distiller_page.Pass());
158  task_tracker->StartBlobFetcher();
159
160  return viewer_handle.Pass();
161}
162
163scoped_ptr<ViewerHandle> DomDistillerService::ViewUrl(
164    ViewRequestDelegate* delegate,
165    scoped_ptr<DistillerPage> distiller_page,
166    const GURL& url) {
167  if (!url.is_valid()) {
168    return scoped_ptr<ViewerHandle>();
169  }
170
171  TaskTracker* task_tracker = GetOrCreateTaskTrackerForUrl(url);
172  scoped_ptr<ViewerHandle> viewer_handle = task_tracker->AddViewer(delegate);
173  task_tracker->StartDistiller(distiller_factory_.get(), distiller_page.Pass());
174  task_tracker->StartBlobFetcher();
175
176  return viewer_handle.Pass();
177}
178
179TaskTracker* DomDistillerService::GetOrCreateTaskTrackerForUrl(
180    const GURL& url) {
181  ArticleEntry entry;
182  if (store_->GetEntryByUrl(url, &entry)) {
183    return GetOrCreateTaskTrackerForEntry(entry);
184  }
185
186  for (TaskList::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
187    if ((*it)->HasUrl(url)) {
188      return *it;
189    }
190  }
191
192  ArticleEntry skeleton_entry = CreateSkeletonEntryForUrl(url);
193  TaskTracker* task_tracker = CreateTaskTracker(skeleton_entry);
194  return task_tracker;
195}
196
197TaskTracker* DomDistillerService::GetTaskTrackerForEntry(
198    const ArticleEntry& entry) const {
199  const std::string& entry_id = entry.entry_id();
200  for (TaskList::const_iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
201    if ((*it)->HasEntryId(entry_id)) {
202      return *it;
203    }
204  }
205  return NULL;
206}
207
208TaskTracker* DomDistillerService::GetOrCreateTaskTrackerForEntry(
209    const ArticleEntry& entry) {
210  TaskTracker* task_tracker = GetTaskTrackerForEntry(entry);
211  if (task_tracker == NULL) {
212    task_tracker = CreateTaskTracker(entry);
213  }
214  return task_tracker;
215}
216
217TaskTracker* DomDistillerService::CreateTaskTracker(const ArticleEntry& entry) {
218  TaskTracker::CancelCallback cancel_callback =
219      base::Bind(&DomDistillerService::CancelTask, base::Unretained(this));
220  TaskTracker* tracker =
221      new TaskTracker(entry, cancel_callback, content_store_.get());
222  tasks_.push_back(tracker);
223  return tracker;
224}
225
226void DomDistillerService::CancelTask(TaskTracker* task) {
227  TaskList::iterator it = std::find(tasks_.begin(), tasks_.end(), task);
228  if (it != tasks_.end()) {
229    tasks_.weak_erase(it);
230    base::MessageLoop::current()->DeleteSoon(FROM_HERE, task);
231  }
232}
233
234void DomDistillerService::AddDistilledPageToList(
235    const ArticleEntry& entry,
236    const DistilledArticleProto* article_proto,
237    bool distillation_succeeded) {
238  DCHECK(IsEntryValid(entry));
239  if (distillation_succeeded) {
240    DCHECK(article_proto);
241    DCHECK_GT(article_proto->pages_size(), 0);
242    store_->AddEntry(entry);
243    DCHECK_EQ(article_proto->pages_size(), entry.pages_size());
244  }
245}
246
247void DomDistillerService::AddObserver(DomDistillerObserver* observer) {
248  DCHECK(observer);
249  store_->AddObserver(observer);
250}
251
252void DomDistillerService::RemoveObserver(DomDistillerObserver* observer) {
253  DCHECK(observer);
254  store_->RemoveObserver(observer);
255}
256
257DistilledPagePrefs* DomDistillerService::GetDistilledPagePrefs() {
258  return distilled_page_prefs_.get();
259}
260
261}  // namespace dom_distiller
262