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/location.h"
9 #include "base/logging.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "components/dom_distiller/core/article_entry.h"
13 #include "sync/api/sync_change.h"
14 #include "sync/protocol/article_specifics.pb.h"
15 #include "sync/protocol/sync.pb.h"
17 using leveldb_proto::ProtoDatabase
;
18 using sync_pb::ArticleSpecifics
;
19 using sync_pb::EntitySpecifics
;
20 using syncer::ModelType
;
21 using syncer::SyncChange
;
22 using syncer::SyncChangeList
;
23 using syncer::SyncData
;
24 using syncer::SyncDataList
;
25 using syncer::SyncError
;
26 using syncer::SyncMergeResult
;
28 namespace dom_distiller
{
30 DomDistillerStore::DomDistillerStore(
31 scoped_ptr
<ProtoDatabase
<ArticleEntry
> > database
,
32 const base::FilePath
& database_dir
)
33 : database_(database
.Pass()),
34 database_loaded_(false),
35 attachment_store_(syncer::AttachmentStore::CreateInMemoryStore()),
36 weak_ptr_factory_(this) {
37 database_
->Init(database_dir
, base::Bind(&DomDistillerStore::OnDatabaseInit
,
38 weak_ptr_factory_
.GetWeakPtr()));
41 DomDistillerStore::DomDistillerStore(
42 scoped_ptr
<ProtoDatabase
<ArticleEntry
> > database
,
43 const std::vector
<ArticleEntry
>& initial_data
,
44 const base::FilePath
& database_dir
)
45 : database_(database
.Pass()),
46 database_loaded_(false),
47 attachment_store_(syncer::AttachmentStore::CreateInMemoryStore()),
49 weak_ptr_factory_(this) {
50 database_
->Init(database_dir
, base::Bind(&DomDistillerStore::OnDatabaseInit
,
51 weak_ptr_factory_
.GetWeakPtr()));
54 DomDistillerStore::~DomDistillerStore() {}
56 // DomDistillerStoreInterface implementation.
57 syncer::SyncableService
* DomDistillerStore::GetSyncableService() {
61 bool DomDistillerStore::GetEntryById(const std::string
& entry_id
,
62 ArticleEntry
* entry
) {
63 return model_
.GetEntryById(entry_id
, entry
);
66 bool DomDistillerStore::GetEntryByUrl(const GURL
& url
, ArticleEntry
* entry
) {
67 return model_
.GetEntryByUrl(url
, entry
);
70 void DomDistillerStore::UpdateAttachments(
71 const std::string
& entry_id
,
72 scoped_ptr
<ArticleAttachmentsData
> attachments_data
,
73 const UpdateAttachmentsCallback
& callback
) {
74 if (!GetEntryById(entry_id
, nullptr)) {
75 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
76 base::Bind(callback
, false));
79 scoped_ptr
<sync_pb::ArticleAttachments
> article_attachments(
80 new sync_pb::ArticleAttachments());
81 syncer::AttachmentList attachment_list
;
82 attachments_data
->CreateSyncAttachments(&attachment_list
,
83 article_attachments
.get());
85 attachment_store_
->Write(
87 base::Bind(&DomDistillerStore::OnAttachmentsWrite
,
88 weak_ptr_factory_
.GetWeakPtr(), entry_id
,
89 base::Passed(&article_attachments
), callback
));
92 void DomDistillerStore::OnAttachmentsWrite(
93 const std::string
& entry_id
,
94 scoped_ptr
<sync_pb::ArticleAttachments
> article_attachments
,
95 const UpdateAttachmentsCallback
& callback
,
96 const syncer::AttachmentStore::Result
& result
) {
99 case syncer::AttachmentStore::UNSPECIFIED_ERROR
:
100 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED
:
102 case syncer::AttachmentStore::SUCCESS
:
109 bool has_entry
= GetEntryById(entry_id
, &entry
);
112 attachment_store_
->Drop(GetAttachmentIds(*article_attachments
),
113 syncer::AttachmentStore::DropCallback());
115 if (entry
.has_attachments()) {
116 attachment_store_
->Drop(GetAttachmentIds(entry
.attachments()),
117 syncer::AttachmentStore::DropCallback());
119 entry
.set_allocated_attachments(article_attachments
.release());
121 SyncChangeList changes_to_apply
;
122 changes_to_apply
.push_back(SyncChange(
123 FROM_HERE
, SyncChange::ACTION_UPDATE
, CreateLocalData(entry
)));
125 SyncChangeList changes_applied
;
126 SyncChangeList changes_missing
;
128 ApplyChangesToModel(changes_to_apply
, &changes_applied
, &changes_missing
);
130 DCHECK_EQ(size_t(0), changes_missing
.size());
131 DCHECK_EQ(size_t(1), changes_applied
.size());
133 ApplyChangesToSync(FROM_HERE
, changes_applied
);
134 ApplyChangesToDatabase(changes_applied
);
137 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
138 base::Bind(callback
, success
));
141 void DomDistillerStore::GetAttachments(
142 const std::string
& entry_id
,
143 const GetAttachmentsCallback
& callback
) {
145 if (!model_
.GetEntryById(entry_id
, &entry
)
146 || !entry
.has_attachments()) {
147 base::ThreadTaskRunnerHandle::Get()->PostTask(
148 FROM_HERE
, base::Bind(callback
, false, nullptr));
152 // TODO(cjhopman): This should use GetOrDownloadAttachments() once there is a
153 // feasible way to use that.
154 attachment_store_
->Read(GetAttachmentIds(entry
.attachments()),
155 base::Bind(&DomDistillerStore::OnAttachmentsRead
,
156 weak_ptr_factory_
.GetWeakPtr(),
157 entry
.attachments(), callback
));
160 void DomDistillerStore::OnAttachmentsRead(
161 const sync_pb::ArticleAttachments
& attachments_proto
,
162 const GetAttachmentsCallback
& callback
,
163 const syncer::AttachmentStore::Result
& result
,
164 scoped_ptr
<syncer::AttachmentMap
> attachments
,
165 scoped_ptr
<syncer::AttachmentIdList
> missing
) {
166 bool success
= false;
168 case syncer::AttachmentStore::UNSPECIFIED_ERROR
:
169 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED
:
171 case syncer::AttachmentStore::SUCCESS
:
172 DCHECK(missing
->empty());
176 scoped_ptr
<ArticleAttachmentsData
> attachments_data
;
178 attachments_data
= ArticleAttachmentsData::GetFromAttachmentMap(
179 attachments_proto
, *attachments
);
181 base::ThreadTaskRunnerHandle::Get()->PostTask(
183 base::Bind(callback
, success
, base::Passed(&attachments_data
)));
186 bool DomDistillerStore::AddEntry(const ArticleEntry
& entry
) {
187 return ChangeEntry(entry
, SyncChange::ACTION_ADD
);
190 bool DomDistillerStore::UpdateEntry(const ArticleEntry
& entry
) {
191 return ChangeEntry(entry
, SyncChange::ACTION_UPDATE
);
194 bool DomDistillerStore::RemoveEntry(const ArticleEntry
& entry
) {
195 return ChangeEntry(entry
, SyncChange::ACTION_DELETE
);
200 bool VerifyAttachmentsUnchanged(const ArticleEntry
& entry
,
201 const DomDistillerModel
& model
) {
202 ArticleEntry currentEntry
;
203 model
.GetEntryById(entry
.entry_id(), ¤tEntry
);
204 DCHECK_EQ(currentEntry
.has_attachments(), entry
.has_attachments());
205 if (currentEntry
.has_attachments()) {
206 DCHECK_EQ(currentEntry
.attachments().SerializeAsString(),
207 entry
.attachments().SerializeAsString());
214 bool DomDistillerStore::ChangeEntry(const ArticleEntry
& entry
,
215 SyncChange::SyncChangeType changeType
) {
216 if (!database_loaded_
) {
220 bool hasEntry
= model_
.GetEntryById(entry
.entry_id(), NULL
);
222 if (changeType
== SyncChange::ACTION_ADD
) {
223 DVLOG(1) << "Already have entry with id " << entry
.entry_id() << ".";
226 DCHECK(VerifyAttachmentsUnchanged(entry
, model_
));
227 } else if (changeType
!= SyncChange::ACTION_ADD
) {
228 DVLOG(1) << "No entry with id " << entry
.entry_id() << " found.";
232 SyncChangeList changes_to_apply
;
233 changes_to_apply
.push_back(
234 SyncChange(FROM_HERE
, changeType
, CreateLocalData(entry
)));
236 SyncChangeList changes_applied
;
237 SyncChangeList changes_missing
;
239 ApplyChangesToModel(changes_to_apply
, &changes_applied
, &changes_missing
);
241 if (changeType
== SyncChange::ACTION_UPDATE
&& changes_applied
.size() != 1) {
242 DVLOG(1) << "Failed to update entry with id " << entry
.entry_id() << ".";
246 DCHECK_EQ(size_t(0), changes_missing
.size());
247 DCHECK_EQ(size_t(1), changes_applied
.size());
249 ApplyChangesToSync(FROM_HERE
, changes_applied
);
250 ApplyChangesToDatabase(changes_applied
);
255 void DomDistillerStore::AddObserver(DomDistillerObserver
* observer
) {
256 observers_
.AddObserver(observer
);
259 void DomDistillerStore::RemoveObserver(DomDistillerObserver
* observer
) {
260 observers_
.RemoveObserver(observer
);
263 std::vector
<ArticleEntry
> DomDistillerStore::GetEntries() const {
264 return model_
.GetEntries();
267 // syncer::SyncableService implementation.
268 SyncMergeResult
DomDistillerStore::MergeDataAndStartSyncing(
269 ModelType type
, const SyncDataList
& initial_sync_data
,
270 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
271 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
272 DCHECK_EQ(syncer::ARTICLES
, type
);
273 DCHECK(!sync_processor_
);
274 DCHECK(!error_factory_
);
275 sync_processor_
.reset(sync_processor
.release());
276 error_factory_
.reset(error_handler
.release());
278 SyncChangeList database_changes
;
279 SyncChangeList sync_changes
;
280 SyncMergeResult result
=
281 MergeDataWithModel(initial_sync_data
, &database_changes
, &sync_changes
);
282 ApplyChangesToDatabase(database_changes
);
283 ApplyChangesToSync(FROM_HERE
, sync_changes
);
288 void DomDistillerStore::StopSyncing(ModelType type
) {
289 sync_processor_
.reset();
290 error_factory_
.reset();
293 SyncDataList
DomDistillerStore::GetAllSyncData(ModelType type
) const {
294 return model_
.GetAllSyncData();
297 SyncError
DomDistillerStore::ProcessSyncChanges(
298 const tracked_objects::Location
& from_here
,
299 const SyncChangeList
& change_list
) {
300 DCHECK(database_loaded_
);
301 SyncChangeList database_changes
;
302 SyncChangeList sync_changes
;
303 ApplyChangesToModel(change_list
, &database_changes
, &sync_changes
);
304 ApplyChangesToDatabase(database_changes
);
305 DCHECK_EQ(size_t(0), sync_changes
.size());
309 void DomDistillerStore::NotifyObservers(const syncer::SyncChangeList
& changes
) {
310 if (observers_
.might_have_observers() && changes
.size() > 0) {
311 std::vector
<DomDistillerObserver::ArticleUpdate
> article_changes
;
312 for (SyncChangeList::const_iterator it
= changes
.begin();
313 it
!= changes
.end(); ++it
) {
314 DomDistillerObserver::ArticleUpdate article_update
;
315 switch (it
->change_type()) {
316 case SyncChange::ACTION_ADD
:
317 article_update
.update_type
= DomDistillerObserver::ArticleUpdate::ADD
;
319 case SyncChange::ACTION_UPDATE
:
320 article_update
.update_type
=
321 DomDistillerObserver::ArticleUpdate::UPDATE
;
323 case SyncChange::ACTION_DELETE
:
324 article_update
.update_type
=
325 DomDistillerObserver::ArticleUpdate::REMOVE
;
327 case SyncChange::ACTION_INVALID
:
331 const ArticleEntry
& entry
= GetEntryFromChange(*it
);
332 article_update
.entry_id
= entry
.entry_id();
333 article_changes
.push_back(article_update
);
335 FOR_EACH_OBSERVER(DomDistillerObserver
, observers_
,
336 ArticleEntriesUpdated(article_changes
));
340 void DomDistillerStore::ApplyChangesToModel(const SyncChangeList
& changes
,
341 SyncChangeList
* changes_applied
,
342 SyncChangeList
* changes_missing
) {
343 model_
.ApplyChangesToModel(changes
, changes_applied
, changes_missing
);
344 NotifyObservers(*changes_applied
);
347 void DomDistillerStore::OnDatabaseInit(bool success
) {
349 DVLOG(1) << "DOM Distiller database init failed.";
353 database_
->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad
,
354 weak_ptr_factory_
.GetWeakPtr()));
357 void DomDistillerStore::OnDatabaseLoad(bool success
,
358 scoped_ptr
<EntryVector
> entries
) {
360 DVLOG(1) << "DOM Distiller database load failed.";
364 database_loaded_
= true;
367 for (EntryVector::iterator it
= entries
->begin(); it
!= entries
->end();
369 data
.push_back(CreateLocalData(*it
));
371 SyncChangeList changes_applied
;
372 SyncChangeList database_changes_needed
;
373 MergeDataWithModel(data
, &changes_applied
, &database_changes_needed
);
374 ApplyChangesToDatabase(database_changes_needed
);
375 ApplyChangesToSync(FROM_HERE
, changes_applied
);
378 void DomDistillerStore::OnDatabaseSave(bool success
) {
380 DVLOG(1) << "DOM Distiller database save failed."
381 << " Disabling modifications and sync.";
383 database_loaded_
= false;
384 StopSyncing(syncer::ARTICLES
);
388 bool DomDistillerStore::ApplyChangesToSync(
389 const tracked_objects::Location
& from_here
,
390 const SyncChangeList
& change_list
) {
391 if (!sync_processor_
) {
394 if (change_list
.empty()) {
398 SyncError error
= sync_processor_
->ProcessSyncChanges(from_here
, change_list
);
400 StopSyncing(syncer::ARTICLES
);
406 bool DomDistillerStore::ApplyChangesToDatabase(
407 const SyncChangeList
& change_list
) {
408 if (!database_loaded_
) {
411 if (change_list
.empty()) {
414 scoped_ptr
<ProtoDatabase
<ArticleEntry
>::KeyEntryVector
> entries_to_save(
415 new ProtoDatabase
<ArticleEntry
>::KeyEntryVector());
416 scoped_ptr
<std::vector
<std::string
> > keys_to_remove(
417 new std::vector
<std::string
>());
419 for (SyncChangeList::const_iterator it
= change_list
.begin();
420 it
!= change_list
.end(); ++it
) {
421 if (it
->change_type() == SyncChange::ACTION_DELETE
) {
422 ArticleEntry entry
= GetEntryFromChange(*it
);
423 keys_to_remove
->push_back(entry
.entry_id());
425 ArticleEntry entry
= GetEntryFromChange(*it
);
426 entries_to_save
->push_back(std::make_pair(entry
.entry_id(), entry
));
429 database_
->UpdateEntries(entries_to_save
.Pass(), keys_to_remove
.Pass(),
430 base::Bind(&DomDistillerStore::OnDatabaseSave
,
431 weak_ptr_factory_
.GetWeakPtr()));
435 SyncMergeResult
DomDistillerStore::MergeDataWithModel(
436 const SyncDataList
& data
, SyncChangeList
* changes_applied
,
437 SyncChangeList
* changes_missing
) {
438 // TODO(cjhopman): This naive merge algorithm could cause flip-flopping
439 // between database/sync of multiple clients.
440 DCHECK(changes_applied
);
441 DCHECK(changes_missing
);
443 SyncMergeResult
result(syncer::ARTICLES
);
444 result
.set_num_items_before_association(model_
.GetNumEntries());
446 SyncChangeList changes_to_apply
;
447 model_
.CalculateChangesForMerge(data
, &changes_to_apply
, changes_missing
);
449 ApplyChangesToModel(changes_to_apply
, changes_applied
, changes_missing
);
452 int num_modified
= 0;
453 for (SyncChangeList::const_iterator it
= changes_applied
->begin();
454 it
!= changes_applied
->end(); ++it
) {
455 DCHECK(it
->IsValid());
456 switch (it
->change_type()) {
457 case SyncChange::ACTION_ADD
:
460 case SyncChange::ACTION_UPDATE
:
467 result
.set_num_items_added(num_added
);
468 result
.set_num_items_modified(num_modified
);
469 result
.set_num_items_deleted(0);
471 result
.set_pre_association_version(0);
472 result
.set_num_items_after_association(model_
.GetNumEntries());
473 result
.set_error(error
);
478 } // namespace dom_distiller