diff options
author | cjhopman@chromium.org <cjhopman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-22 21:17:56 +0000 |
---|---|---|
committer | cjhopman@chromium.org <cjhopman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-22 21:17:56 +0000 |
commit | 96a45d875ae0cb3a26c2260c711e651f363985b9 (patch) | |
tree | 1b82776755cd216f54eabc07560249fad90de198 /components | |
parent | 5fd860aba4fb327e73ce1824a037c83bdb5db1ce (diff) | |
download | chromium_src-96a45d875ae0cb3a26c2260c711e651f363985b9.zip chromium_src-96a45d875ae0cb3a26c2260c711e651f363985b9.tar.gz chromium_src-96a45d875ae0cb3a26c2260c711e651f363985b9.tar.bz2 |
Extract the in-memory model from DomDistillerStore
This moves interaction with the in-memory model from the store to its
own class: DomDistillerModel. This also adds two ways to lookup entries
to both the store and the model, GetEntryById and GetEntryByUrl.
BUG=288015
Review URL: https://codereview.chromium.org/26574002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@230207 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r-- | components/components_tests.gyp | 1 | ||||
-rw-r--r-- | components/dom_distiller.gypi | 2 | ||||
-rw-r--r-- | components/dom_distiller/core/article_entry.cc | 21 | ||||
-rw-r--r-- | components/dom_distiller/core/article_entry.h | 10 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_model.cc | 208 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_model.h | 94 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_model_unittest.cc | 131 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_store.cc | 177 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_store.h | 36 | ||||
-rw-r--r-- | components/dom_distiller/core/dom_distiller_store_unittest.cc | 17 |
10 files changed, 555 insertions, 142 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 98f3901..2b6b22f 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -23,6 +23,7 @@ 'browser_context_keyed_service/dependency_graph_unittest.cc', 'dom_distiller/core/distiller_url_fetcher_unittest.cc', 'dom_distiller/core/dom_distiller_database_unittest.cc', + 'dom_distiller/core/dom_distiller_model_unittest.cc', 'dom_distiller/core/dom_distiller_store_unittest.cc', 'dom_distiller/core/article_entry_unittest.cc', 'json_schema/json_schema_validator_unittest.cc', diff --git a/components/dom_distiller.gypi b/components/dom_distiller.gypi index 4efe807..abece3f 100644 --- a/components/dom_distiller.gypi +++ b/components/dom_distiller.gypi @@ -82,6 +82,8 @@ 'dom_distiller/core/dom_distiller_constants.h', 'dom_distiller/core/dom_distiller_database.cc', 'dom_distiller/core/dom_distiller_database.h', + 'dom_distiller/core/dom_distiller_model.cc', + 'dom_distiller/core/dom_distiller_model.h', 'dom_distiller/core/dom_distiller_service.cc', 'dom_distiller/core/dom_distiller_service.h', 'dom_distiller/core/dom_distiller_store.cc', diff --git a/components/dom_distiller/core/article_entry.cc b/components/dom_distiller/core/article_entry.cc index 2e370a4..1550caf 100644 --- a/components/dom_distiller/core/article_entry.cc +++ b/components/dom_distiller/core/article_entry.cc @@ -5,6 +5,7 @@ #include "components/dom_distiller/core/article_entry.h" #include "base/logging.h" +#include "sync/api/sync_change.h" using sync_pb::EntitySpecifics; using sync_pb::ArticlePage; @@ -45,4 +46,24 @@ EntitySpecifics SpecificsFromEntry(const ArticleEntry& entry) { return specifics; } +ArticleEntry GetEntryFromChange(const syncer::SyncChange& change) { + DCHECK(change.IsValid()); + DCHECK(change.sync_data().IsValid()); + return EntryFromSpecifics(change.sync_data().GetSpecifics()); +} + +std::string GetEntryIdFromSyncData(const syncer::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(); +} + +syncer::SyncData CreateLocalData(const ArticleEntry& entry) { + EntitySpecifics specifics = SpecificsFromEntry(entry); + const std::string& entry_id = entry.entry_id(); + return syncer::SyncData::CreateLocalData(entry_id, entry_id, specifics); +} + } // namespace dom_distiller diff --git a/components/dom_distiller/core/article_entry.h b/components/dom_distiller/core/article_entry.h index 3811612..388dc51 100644 --- a/components/dom_distiller/core/article_entry.h +++ b/components/dom_distiller/core/article_entry.h @@ -5,9 +5,14 @@ #ifndef COMPONENTS_DOM_DISTILLER_CORE_ARTICLE_ENTRY_H_ #define COMPONENTS_DOM_DISTILLER_CORE_ARTICLE_ENTRY_H_ +#include "sync/api/sync_data.h" #include "sync/protocol/article_specifics.pb.h" #include "sync/protocol/sync.pb.h" +namespace syncer { +class SyncChange; +} + namespace dom_distiller { typedef sync_pb::ArticleSpecifics ArticleEntry; @@ -20,6 +25,11 @@ bool AreEntriesEqual(const ArticleEntry& left, const ArticleEntry& right); sync_pb::EntitySpecifics SpecificsFromEntry(const ArticleEntry& entry); ArticleEntry EntryFromSpecifics(const sync_pb::EntitySpecifics& specifics); +ArticleEntry GetEntryFromChange(const syncer::SyncChange& change); +std::string GetEntryIdFromSyncData(const syncer::SyncData& data); +syncer::SyncData CreateLocalData(const ArticleEntry& entry); + + } // namespace dom_distiller #endif diff --git a/components/dom_distiller/core/dom_distiller_model.cc b/components/dom_distiller/core/dom_distiller_model.cc new file mode 100644 index 0000000..79a95ab --- /dev/null +++ b/components/dom_distiller/core/dom_distiller_model.cc @@ -0,0 +1,208 @@ +// 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_model.h" + +using syncer::SyncChange; +using syncer::SyncChangeList; +using syncer::SyncData; +using syncer::SyncDataList; + +namespace dom_distiller { + +DomDistillerModel::DomDistillerModel() + : next_key_(1) {} + +DomDistillerModel::DomDistillerModel( + const std::vector<ArticleEntry>& initial_data) + : next_key_(1) { + for (size_t i = 0; i < initial_data.size(); ++i) { + AddEntry(initial_data[i]); + } +} + +DomDistillerModel::~DomDistillerModel() {} + +bool DomDistillerModel::GetEntryById(const std::string& entry_id, + ArticleEntry* entry) const { + KeyType key = 0; + if (!GetKeyById(entry_id, &key)) { + return false; + } + GetEntryByKey(key, entry); + return true; +} + +bool DomDistillerModel::GetEntryByUrl(const GURL& url, + ArticleEntry* entry) const { + KeyType key = 0; + if (!GetKeyByUrl(url, &key)) { + return false; + } + GetEntryByKey(key, entry); + return true; +} + +bool DomDistillerModel::GetKeyById(const std::string& entry_id, + KeyType* key) const { + StringToKeyMap::const_iterator it = entry_id_to_key_map_.find(entry_id); + if (it == entry_id_to_key_map_.end()) { + return false; + } + if (key != NULL) { + *key = it->second; + } + return true; +} + +bool DomDistillerModel::GetKeyByUrl(const GURL& url, KeyType* key) const { + StringToKeyMap::const_iterator it = url_to_key_map_.find(url.spec()); + if (it == url_to_key_map_.end()) { + return false; + } + if (key != NULL) { + *key = it->second; + } + return true; +} + +void DomDistillerModel::GetEntryByKey(KeyType key, ArticleEntry* entry) const { + if (entry != NULL) { + EntryMap::const_iterator it = entries_.find(key); + DCHECK(it != entries_.end()); + *entry = it->second; + } +} + +size_t DomDistillerModel::GetNumEntries() const { + return entries_.size(); +} + +std::vector<ArticleEntry> DomDistillerModel::GetEntries() const { + std::vector<ArticleEntry> entries_list; + for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end(); + ++it) { + entries_list.push_back(it->second); + } + return entries_list; +} + +SyncDataList DomDistillerModel::GetAllSyncData() const { + SyncDataList data; + for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end(); + ++it) { + data.push_back(CreateLocalData(it->second)); + } + return data; +} + +void DomDistillerModel::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; + if (GetEntryById(entry_id, NULL)) { + change_type = SyncChange::ACTION_UPDATE; + } + changes_to_apply->push_back(SyncChange(FROM_HERE, change_type, *it)); + } + + for (EntryMap::const_iterator it = entries_.begin(); it != entries_.end(); + ++it) { + if (entries_to_change.find(it->second.entry_id()) == + entries_to_change.end()) { + changes_missing->push_back(SyncChange( + FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(it->second))); + } + } +} + +DomDistillerModel::ChangeResult DomDistillerModel::ApplyChangesToModel( + const SyncChangeList& changes, + SyncChangeList* changes_applied, + SyncChangeList* changes_missing) { + DCHECK(changes_applied); + DCHECK(changes_missing); + + ChangeResult result = SUCCESS; + + for (SyncChangeList::const_iterator it = changes.begin(); it != changes.end(); + ++it) { + result = ApplyChangeToModel(*it, changes_applied, changes_missing); + if (result != SUCCESS) { + break; + } + } + return result; +} + +void DomDistillerModel::AddEntry(const ArticleEntry& entry) { + const std::string& entry_id = entry.entry_id(); + KeyType key = next_key_++; + DCHECK(!GetKeyById(entry_id, NULL)); + entries_.insert(std::make_pair(key, entry)); + entry_id_to_key_map_.insert(std::make_pair(entry_id, key)); + for (int i = 0; i < entry.pages_size(); ++i) { + url_to_key_map_.insert(std::make_pair(entry.pages(i).url(), key)); + } +} + +void DomDistillerModel::RemoveEntry(const ArticleEntry& entry) { + const std::string& entry_id = entry.entry_id(); + KeyType key = 0; + bool success = GetKeyById(entry_id, &key); + DCHECK(success); + + entries_.erase(key); + entry_id_to_key_map_.erase(entry_id); + for (int i = 0; i < entry.pages_size(); ++i) { + url_to_key_map_.erase(entry.pages(i).url()); + } +} + +DomDistillerModel::ChangeResult DomDistillerModel::ApplyChangeToModel( + const SyncChange& change, + SyncChangeList* changes_applied, + SyncChangeList* changes_missing) { + DCHECK(change.IsValid()); + DCHECK(changes_applied); + DCHECK(changes_missing); + + const std::string& entry_id = GetEntryIdFromSyncData(change.sync_data()); + + if (change.change_type() == SyncChange::ACTION_DELETE) { + // TODO(cjhopman): Support delete. + NOTIMPLEMENTED(); + return DELETE_NOT_SUPPORTED; + } + + ArticleEntry entry = GetEntryFromChange(change); + ArticleEntry current_entry; + if (!GetEntryById(entry_id, ¤t_entry)) { + AddEntry(entry); + changes_applied->push_back(SyncChange( + change.location(), SyncChange::ACTION_ADD, change.sync_data())); + } else { + if (!AreEntriesEqual(current_entry, entry)) { + // Currently, conflicts are simply resolved by accepting the last one to + // arrive. + RemoveEntry(current_entry); + AddEntry(entry); + changes_applied->push_back(SyncChange( + change.location(), SyncChange::ACTION_UPDATE, change.sync_data())); + } + } + return SUCCESS; +} + +} // namespace dom_distiller diff --git a/components/dom_distiller/core/dom_distiller_model.h b/components/dom_distiller/core/dom_distiller_model.h new file mode 100644 index 0000000..1bc2ec2 --- /dev/null +++ b/components/dom_distiller/core/dom_distiller_model.h @@ -0,0 +1,94 @@ +// 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. + +#ifndef COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_MODEL_H_ +#define COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_MODEL_H_ + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/id_map.h" +#include "components/dom_distiller/core/article_entry.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_change_processor.h" // syncer::SyncChangeList +#include "sync/api/sync_data.h" +#include "url/gurl.h" + +namespace dom_distiller { + +// This stores the in-memory model of the DOM distiller list. Entries can be +// looked up by URL or by entry_id. +// The model assumes that an URL corresponds to at most a single entry. If this +// assumption is broken, lookup by URL may return unexpected results. +class DomDistillerModel { + public: + enum ChangeResult { + SUCCESS = 0, + DELETE_NOT_SUPPORTED, + }; + + DomDistillerModel(); + explicit DomDistillerModel(const std::vector<ArticleEntry>& initial_data); + + ~DomDistillerModel(); + + // Lookup an ArticleEntry by ID or URL. Returns whether a corresponding entry + // was found. On success, if |entry| is not null, it will contain the entry. + bool GetEntryById(const std::string& entry_id, ArticleEntry* entry) const; + bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) const; + + std::vector<ArticleEntry> GetEntries() const; + size_t GetNumEntries() const; + + syncer::SyncDataList GetAllSyncData() const; + + // Convert a SyncDataList to a SyncChangeList of add or update changes based + // on the state of the model. Also calculate the entries missing from the + // SyncDataList. + void CalculateChangesForMerge(const syncer::SyncDataList& data, + syncer::SyncChangeList* changes_to_apply, + syncer::SyncChangeList* changes_missing); + + // Applies the change list to the model, appending the actual changes made to + // the model to |changes_applied|. If conflict resolution does not apply the + // requested change, then adds the "diff" between what was requested and what + // was actually applied to |changes_missing|. + // Note: Currently conflicts are resolved by just applying the requested + // change. This means nothing will be added to |changes_missing|. + ChangeResult ApplyChangesToModel(const syncer::SyncChangeList& change_list, + syncer::SyncChangeList* changes_applied, + syncer::SyncChangeList* changes_missing); + + private: + typedef int32 KeyType; + typedef base::hash_map<KeyType, ArticleEntry> EntryMap; + typedef base::hash_map<std::string, KeyType> StringToKeyMap; + + void AddEntry(const ArticleEntry& entry); + void RemoveEntry(const ArticleEntry& entry); + + // Lookup an entry's key by ID or URL. Returns whether a corresponding key was + // found. On success, if |key| is not null, it will contain the entry. + bool GetKeyById(const std::string& entry_id, KeyType* key) const; + bool GetKeyByUrl(const GURL& url, KeyType* key) const; + + // If |entry| is not null, assigns the entry for |key| to it. |key| must map + // to an entry in |entries_|. + void GetEntryByKey(KeyType key, ArticleEntry* entry) const; + + ChangeResult ApplyChangeToModel(const syncer::SyncChange& change, + syncer::SyncChangeList* changes_applied, + syncer::SyncChangeList* changes_missing); + + KeyType next_key_; + EntryMap entries_; + StringToKeyMap url_to_key_map_; + StringToKeyMap entry_id_to_key_map_; + + DISALLOW_COPY_AND_ASSIGN(DomDistillerModel); +}; + +} // namespace dom_distiller + +#endif diff --git a/components/dom_distiller/core/dom_distiller_model_unittest.cc b/components/dom_distiller/core/dom_distiller_model_unittest.cc new file mode 100644 index 0000000..0220439 --- /dev/null +++ b/components/dom_distiller/core/dom_distiller_model_unittest.cc @@ -0,0 +1,131 @@ +// 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_model.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace dom_distiller { + +TEST(DomDistillerModelTest, TestGetByEntryId) { + ArticleEntry entry1; + entry1.set_entry_id("id1"); + entry1.set_title("title1"); + ArticleEntry entry2; + entry2.set_entry_id("id2"); + entry2.set_title("title1"); + + std::vector<ArticleEntry> initial_model; + initial_model.push_back(entry1); + initial_model.push_back(entry2); + + DomDistillerModel model(initial_model); + + ArticleEntry found_entry; + EXPECT_TRUE(model.GetEntryById(entry1.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry1, found_entry)); + + EXPECT_TRUE(model.GetEntryById(entry2.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry2, found_entry)); + + EXPECT_FALSE(model.GetEntryById("some_other_id", NULL)); +} + +TEST(DomDistillerModelTest, TestGetByUrl) { + ArticleEntry entry1; + entry1.set_entry_id("id1"); + entry1.set_title("title1"); + ArticleEntryPage* page1 = entry1.add_pages(); + page1->set_url("http://example.com/1"); + ArticleEntryPage* page2 = entry1.add_pages(); + page2->set_url("http://example.com/2"); + + ArticleEntry entry2; + entry2.set_entry_id("id2"); + entry2.set_title("title1"); + ArticleEntryPage* page3 = entry2.add_pages(); + page3->set_url("http://example.com/a1"); + + std::vector<ArticleEntry> initial_model; + initial_model.push_back(entry1); + initial_model.push_back(entry2); + + DomDistillerModel model(initial_model); + + ArticleEntry found_entry; + EXPECT_TRUE(model.GetEntryByUrl(GURL(page1->url()), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry1, found_entry)); + + EXPECT_TRUE(model.GetEntryByUrl(GURL(page2->url()), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry1, found_entry)); + + EXPECT_TRUE(model.GetEntryByUrl(GURL(page3->url()), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry2, found_entry)); + + EXPECT_FALSE(model.GetEntryByUrl(GURL("http://example.com/foo"), NULL)); +} + +// This test ensures that the model handles the case where an URL maps to +// multiple entries. In that case, the model is allowed to have an inconsistent +// url-to-entry mapping, but it should not fail in other ways (i.e. id-to-entry +// should be correct, shouldn't crash). +TEST(DomDistillerModelTest, TestUrlToMultipleEntries) { + ArticleEntry entry1; + entry1.set_entry_id("id1"); + entry1.set_title("title1"); + ArticleEntryPage* page1 = entry1.add_pages(); + page1->set_url("http://example.com/1"); + ArticleEntryPage* page2 = entry1.add_pages(); + page2->set_url("http://example.com/2"); + + ArticleEntry entry2; + entry2.set_entry_id("id2"); + entry2.set_title("title1"); + ArticleEntryPage* page3 = entry2.add_pages(); + page3->set_url("http://example.com/1"); + + std::vector<ArticleEntry> initial_model; + initial_model.push_back(entry1); + initial_model.push_back(entry2); + + DomDistillerModel model(initial_model); + + EXPECT_TRUE(model.GetEntryByUrl(GURL(page1->url()), NULL)); + EXPECT_TRUE(model.GetEntryByUrl(GURL(page2->url()), NULL)); + EXPECT_TRUE(model.GetEntryByUrl(GURL(page3->url()), NULL)); + + ArticleEntry found_entry; + EXPECT_TRUE(model.GetEntryById(entry1.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry1, found_entry)); + + EXPECT_TRUE(model.GetEntryById(entry2.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry2, found_entry)); + + syncer::SyncChangeList changes_to_apply; + syncer::SyncChangeList changes_applied; + syncer::SyncChangeList changes_missing; + + entry2.mutable_pages(0)->set_url("http://example.com/foo1"); + changes_to_apply.push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_UPDATE, CreateLocalData(entry2))); + model.ApplyChangesToModel( + changes_to_apply, &changes_applied, &changes_missing); + + EXPECT_TRUE(model.GetEntryById(entry1.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry1, found_entry)); + + EXPECT_TRUE(model.GetEntryById(entry2.entry_id(), &found_entry)); + ASSERT_TRUE(IsEntryValid(found_entry)); + EXPECT_TRUE(AreEntriesEqual(entry2, found_entry)); +} + +} // namespace dom_distiller diff --git a/components/dom_distiller/core/dom_distiller_store.cc b/components/dom_distiller/core/dom_distiller_store.cc index f5a7a70..8be2cb6 100644 --- a/components/dom_distiller/core/dom_distiller_store.cc +++ b/components/dom_distiller/core/dom_distiller_store.cc @@ -23,24 +23,6 @@ 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) @@ -54,11 +36,11 @@ DomDistillerStore::DomDistillerStore( DomDistillerStore::DomDistillerStore( scoped_ptr<DomDistillerDatabaseInterface> database, - const EntryMap& initial_model, + const std::vector<ArticleEntry>& initial_data, const base::FilePath& database_dir) : database_(database.Pass()), database_loaded_(false), - model_(initial_model), + model_(initial_data), weak_ptr_factory_(this) { database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit, @@ -72,11 +54,23 @@ syncer::SyncableService* DomDistillerStore::GetSyncableService() { return this; } +bool DomDistillerStore::GetEntryById(const std::string& entry_id, + ArticleEntry* entry) { + return model_.GetEntryById(entry_id, entry); +} + +bool DomDistillerStore::GetEntryByUrl(const GURL& url, + ArticleEntry* entry) { + return model_.GetEntryByUrl(url, entry); +} + + bool DomDistillerStore::AddEntry(const ArticleEntry& entry) { if (!database_loaded_) { return false; } - if (model_.find(entry.entry_id()) != model_.end()) { + + if (model_.GetEntryById(entry.entry_id(), NULL)) { return false; } @@ -86,7 +80,11 @@ bool DomDistillerStore::AddEntry(const ArticleEntry& entry) { SyncChangeList changes_applied; SyncChangeList changes_missing; - ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing); + + if (!ApplyChangesToModel( + changes_to_apply, &changes_applied, &changes_missing)) { + return false; + } DCHECK_EQ(size_t(0), changes_missing.size()); DCHECK_EQ(size_t(1), changes_applied.size()); @@ -98,11 +96,7 @@ bool DomDistillerStore::AddEntry(const ArticleEntry& entry) { } 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; + return model_.GetEntries(); } // syncer::SyncableService implementation. @@ -133,11 +127,7 @@ void DomDistillerStore::StopSyncing(ModelType type) { } 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; + return model_.GetAllSyncData(); } SyncError DomDistillerStore::ProcessSyncChanges( @@ -146,17 +136,34 @@ SyncError DomDistillerStore::ProcessSyncChanges( DCHECK(database_loaded_); SyncChangeList database_changes; SyncChangeList sync_changes; - SyncError error = - ApplyChangesToModel(change_list, &database_changes, &sync_changes); + if (!ApplyChangesToModel(change_list, &database_changes, &sync_changes)) { + return SyncError(FROM_HERE, + SyncError::DATATYPE_ERROR, + "Applying changes to the DOM distiller model failed", + syncer::ARTICLES); + } ApplyChangesToDatabase(database_changes); DCHECK_EQ(size_t(0), sync_changes.size()); - return error; + return SyncError(); } -ArticleEntry DomDistillerStore::GetEntryFromChange(const SyncChange& change) { - DCHECK(change.IsValid()); - DCHECK(change.sync_data().IsValid()); - return EntryFromSpecifics(change.sync_data().GetSpecifics()); +bool DomDistillerStore::ApplyChangesToModel( + const SyncChangeList& changes, + SyncChangeList* changes_applied, + SyncChangeList* changes_missing) { + DomDistillerModel::ChangeResult change_result = + model_.ApplyChangesToModel(changes, changes_applied, changes_missing); + if (change_result == DomDistillerModel::SUCCESS) { + return true; + } + + LOG(WARNING) << "Applying changes to DOM distiller model failed with error " + << change_result; + + database_.reset(); + database_loaded_ = false; + StopSyncing(syncer::ARTICLES); + return false; } void DomDistillerStore::OnDatabaseInit(bool success) { @@ -238,35 +245,6 @@ bool DomDistillerStore::ApplyChangesToDatabase( 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, @@ -275,12 +253,18 @@ SyncMergeResult DomDistillerStore::MergeDataWithModel( DCHECK(changes_missing); SyncMergeResult result(syncer::ARTICLES); - result.set_num_items_before_association(model_.size()); + result.set_num_items_before_association(model_.GetNumEntries()); SyncChangeList changes_to_apply; - CalculateChangesForMerge(data, &changes_to_apply, changes_missing); - SyncError error = - ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing); + model_.CalculateChangesForMerge(data, &changes_to_apply, changes_missing); + SyncError error; + if (!ApplyChangesToModel( + changes_to_apply, changes_applied, changes_missing)) { + error = SyncError(FROM_HERE, + SyncError::DATATYPE_ERROR, + "Applying changes to the DOM distiller model failed", + syncer::ARTICLES); + } int num_added = 0; int num_modified = 0; @@ -304,57 +288,10 @@ SyncMergeResult DomDistillerStore::MergeDataWithModel( result.set_num_items_deleted(0); result.set_pre_association_version(0); - result.set_num_items_after_association(model_.size()); + result.set_num_items_after_association(model_.GetNumEntries()); 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 diff --git a/components/dom_distiller/core/dom_distiller_store.h b/components/dom_distiller/core/dom_distiller_store.h index fdc1851..18641b7 100644 --- a/components/dom_distiller/core/dom_distiller_store.h +++ b/components/dom_distiller/core/dom_distiller_store.h @@ -11,12 +11,14 @@ #include "base/memory/weak_ptr.h" #include "components/dom_distiller/core/article_entry.h" #include "components/dom_distiller/core/dom_distiller_database.h" +#include "components/dom_distiller/core/dom_distiller_model.h" #include "sync/api/sync_change.h" #include "sync/api/sync_data.h" #include "sync/api/sync_error.h" #include "sync/api/sync_error_factory.h" #include "sync/api/sync_merge_result.h" #include "sync/api/syncable_service.h" +#include "url/gurl.h" namespace base { class FilePath; @@ -34,6 +36,12 @@ class DomDistillerStoreInterface { virtual bool AddEntry(const ArticleEntry& entry) = 0; + // Lookup an ArticleEntry by ID or URL. Returns whether a corresponding entry + // was found. On success, if |entry| is not null, it will contain the entry. + virtual bool GetEntryById(const std::string& entry_id, + ArticleEntry* entry) = 0; + virtual bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) = 0; + // Gets a copy of all the current entries. virtual std::vector<ArticleEntry> GetEntries() const = 0; @@ -59,8 +67,6 @@ class DomDistillerStoreInterface { class DomDistillerStore : public syncer::SyncableService, DomDistillerStoreInterface { public: - typedef base::hash_map<std::string, ArticleEntry> EntryMap; - // Creates storage using the given database for local storage. Initializes the // database with |database_dir|. DomDistillerStore(scoped_ptr<DomDistillerDatabaseInterface> database, @@ -70,7 +76,7 @@ class DomDistillerStore : public syncer::SyncableService, // database with |database_dir|. Also initializes the internal model to // |initial_model|. DomDistillerStore(scoped_ptr<DomDistillerDatabaseInterface> database, - const EntryMap& initial_model, + const std::vector<ArticleEntry>& initial_data, const base::FilePath& database_dir); virtual ~DomDistillerStore(); @@ -78,6 +84,9 @@ class DomDistillerStore : public syncer::SyncableService, // DomDistillerStoreInterface implementation. virtual syncer::SyncableService* GetSyncableService() OVERRIDE; virtual bool AddEntry(const ArticleEntry& entry) OVERRIDE; + virtual bool GetEntryById(const std::string& entry_id, + ArticleEntry* entry) OVERRIDE; + virtual bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) OVERRIDE; virtual std::vector<ArticleEntry> GetEntries() const OVERRIDE; // syncer::SyncableService implementation. @@ -92,9 +101,6 @@ class DomDistillerStore : public syncer::SyncableService, virtual syncer::SyncError ProcessSyncChanges( const tracked_objects::Location& from_here, const syncer::SyncChangeList& change_list) OVERRIDE; - - static ArticleEntry GetEntryFromChange(const syncer::SyncChange& change); - private: void OnDatabaseInit(bool success); void OnDatabaseLoad(bool success, scoped_ptr<EntryVector> entries); @@ -116,24 +122,18 @@ class DomDistillerStore : public syncer::SyncableService, const syncer::SyncChangeList& change_list); bool ApplyChangesToDatabase(const syncer::SyncChangeList& change_list); - // Applies the change list to the in-memory model, appending the actual - // changes made to the model to changes_applied. If conflict resolution does - // not apply the requested change, then adds the "diff" to changes_missing. - syncer::SyncError ApplyChangesToModel( - const syncer::SyncChangeList& change_list, - syncer::SyncChangeList* changes_applied, - syncer::SyncChangeList* changes_missing); - - void ApplyChangeToModel(const syncer::SyncChange& change, - syncer::SyncChangeList* changes_applied, - syncer::SyncChangeList* changes_missing); + // Applies the changes to |model_|. If the model returns an error, disables + // syncing and database changes and returns false. + bool ApplyChangesToModel(const syncer::SyncChangeList& change_list, + syncer::SyncChangeList* changes_applied, + syncer::SyncChangeList* changes_missing); scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; scoped_ptr<syncer::SyncErrorFactory> error_factory_; scoped_ptr<DomDistillerDatabaseInterface> database_; bool database_loaded_; - EntryMap model_; + DomDistillerModel model_; base::WeakPtrFactory<DomDistillerStore> weak_ptr_factory_; diff --git a/components/dom_distiller/core/dom_distiller_store_unittest.cc b/components/dom_distiller/core/dom_distiller_store_unittest.cc index 333d72d..9e4cf35 100644 --- a/components/dom_distiller/core/dom_distiller_store_unittest.cc +++ b/components/dom_distiller/core/dom_distiller_store_unittest.cc @@ -34,12 +34,21 @@ namespace { const ModelType kDomDistillerModelType = syncer::ARTICLES; -typedef DomDistillerStore::EntryMap EntryMap; +typedef base::hash_map<std::string, ArticleEntry> EntryMap; void AddEntry(const ArticleEntry& e, EntryMap* map) { (*map)[e.entry_id()] = e; } +std::vector<ArticleEntry> EntryMapToList(const EntryMap& entries) { + std::vector<ArticleEntry> entry_list; + for (EntryMap::const_iterator it = entries.begin(); it != entries.end(); + ++it) { + entry_list.push_back(it->second); + } + return entry_list; +} + class FakeDB : public DomDistillerDatabaseInterface { typedef base::Callback<void(bool)> Callback; @@ -134,7 +143,7 @@ class FakeSyncChangeProcessor : public syncer::SyncChangeProcessor { for (SyncChangeList::const_iterator it = changes.begin(); it != changes.end(); ++it) { - AddEntry(DomDistillerStore::GetEntryFromChange(*it), model_); + AddEntry(GetEntryFromChange(*it), model_); } return SyncError(); } @@ -203,7 +212,7 @@ class DomDistillerStoreTest : public testing::Test { fake_db_ = new FakeDB(&db_model_); store_.reset(new DomDistillerStore( scoped_ptr<DomDistillerDatabaseInterface>(fake_db_), - store_model_, + EntryMapToList(store_model_), db_dir_)); } @@ -448,7 +457,7 @@ TEST_F(DomDistillerStoreTest, TestSyncMergeWithSecondDomDistillerStore) { FakeDB* other_fake_db = new FakeDB(&other_db_model); scoped_ptr<DomDistillerStore> owned_other_store(new DomDistillerStore( scoped_ptr<DomDistillerDatabaseInterface>(other_fake_db), - EntryMap(), + std::vector<ArticleEntry>(), base::FilePath(FILE_PATH_LITERAL("/fake/other/path")))); DomDistillerStore* other_store = owned_other_store.get(); other_fake_db->InitCallback(true); |