1// Copyright 2014 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/distilled_content_store.h"
6
7#include "base/message_loop/message_loop.h"
8
9namespace dom_distiller {
10
11InMemoryContentStore::InMemoryContentStore(const int max_num_entries)
12    : cache_(max_num_entries, CacheDeletor(this)) {
13}
14
15InMemoryContentStore::~InMemoryContentStore() {
16  // Clear the cache before destruction to ensure the CacheDeletor is not called
17  // after InMemoryContentStore has been destroyed.
18  cache_.Clear();
19}
20
21void InMemoryContentStore::SaveContent(
22    const ArticleEntry& entry,
23    const DistilledArticleProto& proto,
24    InMemoryContentStore::SaveCallback callback) {
25  InjectContent(entry, proto);
26  if (!callback.is_null()) {
27    base::MessageLoop::current()->PostTask(FROM_HERE,
28                                           base::Bind(callback, true));
29  }
30}
31
32void InMemoryContentStore::LoadContent(
33    const ArticleEntry& entry,
34    InMemoryContentStore::LoadCallback callback) {
35  if (callback.is_null())
36    return;
37
38  ContentMap::const_iterator it = cache_.Get(entry.entry_id());
39  bool success = it != cache_.end();
40  if (!success) {
41    // Could not find article by entry ID, so try looking it up by URL.
42    for (int i = 0; i < entry.pages_size(); ++i) {
43      UrlMap::const_iterator url_it = url_to_id_.find(entry.pages(i).url());
44      if (url_it != url_to_id_.end()) {
45        it = cache_.Get(url_it->second);
46        success = it != cache_.end();
47        if (success) {
48          break;
49        }
50      }
51    }
52  }
53  scoped_ptr<DistilledArticleProto> distilled_article;
54  if (success) {
55    distilled_article.reset(new DistilledArticleProto(it->second));
56  } else {
57    distilled_article.reset(new DistilledArticleProto());
58  }
59  base::MessageLoop::current()->PostTask(
60      FROM_HERE,
61      base::Bind(callback, success, base::Passed(&distilled_article)));
62}
63
64void InMemoryContentStore::InjectContent(const ArticleEntry& entry,
65                                         const DistilledArticleProto& proto) {
66  cache_.Put(entry.entry_id(), proto);
67  AddUrlToIdMapping(entry, proto);
68}
69
70void InMemoryContentStore::AddUrlToIdMapping(
71    const ArticleEntry& entry,
72    const DistilledArticleProto& proto) {
73  for (int i = 0; i < proto.pages_size(); i++) {
74    const DistilledPageProto& page = proto.pages(i);
75    if (page.has_url()) {
76      url_to_id_[page.url()] = entry.entry_id();
77    }
78  }
79}
80
81void InMemoryContentStore::EraseUrlToIdMapping(
82    const DistilledArticleProto& proto) {
83  for (int i = 0; i < proto.pages_size(); i++) {
84    const DistilledPageProto& page = proto.pages(i);
85    if (page.has_url()) {
86      url_to_id_.erase(page.url());
87    }
88  }
89}
90
91InMemoryContentStore::CacheDeletor::CacheDeletor(InMemoryContentStore* store)
92    : store_(store) {
93}
94
95InMemoryContentStore::CacheDeletor::~CacheDeletor() {
96}
97
98void InMemoryContentStore::CacheDeletor::operator()(
99    const DistilledArticleProto& proto) {
100  // When InMemoryContentStore is deleted, the |store_| pointer becomes invalid,
101  // but since the ContentMap is cleared in the InMemoryContentStore destructor,
102  // this should never be called after the destructor.
103  store_->EraseUrlToIdMapping(proto);
104}
105
106}  // namespace dom_distiller
107