Roll src/third_party/skia de7665a:76033be
[chromium-blink-merge.git] / components / dom_distiller / core / dom_distiller_store.cc
blobe189d8cd775f3eb06e189b378573b026f18756a0
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.
5 #include "components/dom_distiller/core/dom_distiller_store.h"
7 #include "base/bind.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "components/dom_distiller/core/article_entry.h"
11 #include "sync/api/sync_change.h"
12 #include "sync/protocol/article_specifics.pb.h"
13 #include "sync/protocol/sync.pb.h"
15 using leveldb_proto::ProtoDatabase;
16 using sync_pb::ArticleSpecifics;
17 using sync_pb::EntitySpecifics;
18 using syncer::ModelType;
19 using syncer::SyncChange;
20 using syncer::SyncChangeList;
21 using syncer::SyncData;
22 using syncer::SyncDataList;
23 using syncer::SyncError;
24 using syncer::SyncMergeResult;
26 namespace dom_distiller {
28 DomDistillerStore::DomDistillerStore(
29 scoped_ptr<ProtoDatabase<ArticleEntry> > database,
30 const base::FilePath& database_dir)
31 : database_(database.Pass()),
32 database_loaded_(false),
33 attachment_store_(syncer::AttachmentStore::CreateInMemoryStore()),
34 weak_ptr_factory_(this) {
35 database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit,
36 weak_ptr_factory_.GetWeakPtr()));
39 DomDistillerStore::DomDistillerStore(
40 scoped_ptr<ProtoDatabase<ArticleEntry> > database,
41 const std::vector<ArticleEntry>& initial_data,
42 const base::FilePath& database_dir)
43 : database_(database.Pass()),
44 database_loaded_(false),
45 attachment_store_(syncer::AttachmentStore::CreateInMemoryStore()),
46 model_(initial_data),
47 weak_ptr_factory_(this) {
48 database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit,
49 weak_ptr_factory_.GetWeakPtr()));
52 DomDistillerStore::~DomDistillerStore() {}
54 // DomDistillerStoreInterface implementation.
55 syncer::SyncableService* DomDistillerStore::GetSyncableService() {
56 return this;
59 bool DomDistillerStore::GetEntryById(const std::string& entry_id,
60 ArticleEntry* entry) {
61 return model_.GetEntryById(entry_id, entry);
64 bool DomDistillerStore::GetEntryByUrl(const GURL& url, ArticleEntry* entry) {
65 return model_.GetEntryByUrl(url, entry);
68 void DomDistillerStore::UpdateAttachments(
69 const std::string& entry_id,
70 scoped_ptr<ArticleAttachmentsData> attachments_data,
71 const UpdateAttachmentsCallback& callback) {
72 if (!GetEntryById(entry_id, nullptr)) {
73 base::MessageLoop::current()->PostTask(FROM_HERE,
74 base::Bind(callback, false));
77 scoped_ptr<sync_pb::ArticleAttachments> article_attachments(
78 new sync_pb::ArticleAttachments());
79 syncer::AttachmentList attachment_list;
80 attachments_data->CreateSyncAttachments(&attachment_list,
81 article_attachments.get());
83 attachment_store_->Write(
84 attachment_list,
85 base::Bind(&DomDistillerStore::OnAttachmentsWrite,
86 weak_ptr_factory_.GetWeakPtr(), entry_id,
87 base::Passed(&article_attachments), callback));
90 void DomDistillerStore::OnAttachmentsWrite(
91 const std::string& entry_id,
92 scoped_ptr<sync_pb::ArticleAttachments> article_attachments,
93 const UpdateAttachmentsCallback& callback,
94 const syncer::AttachmentStore::Result& result) {
95 bool success = false;
96 switch (result) {
97 case syncer::AttachmentStore::UNSPECIFIED_ERROR:
98 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED:
99 break;
100 case syncer::AttachmentStore::SUCCESS:
101 success = true;
102 break;
105 if (success) {
106 ArticleEntry entry;
107 bool has_entry = GetEntryById(entry_id, &entry);
108 if (!has_entry) {
109 success = false;
110 attachment_store_->Drop(GetAttachmentIds(*article_attachments),
111 syncer::AttachmentStore::DropCallback());
112 } else {
113 if (entry.has_attachments()) {
114 attachment_store_->Drop(GetAttachmentIds(entry.attachments()),
115 syncer::AttachmentStore::DropCallback());
117 entry.set_allocated_attachments(article_attachments.release());
119 SyncChangeList changes_to_apply;
120 changes_to_apply.push_back(SyncChange(
121 FROM_HERE, SyncChange::ACTION_UPDATE, CreateLocalData(entry)));
123 SyncChangeList changes_applied;
124 SyncChangeList changes_missing;
126 ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
128 DCHECK_EQ(size_t(0), changes_missing.size());
129 DCHECK_EQ(size_t(1), changes_applied.size());
131 ApplyChangesToSync(FROM_HERE, changes_applied);
132 ApplyChangesToDatabase(changes_applied);
135 base::MessageLoop::current()->PostTask(FROM_HERE,
136 base::Bind(callback, success));
139 void DomDistillerStore::GetAttachments(
140 const std::string& entry_id,
141 const GetAttachmentsCallback& callback) {
142 ArticleEntry entry;
143 if (!model_.GetEntryById(entry_id, &entry)
144 || !entry.has_attachments()) {
145 base::MessageLoop::current()->PostTask(
146 FROM_HERE, base::Bind(callback, false, nullptr));
147 return;
150 // TODO(cjhopman): This should use GetOrDownloadAttachments() once there is a
151 // feasible way to use that.
152 attachment_store_->Read(GetAttachmentIds(entry.attachments()),
153 base::Bind(&DomDistillerStore::OnAttachmentsRead,
154 weak_ptr_factory_.GetWeakPtr(),
155 entry.attachments(), callback));
158 void DomDistillerStore::OnAttachmentsRead(
159 const sync_pb::ArticleAttachments& attachments_proto,
160 const GetAttachmentsCallback& callback,
161 const syncer::AttachmentStore::Result& result,
162 scoped_ptr<syncer::AttachmentMap> attachments,
163 scoped_ptr<syncer::AttachmentIdList> missing) {
164 bool success = false;
165 switch (result) {
166 case syncer::AttachmentStore::UNSPECIFIED_ERROR:
167 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED:
168 break;
169 case syncer::AttachmentStore::SUCCESS:
170 DCHECK(missing->empty());
171 success = true;
172 break;
174 scoped_ptr<ArticleAttachmentsData> attachments_data;
175 if (success) {
176 attachments_data = ArticleAttachmentsData::GetFromAttachmentMap(
177 attachments_proto, *attachments);
179 base::MessageLoop::current()->PostTask(
180 FROM_HERE,
181 base::Bind(callback, success, base::Passed(&attachments_data)));
184 bool DomDistillerStore::AddEntry(const ArticleEntry& entry) {
185 return ChangeEntry(entry, SyncChange::ACTION_ADD);
188 bool DomDistillerStore::UpdateEntry(const ArticleEntry& entry) {
189 return ChangeEntry(entry, SyncChange::ACTION_UPDATE);
192 bool DomDistillerStore::RemoveEntry(const ArticleEntry& entry) {
193 return ChangeEntry(entry, SyncChange::ACTION_DELETE);
196 namespace {
198 bool VerifyAttachmentsUnchanged(const ArticleEntry& entry,
199 const DomDistillerModel& model) {
200 ArticleEntry currentEntry;
201 model.GetEntryById(entry.entry_id(), &currentEntry);
202 DCHECK_EQ(currentEntry.has_attachments(), entry.has_attachments());
203 if (currentEntry.has_attachments()) {
204 DCHECK_EQ(currentEntry.attachments().SerializeAsString(),
205 entry.attachments().SerializeAsString());
207 return true;
210 } // namespace
212 bool DomDistillerStore::ChangeEntry(const ArticleEntry& entry,
213 SyncChange::SyncChangeType changeType) {
214 if (!database_loaded_) {
215 return false;
218 bool hasEntry = model_.GetEntryById(entry.entry_id(), NULL);
219 if (hasEntry) {
220 if (changeType == SyncChange::ACTION_ADD) {
221 DVLOG(1) << "Already have entry with id " << entry.entry_id() << ".";
222 return false;
224 DCHECK(VerifyAttachmentsUnchanged(entry, model_));
225 } else if (changeType != SyncChange::ACTION_ADD) {
226 DVLOG(1) << "No entry with id " << entry.entry_id() << " found.";
227 return false;
230 SyncChangeList changes_to_apply;
231 changes_to_apply.push_back(
232 SyncChange(FROM_HERE, changeType, CreateLocalData(entry)));
234 SyncChangeList changes_applied;
235 SyncChangeList changes_missing;
237 ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
239 if (changeType == SyncChange::ACTION_UPDATE && changes_applied.size() != 1) {
240 DVLOG(1) << "Failed to update entry with id " << entry.entry_id() << ".";
241 return false;
244 DCHECK_EQ(size_t(0), changes_missing.size());
245 DCHECK_EQ(size_t(1), changes_applied.size());
247 ApplyChangesToSync(FROM_HERE, changes_applied);
248 ApplyChangesToDatabase(changes_applied);
250 return true;
253 void DomDistillerStore::AddObserver(DomDistillerObserver* observer) {
254 observers_.AddObserver(observer);
257 void DomDistillerStore::RemoveObserver(DomDistillerObserver* observer) {
258 observers_.RemoveObserver(observer);
261 std::vector<ArticleEntry> DomDistillerStore::GetEntries() const {
262 return model_.GetEntries();
265 // syncer::SyncableService implementation.
266 SyncMergeResult DomDistillerStore::MergeDataAndStartSyncing(
267 ModelType type, const SyncDataList& initial_sync_data,
268 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
269 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
270 DCHECK_EQ(syncer::ARTICLES, type);
271 DCHECK(!sync_processor_);
272 DCHECK(!error_factory_);
273 sync_processor_.reset(sync_processor.release());
274 error_factory_.reset(error_handler.release());
276 SyncChangeList database_changes;
277 SyncChangeList sync_changes;
278 SyncMergeResult result =
279 MergeDataWithModel(initial_sync_data, &database_changes, &sync_changes);
280 ApplyChangesToDatabase(database_changes);
281 ApplyChangesToSync(FROM_HERE, sync_changes);
283 return result;
286 void DomDistillerStore::StopSyncing(ModelType type) {
287 sync_processor_.reset();
288 error_factory_.reset();
291 SyncDataList DomDistillerStore::GetAllSyncData(ModelType type) const {
292 return model_.GetAllSyncData();
295 SyncError DomDistillerStore::ProcessSyncChanges(
296 const tracked_objects::Location& from_here,
297 const SyncChangeList& change_list) {
298 DCHECK(database_loaded_);
299 SyncChangeList database_changes;
300 SyncChangeList sync_changes;
301 ApplyChangesToModel(change_list, &database_changes, &sync_changes);
302 ApplyChangesToDatabase(database_changes);
303 DCHECK_EQ(size_t(0), sync_changes.size());
304 return SyncError();
307 void DomDistillerStore::NotifyObservers(const syncer::SyncChangeList& changes) {
308 if (observers_.might_have_observers() && changes.size() > 0) {
309 std::vector<DomDistillerObserver::ArticleUpdate> article_changes;
310 for (SyncChangeList::const_iterator it = changes.begin();
311 it != changes.end(); ++it) {
312 DomDistillerObserver::ArticleUpdate article_update;
313 switch (it->change_type()) {
314 case SyncChange::ACTION_ADD:
315 article_update.update_type = DomDistillerObserver::ArticleUpdate::ADD;
316 break;
317 case SyncChange::ACTION_UPDATE:
318 article_update.update_type =
319 DomDistillerObserver::ArticleUpdate::UPDATE;
320 break;
321 case SyncChange::ACTION_DELETE:
322 article_update.update_type =
323 DomDistillerObserver::ArticleUpdate::REMOVE;
324 break;
325 case SyncChange::ACTION_INVALID:
326 NOTREACHED();
327 break;
329 const ArticleEntry& entry = GetEntryFromChange(*it);
330 article_update.entry_id = entry.entry_id();
331 article_changes.push_back(article_update);
333 FOR_EACH_OBSERVER(DomDistillerObserver, observers_,
334 ArticleEntriesUpdated(article_changes));
338 void DomDistillerStore::ApplyChangesToModel(const SyncChangeList& changes,
339 SyncChangeList* changes_applied,
340 SyncChangeList* changes_missing) {
341 model_.ApplyChangesToModel(changes, changes_applied, changes_missing);
342 NotifyObservers(*changes_applied);
345 void DomDistillerStore::OnDatabaseInit(bool success) {
346 if (!success) {
347 DVLOG(1) << "DOM Distiller database init failed.";
348 database_.reset();
349 return;
351 database_->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad,
352 weak_ptr_factory_.GetWeakPtr()));
355 void DomDistillerStore::OnDatabaseLoad(bool success,
356 scoped_ptr<EntryVector> entries) {
357 if (!success) {
358 DVLOG(1) << "DOM Distiller database load failed.";
359 database_.reset();
360 return;
362 database_loaded_ = true;
364 SyncDataList data;
365 for (EntryVector::iterator it = entries->begin(); it != entries->end();
366 ++it) {
367 data.push_back(CreateLocalData(*it));
369 SyncChangeList changes_applied;
370 SyncChangeList database_changes_needed;
371 MergeDataWithModel(data, &changes_applied, &database_changes_needed);
372 ApplyChangesToDatabase(database_changes_needed);
373 ApplyChangesToSync(FROM_HERE, changes_applied);
376 void DomDistillerStore::OnDatabaseSave(bool success) {
377 if (!success) {
378 DVLOG(1) << "DOM Distiller database save failed."
379 << " Disabling modifications and sync.";
380 database_.reset();
381 database_loaded_ = false;
382 StopSyncing(syncer::ARTICLES);
386 bool DomDistillerStore::ApplyChangesToSync(
387 const tracked_objects::Location& from_here,
388 const SyncChangeList& change_list) {
389 if (!sync_processor_) {
390 return false;
392 if (change_list.empty()) {
393 return true;
396 SyncError error = sync_processor_->ProcessSyncChanges(from_here, change_list);
397 if (error.IsSet()) {
398 StopSyncing(syncer::ARTICLES);
399 return false;
401 return true;
404 bool DomDistillerStore::ApplyChangesToDatabase(
405 const SyncChangeList& change_list) {
406 if (!database_loaded_) {
407 return false;
409 if (change_list.empty()) {
410 return true;
412 scoped_ptr<ProtoDatabase<ArticleEntry>::KeyEntryVector> entries_to_save(
413 new ProtoDatabase<ArticleEntry>::KeyEntryVector());
414 scoped_ptr<std::vector<std::string> > keys_to_remove(
415 new std::vector<std::string>());
417 for (SyncChangeList::const_iterator it = change_list.begin();
418 it != change_list.end(); ++it) {
419 if (it->change_type() == SyncChange::ACTION_DELETE) {
420 ArticleEntry entry = GetEntryFromChange(*it);
421 keys_to_remove->push_back(entry.entry_id());
422 } else {
423 ArticleEntry entry = GetEntryFromChange(*it);
424 entries_to_save->push_back(std::make_pair(entry.entry_id(), entry));
427 database_->UpdateEntries(entries_to_save.Pass(), keys_to_remove.Pass(),
428 base::Bind(&DomDistillerStore::OnDatabaseSave,
429 weak_ptr_factory_.GetWeakPtr()));
430 return true;
433 SyncMergeResult DomDistillerStore::MergeDataWithModel(
434 const SyncDataList& data, SyncChangeList* changes_applied,
435 SyncChangeList* changes_missing) {
436 // TODO(cjhopman): This naive merge algorithm could cause flip-flopping
437 // between database/sync of multiple clients.
438 DCHECK(changes_applied);
439 DCHECK(changes_missing);
441 SyncMergeResult result(syncer::ARTICLES);
442 result.set_num_items_before_association(model_.GetNumEntries());
444 SyncChangeList changes_to_apply;
445 model_.CalculateChangesForMerge(data, &changes_to_apply, changes_missing);
446 SyncError error;
447 ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing);
449 int num_added = 0;
450 int num_modified = 0;
451 for (SyncChangeList::const_iterator it = changes_applied->begin();
452 it != changes_applied->end(); ++it) {
453 DCHECK(it->IsValid());
454 switch (it->change_type()) {
455 case SyncChange::ACTION_ADD:
456 num_added++;
457 break;
458 case SyncChange::ACTION_UPDATE:
459 num_modified++;
460 break;
461 default:
462 NOTREACHED();
465 result.set_num_items_added(num_added);
466 result.set_num_items_modified(num_modified);
467 result.set_num_items_deleted(0);
469 result.set_pre_association_version(0);
470 result.set_num_items_after_association(model_.GetNumEntries());
471 result.set_error(error);
473 return result;
476 } // namespace dom_distiller