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_model.h"
6
7#include <utility>
8
9using syncer::SyncChange;
10using syncer::SyncChangeList;
11using syncer::SyncData;
12using syncer::SyncDataList;
13
14namespace dom_distiller {
15
16DomDistillerModel::DomDistillerModel()
17    : next_key_(1) {}
18
19DomDistillerModel::DomDistillerModel(
20    const std::vector<ArticleEntry>& initial_data)
21    : next_key_(1) {
22  for (size_t i = 0; i < initial_data.size(); ++i) {
23    AddEntry(initial_data[i]);
24  }
25}
26
27DomDistillerModel::~DomDistillerModel() {}
28
29bool DomDistillerModel::GetEntryById(const std::string& entry_id,
30                                     ArticleEntry* entry) const {
31  KeyType key = 0;
32  if (!GetKeyById(entry_id, &key)) {
33    return false;
34  }
35  GetEntryByKey(key, entry);
36  return true;
37}
38
39bool DomDistillerModel::GetEntryByUrl(const GURL& url,
40                                     ArticleEntry* entry) const {
41  KeyType key = 0;
42  if (!GetKeyByUrl(url, &key)) {
43    return false;
44  }
45  GetEntryByKey(key, entry);
46  return true;
47}
48
49bool DomDistillerModel::GetKeyById(const std::string& entry_id,
50                                   KeyType* key) const {
51  StringToKeyMap::const_iterator it = entry_id_to_key_map_.find(entry_id);
52  if (it == entry_id_to_key_map_.end()) {
53    return false;
54  }
55  if (key != NULL) {
56    *key = it->second;
57  }
58  return true;
59}
60
61bool DomDistillerModel::GetKeyByUrl(const GURL& url, KeyType* key) const {
62  StringToKeyMap::const_iterator it = url_to_key_map_.find(url.spec());
63  if (it == url_to_key_map_.end()) {
64    return false;
65  }
66  if (key != NULL) {
67    *key = it->second;
68  }
69  return true;
70}
71
72void DomDistillerModel::GetEntryByKey(KeyType key, ArticleEntry* entry) const {
73  if (entry != NULL) {
74    EntryMap::const_iterator it = entries_.find(key);
75    DCHECK(it != entries_.end());
76    *entry = it->second;
77  }
78}
79
80size_t DomDistillerModel::GetNumEntries() const {
81  return entries_.size();
82}
83
84std::vector<ArticleEntry> DomDistillerModel::GetEntries() const {
85  std::vector<ArticleEntry> entries_list;
86  for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end();
87       ++it) {
88    entries_list.push_back(it->second);
89  }
90  return entries_list;
91}
92
93SyncDataList DomDistillerModel::GetAllSyncData() const {
94  SyncDataList data;
95  for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end();
96       ++it) {
97    data.push_back(CreateLocalData(it->second));
98  }
99  return data;
100}
101
102void DomDistillerModel::CalculateChangesForMerge(
103    const SyncDataList& data,
104    SyncChangeList* changes_to_apply,
105    SyncChangeList* changes_missing) {
106  typedef base::hash_set<std::string> StringSet;
107  StringSet entries_to_change;
108  for (SyncDataList::const_iterator it = data.begin(); it != data.end(); ++it) {
109    std::string entry_id = GetEntryIdFromSyncData(*it);
110    std::pair<StringSet::iterator, bool> insert_result =
111        entries_to_change.insert(entry_id);
112
113    DCHECK(insert_result.second);
114
115    SyncChange::SyncChangeType change_type = SyncChange::ACTION_ADD;
116    if (GetEntryById(entry_id, NULL)) {
117      change_type = SyncChange::ACTION_UPDATE;
118    }
119    changes_to_apply->push_back(SyncChange(FROM_HERE, change_type, *it));
120  }
121
122  for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end();
123       ++it) {
124    if (entries_to_change.find(it->second.entry_id()) ==
125        entries_to_change.end()) {
126      changes_missing->push_back(SyncChange(
127          FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(it->second)));
128    }
129  }
130}
131
132void DomDistillerModel::ApplyChangesToModel(
133    const SyncChangeList& changes,
134    SyncChangeList* changes_applied,
135    SyncChangeList* changes_missing) {
136  DCHECK(changes_applied);
137  DCHECK(changes_missing);
138
139  for (SyncChangeList::const_iterator it = changes.begin(); it != changes.end();
140       ++it) {
141    ApplyChangeToModel(*it, changes_applied, changes_missing);
142  }
143}
144
145void DomDistillerModel::AddEntry(const ArticleEntry& entry) {
146  const std::string& entry_id = entry.entry_id();
147  KeyType key = next_key_++;
148  DCHECK(!GetKeyById(entry_id, NULL));
149  entries_.insert(std::make_pair(key, entry));
150  entry_id_to_key_map_.insert(std::make_pair(entry_id, key));
151  for (int i = 0; i < entry.pages_size(); ++i) {
152    url_to_key_map_.insert(std::make_pair(entry.pages(i).url(), key));
153  }
154}
155
156void DomDistillerModel::RemoveEntry(const ArticleEntry& entry) {
157  const std::string& entry_id = entry.entry_id();
158  KeyType key = 0;
159  bool success = GetKeyById(entry_id, &key);
160  DCHECK(success);
161
162  entries_.erase(key);
163  entry_id_to_key_map_.erase(entry_id);
164  for (int i = 0; i < entry.pages_size(); ++i) {
165    url_to_key_map_.erase(entry.pages(i).url());
166  }
167}
168
169void DomDistillerModel::ApplyChangeToModel(
170    const SyncChange& change,
171    SyncChangeList* changes_applied,
172    SyncChangeList* changes_missing) {
173  DCHECK(change.IsValid());
174  DCHECK(changes_applied);
175  DCHECK(changes_missing);
176
177  const std::string& entry_id = GetEntryIdFromSyncData(change.sync_data());
178
179  if (change.change_type() == SyncChange::ACTION_DELETE) {
180    ArticleEntry current_entry;
181    if (GetEntryById(entry_id, &current_entry)) {
182      RemoveEntry(current_entry);
183      changes_applied->push_back(SyncChange(
184          change.location(), SyncChange::ACTION_DELETE, change.sync_data()));
185    }
186    // If we couldn't find in sync db, we were deleting anyway so swallow the
187    // error.
188    return;
189  }
190
191  ArticleEntry entry = GetEntryFromChange(change);
192  ArticleEntry current_entry;
193  if (!GetEntryById(entry_id, &current_entry)) {
194    AddEntry(entry);
195    changes_applied->push_back(SyncChange(
196        change.location(), SyncChange::ACTION_ADD, change.sync_data()));
197  } else {
198    if (!AreEntriesEqual(current_entry, entry)) {
199      // Currently, conflicts are simply resolved by accepting the last one to
200      // arrive.
201      RemoveEntry(current_entry);
202      AddEntry(entry);
203      changes_applied->push_back(SyncChange(
204          change.location(), SyncChange::ACTION_UPDATE, change.sync_data()));
205    }
206  }
207}
208
209}  // namespace dom_distiller
210