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. 4 5#include "components/dom_distiller/core/dom_distiller_store.h" 6 7#include "base/bind.h" 8#include "base/logging.h" 9#include "components/dom_distiller/core/article_entry.h" 10#include "sync/api/sync_change.h" 11#include "sync/protocol/article_specifics.pb.h" 12#include "sync/protocol/sync.pb.h" 13 14using leveldb_proto::ProtoDatabase; 15using sync_pb::ArticleSpecifics; 16using sync_pb::EntitySpecifics; 17using syncer::ModelType; 18using syncer::SyncChange; 19using syncer::SyncChangeList; 20using syncer::SyncData; 21using syncer::SyncDataList; 22using syncer::SyncError; 23using syncer::SyncMergeResult; 24 25namespace dom_distiller { 26 27DomDistillerStore::DomDistillerStore( 28 scoped_ptr<ProtoDatabase<ArticleEntry> > database, 29 const base::FilePath& database_dir) 30 : database_(database.Pass()), 31 database_loaded_(false), 32 weak_ptr_factory_(this) { 33 database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit, 34 weak_ptr_factory_.GetWeakPtr())); 35} 36 37DomDistillerStore::DomDistillerStore( 38 scoped_ptr<ProtoDatabase<ArticleEntry> > database, 39 const std::vector<ArticleEntry>& initial_data, 40 const base::FilePath& database_dir) 41 : database_(database.Pass()), 42 database_loaded_(false), 43 model_(initial_data), 44 weak_ptr_factory_(this) { 45 database_->Init(database_dir, base::Bind(&DomDistillerStore::OnDatabaseInit, 46 weak_ptr_factory_.GetWeakPtr())); 47} 48 49DomDistillerStore::~DomDistillerStore() {} 50 51// DomDistillerStoreInterface implementation. 52syncer::SyncableService* DomDistillerStore::GetSyncableService() { 53 return this; 54} 55 56bool DomDistillerStore::GetEntryById(const std::string& entry_id, 57 ArticleEntry* entry) { 58 return model_.GetEntryById(entry_id, entry); 59} 60 61bool DomDistillerStore::GetEntryByUrl(const GURL& url, ArticleEntry* entry) { 62 return model_.GetEntryByUrl(url, entry); 63} 64 65bool DomDistillerStore::AddEntry(const ArticleEntry& entry) { 66 if (!database_loaded_) { 67 return false; 68 } 69 70 if (model_.GetEntryById(entry.entry_id(), NULL)) { 71 DVLOG(1) << "Already have entry with id " << entry.entry_id() << "."; 72 return false; 73 } 74 75 SyncChangeList changes_to_apply; 76 changes_to_apply.push_back( 77 SyncChange(FROM_HERE, SyncChange::ACTION_ADD, CreateLocalData(entry))); 78 79 SyncChangeList changes_applied; 80 SyncChangeList changes_missing; 81 82 ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing); 83 84 DCHECK_EQ(size_t(0), changes_missing.size()); 85 DCHECK_EQ(size_t(1), changes_applied.size()); 86 87 ApplyChangesToSync(FROM_HERE, changes_applied); 88 ApplyChangesToDatabase(changes_applied); 89 90 return true; 91} 92 93bool DomDistillerStore::UpdateEntry(const ArticleEntry& entry) { 94 if (!database_loaded_) { 95 return false; 96 } 97 98 if (!model_.GetEntryById(entry.entry_id(), NULL)) { 99 DVLOG(1) << "No entry with id " << entry.entry_id() << " found."; 100 return false; 101 } 102 103 SyncChangeList changes_to_apply; 104 changes_to_apply.push_back( 105 SyncChange(FROM_HERE, SyncChange::ACTION_UPDATE, CreateLocalData(entry))); 106 107 SyncChangeList changes_applied; 108 SyncChangeList changes_missing; 109 110 ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing); 111 112 if (changes_applied.size() != 1) { 113 DVLOG(1) << "Failed to update entry with id " << entry.entry_id() << "."; 114 return false; 115 } 116 117 ApplyChangesToSync(FROM_HERE, changes_applied); 118 ApplyChangesToDatabase(changes_applied); 119 120 return true; 121} 122 123bool DomDistillerStore::RemoveEntry(const ArticleEntry& entry) { 124 if (!database_loaded_) { 125 return false; 126 } 127 128 if (!model_.GetEntryById(entry.entry_id(), NULL)) { 129 DVLOG(1) << "No entry with id " << entry.entry_id() << " found."; 130 return false; 131 } 132 133 SyncChangeList changes_to_apply; 134 changes_to_apply.push_back( 135 SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, CreateLocalData(entry))); 136 137 SyncChangeList changes_applied; 138 SyncChangeList changes_missing; 139 140 ApplyChangesToModel(changes_to_apply, &changes_applied, &changes_missing); 141 142 DCHECK_EQ(size_t(0), changes_missing.size()); 143 DCHECK_EQ(size_t(1), changes_applied.size()); 144 145 ApplyChangesToSync(FROM_HERE, changes_applied); 146 ApplyChangesToDatabase(changes_applied); 147 148 return true; 149} 150 151void DomDistillerStore::AddObserver(DomDistillerObserver* observer) { 152 observers_.AddObserver(observer); 153} 154 155void DomDistillerStore::RemoveObserver(DomDistillerObserver* observer) { 156 observers_.RemoveObserver(observer); 157} 158 159std::vector<ArticleEntry> DomDistillerStore::GetEntries() const { 160 return model_.GetEntries(); 161} 162 163// syncer::SyncableService implementation. 164SyncMergeResult DomDistillerStore::MergeDataAndStartSyncing( 165 ModelType type, const SyncDataList& initial_sync_data, 166 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 167 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 168 DCHECK_EQ(syncer::ARTICLES, type); 169 DCHECK(!sync_processor_); 170 DCHECK(!error_factory_); 171 sync_processor_.reset(sync_processor.release()); 172 error_factory_.reset(error_handler.release()); 173 174 SyncChangeList database_changes; 175 SyncChangeList sync_changes; 176 SyncMergeResult result = 177 MergeDataWithModel(initial_sync_data, &database_changes, &sync_changes); 178 ApplyChangesToDatabase(database_changes); 179 ApplyChangesToSync(FROM_HERE, sync_changes); 180 181 return result; 182} 183 184void DomDistillerStore::StopSyncing(ModelType type) { 185 sync_processor_.reset(); 186 error_factory_.reset(); 187} 188 189SyncDataList DomDistillerStore::GetAllSyncData(ModelType type) const { 190 return model_.GetAllSyncData(); 191} 192 193SyncError DomDistillerStore::ProcessSyncChanges( 194 const tracked_objects::Location& from_here, 195 const SyncChangeList& change_list) { 196 DCHECK(database_loaded_); 197 SyncChangeList database_changes; 198 SyncChangeList sync_changes; 199 ApplyChangesToModel(change_list, &database_changes, &sync_changes); 200 ApplyChangesToDatabase(database_changes); 201 DCHECK_EQ(size_t(0), sync_changes.size()); 202 return SyncError(); 203} 204 205void DomDistillerStore::NotifyObservers(const syncer::SyncChangeList& changes) { 206 if (observers_.might_have_observers() && changes.size() > 0) { 207 std::vector<DomDistillerObserver::ArticleUpdate> article_changes; 208 for (SyncChangeList::const_iterator it = changes.begin(); 209 it != changes.end(); ++it) { 210 DomDistillerObserver::ArticleUpdate article_update; 211 switch (it->change_type()) { 212 case SyncChange::ACTION_ADD: 213 article_update.update_type = DomDistillerObserver::ArticleUpdate::ADD; 214 break; 215 case SyncChange::ACTION_UPDATE: 216 article_update.update_type = 217 DomDistillerObserver::ArticleUpdate::UPDATE; 218 break; 219 case SyncChange::ACTION_DELETE: 220 article_update.update_type = 221 DomDistillerObserver::ArticleUpdate::REMOVE; 222 break; 223 case SyncChange::ACTION_INVALID: 224 NOTREACHED(); 225 break; 226 } 227 const ArticleEntry& entry = GetEntryFromChange(*it); 228 article_update.entry_id = entry.entry_id(); 229 article_changes.push_back(article_update); 230 } 231 FOR_EACH_OBSERVER(DomDistillerObserver, observers_, 232 ArticleEntriesUpdated(article_changes)); 233 } 234} 235 236void DomDistillerStore::ApplyChangesToModel(const SyncChangeList& changes, 237 SyncChangeList* changes_applied, 238 SyncChangeList* changes_missing) { 239 model_.ApplyChangesToModel(changes, changes_applied, changes_missing); 240 NotifyObservers(*changes_applied); 241} 242 243void DomDistillerStore::OnDatabaseInit(bool success) { 244 if (!success) { 245 DVLOG(1) << "DOM Distiller database init failed."; 246 database_.reset(); 247 return; 248 } 249 database_->LoadEntries(base::Bind(&DomDistillerStore::OnDatabaseLoad, 250 weak_ptr_factory_.GetWeakPtr())); 251} 252 253void DomDistillerStore::OnDatabaseLoad(bool success, 254 scoped_ptr<EntryVector> entries) { 255 if (!success) { 256 DVLOG(1) << "DOM Distiller database load failed."; 257 database_.reset(); 258 return; 259 } 260 database_loaded_ = true; 261 262 SyncDataList data; 263 for (EntryVector::iterator it = entries->begin(); it != entries->end(); 264 ++it) { 265 data.push_back(CreateLocalData(*it)); 266 } 267 SyncChangeList changes_applied; 268 SyncChangeList database_changes_needed; 269 MergeDataWithModel(data, &changes_applied, &database_changes_needed); 270 ApplyChangesToDatabase(database_changes_needed); 271} 272 273void DomDistillerStore::OnDatabaseSave(bool success) { 274 if (!success) { 275 DVLOG(1) << "DOM Distiller database save failed." 276 << " Disabling modifications and sync."; 277 database_.reset(); 278 database_loaded_ = false; 279 StopSyncing(syncer::ARTICLES); 280 } 281} 282 283bool DomDistillerStore::ApplyChangesToSync( 284 const tracked_objects::Location& from_here, 285 const SyncChangeList& change_list) { 286 if (!sync_processor_) { 287 return false; 288 } 289 if (change_list.empty()) { 290 return true; 291 } 292 293 SyncError error = sync_processor_->ProcessSyncChanges(from_here, change_list); 294 if (error.IsSet()) { 295 StopSyncing(syncer::ARTICLES); 296 return false; 297 } 298 return true; 299} 300 301bool DomDistillerStore::ApplyChangesToDatabase( 302 const SyncChangeList& change_list) { 303 if (!database_loaded_) { 304 return false; 305 } 306 if (change_list.empty()) { 307 return true; 308 } 309 scoped_ptr<ProtoDatabase<ArticleEntry>::KeyEntryVector> entries_to_save( 310 new ProtoDatabase<ArticleEntry>::KeyEntryVector()); 311 scoped_ptr<std::vector<std::string> > keys_to_remove( 312 new std::vector<std::string>()); 313 314 for (SyncChangeList::const_iterator it = change_list.begin(); 315 it != change_list.end(); ++it) { 316 if (it->change_type() == SyncChange::ACTION_DELETE) { 317 ArticleEntry entry = GetEntryFromChange(*it); 318 keys_to_remove->push_back(entry.entry_id()); 319 } else { 320 ArticleEntry entry = GetEntryFromChange(*it); 321 entries_to_save->push_back(std::make_pair(entry.entry_id(), entry)); 322 } 323 } 324 database_->UpdateEntries(entries_to_save.Pass(), keys_to_remove.Pass(), 325 base::Bind(&DomDistillerStore::OnDatabaseSave, 326 weak_ptr_factory_.GetWeakPtr())); 327 return true; 328} 329 330SyncMergeResult DomDistillerStore::MergeDataWithModel( 331 const SyncDataList& data, SyncChangeList* changes_applied, 332 SyncChangeList* changes_missing) { 333 DCHECK(changes_applied); 334 DCHECK(changes_missing); 335 336 SyncMergeResult result(syncer::ARTICLES); 337 result.set_num_items_before_association(model_.GetNumEntries()); 338 339 SyncChangeList changes_to_apply; 340 model_.CalculateChangesForMerge(data, &changes_to_apply, changes_missing); 341 SyncError error; 342 ApplyChangesToModel(changes_to_apply, changes_applied, changes_missing); 343 344 int num_added = 0; 345 int num_modified = 0; 346 for (SyncChangeList::const_iterator it = changes_applied->begin(); 347 it != changes_applied->end(); ++it) { 348 DCHECK(it->IsValid()); 349 switch (it->change_type()) { 350 case SyncChange::ACTION_ADD: 351 num_added++; 352 break; 353 case SyncChange::ACTION_UPDATE: 354 num_modified++; 355 break; 356 default: 357 NOTREACHED(); 358 } 359 } 360 result.set_num_items_added(num_added); 361 result.set_num_items_modified(num_modified); 362 result.set_num_items_deleted(0); 363 364 result.set_pre_association_version(0); 365 result.set_num_items_after_association(model_.GetNumEntries()); 366 result.set_error(error); 367 368 return result; 369} 370 371} // namespace dom_distiller 372