blob: f5a7a7098f3780da6c121cf5d8d260d11d133f48 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/dom_distiller/core/dom_distiller_store.h"
#include "base/bind.h"
#include "base/logging.h"
#include "components/dom_distiller/core/article_entry.h"
#include "sync/api/sync_change.h"
#include "sync/protocol/article_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
using sync_pb::ArticleSpecifics;
using sync_pb::EntitySpecifics;
using syncer::ModelType;
using syncer::SyncChange;
using syncer::SyncChangeList;
using syncer::SyncData;
using syncer::SyncDataList;
using syncer::SyncError;
using syncer::SyncMergeResult;
namespace dom_distiller {
namespace {
std::string GetEntryIdFromSyncData(const SyncData& data) {
const EntitySpecifics& entity = data.GetSpecifics();
DCHECK(entity.has_article());
const ArticleSpecifics& specifics = entity.article();
DCHECK(specifics.has_entry_id());
return specifics.entry_id();
}
SyncData CreateLocalData(const ArticleEntry& entry) {
EntitySpecifics specifics = SpecificsFromEntry(entry);
const std::string& entry_id = entry.entry_id();
return SyncData::CreateLocalData(entry_id, entry_id, specifics);
}
} // namespace
DomDistillerStore::DomDistillerStore(
scoped_ptr<DomDistillerDatabaseInterface> database,
const base::FilePath& database_dir)
: database_(database.Pass()),
database_loaded_(false),
weak_ptr_factory_(this) {
database_->Init(database_dir,
base::Bind(&DomDistillerStore::OnDatabaseInit,
weak_ptr_factory_.GetWeakPtr()));
}
DomDistillerStore::DomDistillerStore(
scoped_ptr<DomDistillerDatabaseInterface> database,
const EntryMap& initial_model,
const base::FilePath& database_dir)
: database_(database.Pass()),
database_loaded_(false),
model_(initial_model),
weak_ptr_factory_(this) {
database_->Init(database_dir,
base::Bind(&DomDistillerStore::OnDatabaseInit,
weak_ptr_factory_.GetWeakPtr()));
}
DomDistillerStore::~DomDistillerStore() {}
// DomDistillerStoreInterface implementation.
syncer::SyncableService* DomDistillerStore::GetSyncableService() {
return this;
}
bool DomDistillerStore::AddEntry(const ArticleEntry& entry) {
if (!database_loaded_) {
return false;
}
if (model_.find(entry.entry_id()) != model_.end()) {
return false;
}
SyncChangeList changes_to_apply;
changes_to_apply.push_back(
SyncChange(FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(entry)));
SyncChangeList changes_applied;
SyncChangeList changes_missing;
ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing);
DCHECK_EQ(size_t(0), changes_missing.size());
DCHECK_EQ(size_t(1), changes_applied.size());
ApplyChangesToSync(FROM_HERE, changes_applied);
ApplyChangesToDatabase(changes_applied);
return true;
}
std::vector<ArticleEntry> DomDistillerStore::GetEntries() const {
std::vector<ArticleEntry> entries;
for (EntryMap::const_iterator it = model_.begin(); it != model_.end(); ++it) {
entries.push_back(it->second);
}
return entries;
}
// syncer::SyncableService implementation.
SyncMergeResult DomDistillerStore::MergeDataAndStartSyncing(
ModelType type,
const SyncDataList& initial_sync_data,
scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
scoped_ptr<syncer::SyncErrorFactory> error_handler) {
DCHECK_EQ(syncer::ARTICLES, type);
DCHECK(!sync_processor_);
DCHECK(!error_factory_);
sync_processor_.reset(sync_processor.release());
error_factory_.reset(error_handler.release());
SyncChangeList database_changes;
SyncChangeList sync_changes;
SyncMergeResult result =
MergeDataWithModel(initial_sync_data, &database_changes, &sync_changes);
ApplyChangesToDatabase(database_changes);
ApplyChangesToSync(FROM_HERE, sync_changes);
return result;
}
void DomDistillerStore::StopSyncing(ModelType type) {
sync_processor_.reset();
error_factory_.reset();
}
SyncDataList DomDistillerStore::GetAllSyncData(ModelType type) const {
SyncDataList data;
for (EntryMap::const_iterator it = model_.begin(); it != model_.end(); ++it) {
data.push_back(CreateLocalData(it->second));
}
return data;
}
SyncError DomDistillerStore::ProcessSyncChanges(
const tracked_objects::Location& from_here,
const SyncChangeList& change_list) {
DCHECK(database_loaded_);
SyncChangeList database_changes;
SyncChangeList sync_changes;
SyncError error =
ApplyChangesToModel(change_list, &database_changes, &sync_changes);
ApplyChangesToDatabase(database_changes);
DCHECK_EQ(size_t(0), sync_changes.size());
return error;
}
ArticleEntry DomDistillerStore::GetEntryFromChange(const SyncChange& change) {
DCHECK(change.IsValid());
DCHECK(change.sync_data().IsValid());
return EntryFromSpecifics(change.sync_data().GetSpecifics());
}
void DomDistillerStore::OnDatabaseInit(bool success) {
if (!success) {
LOG(INFO) << "DOM Distiller database init failed.";
database_.reset();
return;
}
database_->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad,
weak_ptr_factory_.GetWeakPtr()));
}
void DomDistillerStore::OnDatabaseLoad(bool success,
scoped_ptr<EntryVector> entries) {
if (!success) {
LOG(INFO) << "DOM Distiller database load failed.";
database_.reset();
return;
}
database_loaded_ = true;
SyncDataList data;
for (EntryVector::iterator it = entries->begin(); it != entries->end();
++it) {
data.push_back(CreateLocalData(*it));
}
SyncChangeList changes_applied;
SyncChangeList database_changes_needed;
MergeDataWithModel(data, &changes_applied, &database_changes_needed);
ApplyChangesToDatabase(database_changes_needed);
}
void DomDistillerStore::OnDatabaseSave(bool success) {
if (!success) {
LOG(INFO) << "DOM Distiller database save failed."
<< " Disabling modifications and sync.";
database_.reset();
database_loaded_ = false;
StopSyncing(syncer::ARTICLES);
}
}
bool DomDistillerStore::ApplyChangesToSync(
const tracked_objects::Location& from_here,
const SyncChangeList& change_list) {
if (!sync_processor_) {
return false;
}
if (change_list.empty()) {
return true;
}
SyncError error = sync_processor_->ProcessSyncChanges(from_here, change_list);
if (error.IsSet()) {
StopSyncing(syncer::ARTICLES);
return false;
}
return true;
}
bool DomDistillerStore::ApplyChangesToDatabase(
const SyncChangeList& change_list) {
if (!database_loaded_) {
return false;
}
if (change_list.empty()) {
return true;
}
scoped_ptr<EntryVector> entries_to_save(new EntryVector());
for (SyncChangeList::const_iterator it = change_list.begin();
it != change_list.end();
++it) {
entries_to_save->push_back(GetEntryFromChange(*it));
}
database_->SaveEntries(entries_to_save.Pass(),
base::Bind(&DomDistillerStore::OnDatabaseSave,
weak_ptr_factory_.GetWeakPtr()));
return true;
}
void DomDistillerStore::CalculateChangesForMerge(
const SyncDataList& data,
SyncChangeList* changes_to_apply,
SyncChangeList* changes_missing) {
typedef base::hash_set<std::string> StringSet;
StringSet entries_to_change;
for (SyncDataList::const_iterator it = data.begin(); it != data.end(); ++it) {
std::string entry_id = GetEntryIdFromSyncData(*it);
std::pair<StringSet::iterator, bool> insert_result =
entries_to_change.insert(entry_id);
DCHECK(insert_result.second);
SyncChange::SyncChangeType change_type = SyncChange::ACTION_ADD;
EntryMap::const_iterator current = model_.find(entry_id);
if (current != model_.end()) {
change_type = SyncChange::ACTION_UPDATE;
}
changes_to_apply->push_back(SyncChange(FROM_HERE, change_type, *it));
}
for (EntryMap::const_iterator it = model_.begin(); it != model_.end(); ++it) {
if (entries_to_change.find(it->first) == entries_to_change.end()) {
changes_missing->push_back(SyncChange(
FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(it->second)));
}
}
}
SyncMergeResult DomDistillerStore::MergeDataWithModel(
const SyncDataList& data,
SyncChangeList* changes_applied,
SyncChangeList* changes_missing) {
DCHECK(changes_applied);
DCHECK(changes_missing);
SyncMergeResult result(syncer::ARTICLES);
result.set_num_items_before_association(model_.size());
SyncChangeList changes_to_apply;
CalculateChangesForMerge(data, &changes_to_apply, changes_missing);
SyncError error =
ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing);
int num_added = 0;
int num_modified = 0;
for (SyncChangeList::const_iterator it = changes_applied->begin();
it != changes_applied->end();
++it) {
DCHECK(it->IsValid());
switch (it->change_type()) {
case SyncChange::ACTION_ADD:
num_added++;
break;
case SyncChange::ACTION_UPDATE:
num_modified++;
break;
default:
NOTREACHED();
}
}
result.set_num_items_added(num_added);
result.set_num_items_modified(num_modified);
result.set_num_items_deleted(0);
result.set_pre_association_version(0);
result.set_num_items_after_association(model_.size());
result.set_error(error);
return result;
}
SyncError DomDistillerStore::ApplyChangesToModel(
const SyncChangeList& changes,
SyncChangeList* changes_applied,
SyncChangeList* changes_missing) {
DCHECK(changes_applied);
DCHECK(changes_missing);
for (SyncChangeList::const_iterator it = changes.begin(); it != changes.end();
++it) {
ApplyChangeToModel(*it, changes_applied, changes_missing);
}
return SyncError();
}
void DomDistillerStore::ApplyChangeToModel(const SyncChange& change,
SyncChangeList* changes_applied,
SyncChangeList* changes_missing) {
DCHECK(changes_applied);
DCHECK(changes_missing);
DCHECK(change.IsValid());
const std::string& entry_id = GetEntryIdFromSyncData(change.sync_data());
EntryMap::iterator entry_it = model_.find(entry_id);
if (change.change_type() == SyncChange::ACTION_DELETE) {
// TODO(cjhopman): Support delete.
NOTIMPLEMENTED();
StopSyncing(syncer::ARTICLES);
return;
}
ArticleEntry entry = GetEntryFromChange(change);
if (entry_it == model_.end()) {
model_.insert(std::make_pair(entry.entry_id(), entry));
changes_applied->push_back(SyncChange(
change.location(), SyncChange::ACTION_ADD, change.sync_data()));
} else {
if (!AreEntriesEqual(entry_it->second, entry)) {
// Currently, conflicts are simply resolved by accepting the last one to
// arrive.
entry_it->second = entry;
changes_applied->push_back(SyncChange(
change.location(), SyncChange::ACTION_UPDATE, change.sync_data()));
}
}
}
} // namespace dom_distiller