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_store.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "components/dom_distiller/core/article_entry.h"
10#include "sync/api/sync_change.h"
11#include "sync/protocol/article_specifics.pb.h"
12#include "sync/protocol/sync.pb.h"
13
14using leveldb_proto::ProtoDatabase;
15using sync_pb::ArticleSpecifics;
16using sync_pb::EntitySpecifics;
17using syncer::ModelType;
18using syncer::SyncChange;
19using syncer::SyncChangeList;
20using syncer::SyncData;
21using syncer::SyncDataList;
22using syncer::SyncError;
23using syncer::SyncMergeResult;
24
25namespace dom_distiller {
26
27DomDistillerStore::DomDistillerStore(
28    scoped_ptr<ProtoDatabase<ArticleEntry> > database,
29    const base::FilePath& database_dir)
30    : database_(database.Pass()),
31      database_loaded_(false),
32      weak_ptr_factory_(this) {
33  database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit,
34                                           weak_ptr_factory_.GetWeakPtr()));
35}
36
37DomDistillerStore::DomDistillerStore(
38    scoped_ptr<ProtoDatabase<ArticleEntry> > database,
39    const std::vector<ArticleEntry>& initial_data,
40    const base::FilePath& database_dir)
41    : database_(database.Pass()),
42      database_loaded_(false),
43      model_(initial_data),
44      weak_ptr_factory_(this) {
45  database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit,
46                                           weak_ptr_factory_.GetWeakPtr()));
47}
48
49DomDistillerStore::~DomDistillerStore() {}
50
51// DomDistillerStoreInterface implementation.
52syncer::SyncableService* DomDistillerStore::GetSyncableService() {
53  return this;
54}
55
56bool DomDistillerStore::GetEntryById(const std::string& entry_id,
57                                     ArticleEntry* entry) {
58  return model_.GetEntryById(entry_id, entry);
59}
60
61bool DomDistillerStore::GetEntryByUrl(const GURL& url, ArticleEntry* entry) {
62  return model_.GetEntryByUrl(url, entry);
63}
64
65bool DomDistillerStore::AddEntry(const ArticleEntry& entry) {
66  if (!database_loaded_) {
67    return false;
68  }
69
70  if (model_.GetEntryById(entry.entry_id(), NULL)) {
71    DVLOG(1) << "Already have entry with id " << entry.entry_id() << ".";
72    return false;
73  }
74
75  SyncChangeList changes_to_apply;
76  changes_to_apply.push_back(
77      SyncChange(FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(entry)));
78
79  SyncChangeList changes_applied;
80  SyncChangeList changes_missing;
81
82  ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
83
84  DCHECK_EQ(size_t(0), changes_missing.size());
85  DCHECK_EQ(size_t(1), changes_applied.size());
86
87  ApplyChangesToSync(FROM_HERE, changes_applied);
88  ApplyChangesToDatabase(changes_applied);
89
90  return true;
91}
92
93bool DomDistillerStore::UpdateEntry(const ArticleEntry& entry) {
94  if (!database_loaded_) {
95    return false;
96  }
97
98  if (!model_.GetEntryById(entry.entry_id(), NULL)) {
99    DVLOG(1) << "No entry with id " << entry.entry_id() << " found.";
100    return false;
101  }
102
103  SyncChangeList changes_to_apply;
104  changes_to_apply.push_back(
105      SyncChange(FROM_HERE, SyncChange::ACTION_UPDATE, CreateLocalData(entry)));
106
107  SyncChangeList changes_applied;
108  SyncChangeList changes_missing;
109
110  ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
111
112  if (changes_applied.size() != 1) {
113    DVLOG(1) << "Failed to update entry with id " << entry.entry_id() << ".";
114    return false;
115  }
116
117  ApplyChangesToSync(FROM_HERE, changes_applied);
118  ApplyChangesToDatabase(changes_applied);
119
120  return true;
121}
122
123bool DomDistillerStore::RemoveEntry(const ArticleEntry& entry) {
124  if (!database_loaded_) {
125    return false;
126  }
127
128  if (!model_.GetEntryById(entry.entry_id(), NULL)) {
129    DVLOG(1) << "No entry with id " << entry.entry_id() << " found.";
130    return false;
131  }
132
133  SyncChangeList changes_to_apply;
134  changes_to_apply.push_back(
135      SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, CreateLocalData(entry)));
136
137  SyncChangeList changes_applied;
138  SyncChangeList changes_missing;
139
140  ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
141
142  DCHECK_EQ(size_t(0), changes_missing.size());
143  DCHECK_EQ(size_t(1), changes_applied.size());
144
145  ApplyChangesToSync(FROM_HERE, changes_applied);
146  ApplyChangesToDatabase(changes_applied);
147
148  return true;
149}
150
151void DomDistillerStore::AddObserver(DomDistillerObserver* observer) {
152  observers_.AddObserver(observer);
153}
154
155void DomDistillerStore::RemoveObserver(DomDistillerObserver* observer) {
156  observers_.RemoveObserver(observer);
157}
158
159std::vector<ArticleEntry> DomDistillerStore::GetEntries() const {
160  return model_.GetEntries();
161}
162
163// syncer::SyncableService implementation.
164SyncMergeResult DomDistillerStore::MergeDataAndStartSyncing(
165    ModelType type, const SyncDataList& initial_sync_data,
166    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
167    scoped_ptr<syncer::SyncErrorFactory> error_handler) {
168  DCHECK_EQ(syncer::ARTICLES, type);
169  DCHECK(!sync_processor_);
170  DCHECK(!error_factory_);
171  sync_processor_.reset(sync_processor.release());
172  error_factory_.reset(error_handler.release());
173
174  SyncChangeList database_changes;
175  SyncChangeList sync_changes;
176  SyncMergeResult result =
177      MergeDataWithModel(initial_sync_data, &database_changes, &sync_changes);
178  ApplyChangesToDatabase(database_changes);
179  ApplyChangesToSync(FROM_HERE, sync_changes);
180
181  return result;
182}
183
184void DomDistillerStore::StopSyncing(ModelType type) {
185  sync_processor_.reset();
186  error_factory_.reset();
187}
188
189SyncDataList DomDistillerStore::GetAllSyncData(ModelType type) const {
190  return model_.GetAllSyncData();
191}
192
193SyncError DomDistillerStore::ProcessSyncChanges(
194    const tracked_objects::Location& from_here,
195    const SyncChangeList& change_list) {
196  DCHECK(database_loaded_);
197  SyncChangeList database_changes;
198  SyncChangeList sync_changes;
199  ApplyChangesToModel(change_list, &database_changes, &sync_changes);
200  ApplyChangesToDatabase(database_changes);
201  DCHECK_EQ(size_t(0), sync_changes.size());
202  return SyncError();
203}
204
205void DomDistillerStore::NotifyObservers(const syncer::SyncChangeList& changes) {
206  if (observers_.might_have_observers() && changes.size() > 0) {
207    std::vector<DomDistillerObserver::ArticleUpdate> article_changes;
208    for (SyncChangeList::const_iterator it = changes.begin();
209         it != changes.end(); ++it) {
210      DomDistillerObserver::ArticleUpdate article_update;
211      switch (it->change_type()) {
212        case SyncChange::ACTION_ADD:
213          article_update.update_type = DomDistillerObserver::ArticleUpdate::ADD;
214          break;
215        case SyncChange::ACTION_UPDATE:
216          article_update.update_type =
217              DomDistillerObserver::ArticleUpdate::UPDATE;
218          break;
219        case SyncChange::ACTION_DELETE:
220          article_update.update_type =
221              DomDistillerObserver::ArticleUpdate::REMOVE;
222          break;
223        case SyncChange::ACTION_INVALID:
224          NOTREACHED();
225          break;
226      }
227      const ArticleEntry& entry = GetEntryFromChange(*it);
228      article_update.entry_id = entry.entry_id();
229      article_changes.push_back(article_update);
230    }
231    FOR_EACH_OBSERVER(DomDistillerObserver, observers_,
232                      ArticleEntriesUpdated(article_changes));
233  }
234}
235
236void DomDistillerStore::ApplyChangesToModel(const SyncChangeList& changes,
237                                            SyncChangeList* changes_applied,
238                                            SyncChangeList* changes_missing) {
239  model_.ApplyChangesToModel(changes, changes_applied, changes_missing);
240  NotifyObservers(*changes_applied);
241}
242
243void DomDistillerStore::OnDatabaseInit(bool success) {
244  if (!success) {
245    DVLOG(1) << "DOM Distiller database init failed.";
246    database_.reset();
247    return;
248  }
249  database_->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad,
250                                    weak_ptr_factory_.GetWeakPtr()));
251}
252
253void DomDistillerStore::OnDatabaseLoad(bool success,
254                                       scoped_ptr<EntryVector> entries) {
255  if (!success) {
256    DVLOG(1) << "DOM Distiller database load failed.";
257    database_.reset();
258    return;
259  }
260  database_loaded_ = true;
261
262  SyncDataList data;
263  for (EntryVector::iterator it = entries->begin(); it != entries->end();
264       ++it) {
265    data.push_back(CreateLocalData(*it));
266  }
267  SyncChangeList changes_applied;
268  SyncChangeList database_changes_needed;
269  MergeDataWithModel(data, &changes_applied, &database_changes_needed);
270  ApplyChangesToDatabase(database_changes_needed);
271}
272
273void DomDistillerStore::OnDatabaseSave(bool success) {
274  if (!success) {
275    DVLOG(1) << "DOM Distiller database save failed."
276             << " Disabling modifications and sync.";
277    database_.reset();
278    database_loaded_ = false;
279    StopSyncing(syncer::ARTICLES);
280  }
281}
282
283bool DomDistillerStore::ApplyChangesToSync(
284    const tracked_objects::Location& from_here,
285    const SyncChangeList& change_list) {
286  if (!sync_processor_) {
287    return false;
288  }
289  if (change_list.empty()) {
290    return true;
291  }
292
293  SyncError error = sync_processor_->ProcessSyncChanges(from_here, change_list);
294  if (error.IsSet()) {
295    StopSyncing(syncer::ARTICLES);
296    return false;
297  }
298  return true;
299}
300
301bool DomDistillerStore::ApplyChangesToDatabase(
302    const SyncChangeList& change_list) {
303  if (!database_loaded_) {
304    return false;
305  }
306  if (change_list.empty()) {
307    return true;
308  }
309  scoped_ptr<ProtoDatabase<ArticleEntry>::KeyEntryVector> entries_to_save(
310      new ProtoDatabase<ArticleEntry>::KeyEntryVector());
311  scoped_ptr<std::vector<std::string> > keys_to_remove(
312      new std::vector<std::string>());
313
314  for (SyncChangeList::const_iterator it = change_list.begin();
315       it != change_list.end(); ++it) {
316    if (it->change_type() == SyncChange::ACTION_DELETE) {
317      ArticleEntry entry = GetEntryFromChange(*it);
318      keys_to_remove->push_back(entry.entry_id());
319    } else {
320      ArticleEntry entry = GetEntryFromChange(*it);
321      entries_to_save->push_back(std::make_pair(entry.entry_id(), entry));
322    }
323  }
324  database_->UpdateEntries(entries_to_save.Pass(), keys_to_remove.Pass(),
325                           base::Bind(&DomDistillerStore::OnDatabaseSave,
326                                      weak_ptr_factory_.GetWeakPtr()));
327  return true;
328}
329
330SyncMergeResult DomDistillerStore::MergeDataWithModel(
331    const SyncDataList& data, SyncChangeList* changes_applied,
332    SyncChangeList* changes_missing) {
333  DCHECK(changes_applied);
334  DCHECK(changes_missing);
335
336  SyncMergeResult result(syncer::ARTICLES);
337  result.set_num_items_before_association(model_.GetNumEntries());
338
339  SyncChangeList changes_to_apply;
340  model_.CalculateChangesForMerge(data, &changes_to_apply, changes_missing);
341  SyncError error;
342  ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing);
343
344  int num_added = 0;
345  int num_modified = 0;
346  for (SyncChangeList::const_iterator it = changes_applied->begin();
347       it != changes_applied->end(); ++it) {
348    DCHECK(it->IsValid());
349    switch (it->change_type()) {
350      case SyncChange::ACTION_ADD:
351        num_added++;
352        break;
353      case SyncChange::ACTION_UPDATE:
354        num_modified++;
355        break;
356      default:
357        NOTREACHED();
358    }
359  }
360  result.set_num_items_added(num_added);
361  result.set_num_items_modified(num_modified);
362  result.set_num_items_deleted(0);
363
364  result.set_pre_association_version(0);
365  result.set_num_items_after_association(model_.GetNumEntries());
366  result.set_error(error);
367
368  return result;
369}
370
371}  // namespace dom_distiller
372