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"
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()),
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() {
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(
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
) {
97 case syncer::AttachmentStore::UNSPECIFIED_ERROR
:
98 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED
:
100 case syncer::AttachmentStore::SUCCESS
:
107 bool has_entry
= GetEntryById(entry_id
, &entry
);
110 attachment_store_
->Drop(GetAttachmentIds(*article_attachments
),
111 syncer::AttachmentStore::DropCallback());
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
) {
143 if (!model_
.GetEntryById(entry_id
, &entry
)
144 || !entry
.has_attachments()) {
145 base::MessageLoop::current()->PostTask(
146 FROM_HERE
, base::Bind(callback
, false, nullptr));
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;
166 case syncer::AttachmentStore::UNSPECIFIED_ERROR
:
167 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED
:
169 case syncer::AttachmentStore::SUCCESS
:
170 DCHECK(missing
->empty());
174 scoped_ptr
<ArticleAttachmentsData
> attachments_data
;
176 attachments_data
= ArticleAttachmentsData::GetFromAttachmentMap(
177 attachments_proto
, *attachments
);
179 base::MessageLoop::current()->PostTask(
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
);
198 bool VerifyAttachmentsUnchanged(const ArticleEntry
& entry
,
199 const DomDistillerModel
& model
) {
200 ArticleEntry currentEntry
;
201 model
.GetEntryById(entry
.entry_id(), ¤tEntry
);
202 DCHECK_EQ(currentEntry
.has_attachments(), entry
.has_attachments());
203 if (currentEntry
.has_attachments()) {
204 DCHECK_EQ(currentEntry
.attachments().SerializeAsString(),
205 entry
.attachments().SerializeAsString());
212 bool DomDistillerStore::ChangeEntry(const ArticleEntry
& entry
,
213 SyncChange::SyncChangeType changeType
) {
214 if (!database_loaded_
) {
218 bool hasEntry
= model_
.GetEntryById(entry
.entry_id(), NULL
);
220 if (changeType
== SyncChange::ACTION_ADD
) {
221 DVLOG(1) << "Already have entry with id " << entry
.entry_id() << ".";
224 DCHECK(VerifyAttachmentsUnchanged(entry
, model_
));
225 } else if (changeType
!= SyncChange::ACTION_ADD
) {
226 DVLOG(1) << "No entry with id " << entry
.entry_id() << " found.";
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() << ".";
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
);
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
);
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());
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
;
317 case SyncChange::ACTION_UPDATE
:
318 article_update
.update_type
=
319 DomDistillerObserver::ArticleUpdate::UPDATE
;
321 case SyncChange::ACTION_DELETE
:
322 article_update
.update_type
=
323 DomDistillerObserver::ArticleUpdate::REMOVE
;
325 case SyncChange::ACTION_INVALID
:
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
) {
347 DVLOG(1) << "DOM Distiller database init failed.";
351 database_
->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad
,
352 weak_ptr_factory_
.GetWeakPtr()));
355 void DomDistillerStore::OnDatabaseLoad(bool success
,
356 scoped_ptr
<EntryVector
> entries
) {
358 DVLOG(1) << "DOM Distiller database load failed.";
362 database_loaded_
= true;
365 for (EntryVector::iterator it
= entries
->begin(); it
!= entries
->end();
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
) {
378 DVLOG(1) << "DOM Distiller database save failed."
379 << " Disabling modifications and sync.";
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_
) {
392 if (change_list
.empty()) {
396 SyncError error
= sync_processor_
->ProcessSyncChanges(from_here
, change_list
);
398 StopSyncing(syncer::ARTICLES
);
404 bool DomDistillerStore::ApplyChangesToDatabase(
405 const SyncChangeList
& change_list
) {
406 if (!database_loaded_
) {
409 if (change_list
.empty()) {
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());
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()));
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
);
447 ApplyChangesToModel(changes_to_apply
, changes_applied
, changes_missing
);
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
:
458 case SyncChange::ACTION_UPDATE
:
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
);
476 } // namespace dom_distiller