Don't preload rarely seen large images
[chromium-blink-merge.git] / components / dom_distiller / core / dom_distiller_store.cc
blob6d87e311fe19231b9bb7b2d7d7a341afa2cb2409
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/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()),
48 model_(initial_data),
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() {
58 return this;
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(
86 attachment_list,
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) {
97 bool success = false;
98 switch (result) {
99 case syncer::AttachmentStore::UNSPECIFIED_ERROR:
100 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED:
101 break;
102 case syncer::AttachmentStore::SUCCESS:
103 success = true;
104 break;
107 if (success) {
108 ArticleEntry entry;
109 bool has_entry = GetEntryById(entry_id, &entry);
110 if (!has_entry) {
111 success = false;
112 attachment_store_->Drop(GetAttachmentIds(*article_attachments),
113 syncer::AttachmentStore::DropCallback());
114 } else {
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) {
144 ArticleEntry entry;
145 if (!model_.GetEntryById(entry_id, &entry)
146 || !entry.has_attachments()) {
147 base::ThreadTaskRunnerHandle::Get()->PostTask(
148 FROM_HERE, base::Bind(callback, false, nullptr));
149 return;
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;
167 switch (result) {
168 case syncer::AttachmentStore::UNSPECIFIED_ERROR:
169 case syncer::AttachmentStore::STORE_INITIALIZATION_FAILED:
170 break;
171 case syncer::AttachmentStore::SUCCESS:
172 DCHECK(missing->empty());
173 success = true;
174 break;
176 scoped_ptr<ArticleAttachmentsData> attachments_data;
177 if (success) {
178 attachments_data = ArticleAttachmentsData::GetFromAttachmentMap(
179 attachments_proto, *attachments);
181 base::ThreadTaskRunnerHandle::Get()->PostTask(
182 FROM_HERE,
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);
198 namespace {
200 bool VerifyAttachmentsUnchanged(const ArticleEntry& entry,
201 const DomDistillerModel& model) {
202 ArticleEntry currentEntry;
203 model.GetEntryById(entry.entry_id(), &currentEntry);
204 DCHECK_EQ(currentEntry.has_attachments(), entry.has_attachments());
205 if (currentEntry.has_attachments()) {
206 DCHECK_EQ(currentEntry.attachments().SerializeAsString(),
207 entry.attachments().SerializeAsString());
209 return true;
212 } // namespace
214 bool DomDistillerStore::ChangeEntry(const ArticleEntry& entry,
215 SyncChange::SyncChangeType changeType) {
216 if (!database_loaded_) {
217 return false;
220 bool hasEntry = model_.GetEntryById(entry.entry_id(), NULL);
221 if (hasEntry) {
222 if (changeType == SyncChange::ACTION_ADD) {
223 DVLOG(1) << "Already have entry with id " << entry.entry_id() << ".";
224 return false;
226 DCHECK(VerifyAttachmentsUnchanged(entry, model_));
227 } else if (changeType != SyncChange::ACTION_ADD) {
228 DVLOG(1) << "No entry with id " << entry.entry_id() << " found.";
229 return false;
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() << ".";
243 return false;
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);
252 return true;
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);
285 return result;
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());
306 return SyncError();
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;
318 break;
319 case SyncChange::ACTION_UPDATE:
320 article_update.update_type =
321 DomDistillerObserver::ArticleUpdate::UPDATE;
322 break;
323 case SyncChange::ACTION_DELETE:
324 article_update.update_type =
325 DomDistillerObserver::ArticleUpdate::REMOVE;
326 break;
327 case SyncChange::ACTION_INVALID:
328 NOTREACHED();
329 break;
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) {
348 if (!success) {
349 DVLOG(1) << "DOM Distiller database init failed.";
350 database_.reset();
351 return;
353 database_->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad,
354 weak_ptr_factory_.GetWeakPtr()));
357 void DomDistillerStore::OnDatabaseLoad(bool success,
358 scoped_ptr<EntryVector> entries) {
359 if (!success) {
360 DVLOG(1) << "DOM Distiller database load failed.";
361 database_.reset();
362 return;
364 database_loaded_ = true;
366 SyncDataList data;
367 for (EntryVector::iterator it = entries->begin(); it != entries->end();
368 ++it) {
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) {
379 if (!success) {
380 DVLOG(1) << "DOM Distiller database save failed."
381 << " Disabling modifications and sync.";
382 database_.reset();
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_) {
392 return false;
394 if (change_list.empty()) {
395 return true;
398 SyncError error = sync_processor_->ProcessSyncChanges(from_here, change_list);
399 if (error.IsSet()) {
400 StopSyncing(syncer::ARTICLES);
401 return false;
403 return true;
406 bool DomDistillerStore::ApplyChangesToDatabase(
407 const SyncChangeList& change_list) {
408 if (!database_loaded_) {
409 return false;
411 if (change_list.empty()) {
412 return true;
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());
424 } else {
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()));
432 return true;
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);
448 SyncError error;
449 ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing);
451 int num_added = 0;
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:
458 num_added++;
459 break;
460 case SyncChange::ACTION_UPDATE:
461 num_modified++;
462 break;
463 default:
464 NOTREACHED();
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);
475 return result;
478 } // namespace dom_distiller