#include "sync/engine/get_commit_ids_command.h"
#include <set>
#include <utility>
#include <vector>
#include "sync/engine/syncer_util.h"
#include "sync/engine/throttled_data_type_tracker.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/mutable_entry.h"
#include "sync/syncable/nigori_handler.h"
#include "sync/syncable/nigori_util.h"
#include "sync/syncable/syncable_util.h"
#include "sync/syncable/write_transaction.h"
#include "sync/util/cryptographer.h"
using std::set;
using std::vector;
namespace syncer {
using sessions::OrderedCommitSet;
using sessions::SyncSession;
using sessions::StatusController;
GetCommitIdsCommand::GetCommitIdsCommand(
const size_t commit_batch_size,
sessions::OrderedCommitSet* commit_set)
: requested_commit_batch_size_(commit_batch_size),
commit_set_(commit_set) {
}
GetCommitIdsCommand::~GetCommitIdsCommand() {}
SyncerError GetCommitIdsCommand::ExecuteImpl(SyncSession* session) {
std::set<int64> ready_unsynced_set;
syncable::Directory::UnsyncedMetaHandles all_unsynced_handles;
GetUnsyncedEntries(session->write_transaction(),
&all_unsynced_handles);
ModelTypeSet encrypted_types;
bool passphrase_missing = false;
Cryptographer* cryptographer =
session->context()->
directory()->GetCryptographer(session->write_transaction());
if (cryptographer) {
encrypted_types = session->context()->directory()->GetNigoriHandler()->
GetEncryptedTypes(session->write_transaction());
passphrase_missing = cryptographer->has_pending_keys();
};
const ModelTypeSet throttled_types =
session->context()->throttled_data_type_tracker()->GetThrottledTypes();
FilterUnreadyEntries(session->write_transaction(),
throttled_types,
encrypted_types,
passphrase_missing,
all_unsynced_handles,
&ready_unsynced_set);
BuildCommitIds(session->write_transaction(),
session->routing_info(),
ready_unsynced_set);
const vector<syncable::Id>& verified_commit_ids =
commit_set_->GetAllCommitIds();
for (size_t i = 0; i < verified_commit_ids.size(); i++)
DVLOG(1) << "Debug commit batch result:" << verified_commit_ids[i];
return SYNCER_OK;
}
namespace {
bool IsEntryInConflict(const syncable::Entry& entry) {
if (entry.Get(syncable::IS_UNSYNCED) &&
entry.Get(syncable::SERVER_VERSION) > 0 &&
(entry.Get(syncable::SERVER_VERSION) >
entry.Get(syncable::BASE_VERSION))) {
DCHECK(entry.Get(syncable::IS_UNAPPLIED_UPDATE));
DVLOG(1) << "Excluding entry from commit due to version mismatch "
<< entry;
return true;
}
return false;
}
bool IsEntryReadyForCommit(ModelTypeSet throttled_types,
ModelTypeSet encrypted_types,
bool passphrase_missing,
const syncable::Entry& entry) {
DCHECK(entry.Get(syncable::IS_UNSYNCED));
if (IsEntryInConflict(entry))
return false;
const ModelType type = entry.GetModelType();
if ((type != NIGORI) && encrypted_types.Has(type) &&
(passphrase_missing ||
syncable::EntryNeedsEncryption(encrypted_types, entry))) {
DVLOG(1) << "Excluding entry from commit due to lack of encryption "
<< entry;
return false;
}
if (throttled_types.Has(type))
return false;
if (entry.Get(syncable::IS_DEL) && !entry.Get(syncable::ID).ServerKnows()) {
NOTREACHED() << "Found deleted and unsynced local item: " << entry;
return false;
}
syncable::Id id = entry.Get(syncable::ID);
if (id == entry.Get(syncable::PARENT_ID)) {
CHECK(id.IsRoot()) << "Non-root item is self parenting." << entry;
NOTREACHED() << "Root item became unsynced " << entry;
return false;
}
if (entry.IsRoot()) {
NOTREACHED() << "Permanent item became unsynced " << entry;
return false;
}
DVLOG(2) << "Entry is ready for commit: " << entry;
return true;
}
}
void GetCommitIdsCommand::FilterUnreadyEntries(
syncable::BaseTransaction* trans,
ModelTypeSet throttled_types,
ModelTypeSet encrypted_types,
bool passphrase_missing,
const syncable::Directory::UnsyncedMetaHandles& unsynced_handles,
std::set<int64>* ready_unsynced_set) {
for (syncable::Directory::UnsyncedMetaHandles::const_iterator iter =
unsynced_handles.begin(); iter != unsynced_handles.end(); ++iter) {
syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter);
if (IsEntryReadyForCommit(throttled_types,
encrypted_types,
passphrase_missing,
entry)) {
ready_unsynced_set->insert(*iter);
}
}
}
bool GetCommitIdsCommand::AddUncommittedParentsAndTheirPredecessors(
syncable::BaseTransaction* trans,
const ModelSafeRoutingInfo& routes,
const std::set<int64>& ready_unsynced_set,
const syncable::Entry& item,
sessions::OrderedCommitSet* result) const {
OrderedCommitSet item_dependencies(routes);
syncable::Id parent_id = item.Get(syncable::PARENT_ID);
while (!parent_id.ServerKnows()) {
syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id);
CHECK(parent.good()) << "Bad user-only parent in item path.";
int64 handle = parent.Get(syncable::META_HANDLE);
if (commit_set_->HaveCommitItem(handle)) {
break;
}
if (!AddItemThenPredecessors(trans, ready_unsynced_set, parent,
&item_dependencies)) {
DVLOG(1) << "Parent or parent's predecessor was in conflict, omitting "
<< item;
return false;
}
parent_id = parent.Get(syncable::PARENT_ID);
}
result->AppendReverse(item_dependencies);
return true;
}
bool GetCommitIdsCommand::AddItem(const std::set<int64>& ready_unsynced_set,
const syncable::Entry& item,
OrderedCommitSet* result) const {
DCHECK(item.Get(syncable::IS_UNSYNCED));
if (IsEntryInConflict(item))
return false;
int64 item_handle = item.Get(syncable::META_HANDLE);
if (ready_unsynced_set.count(item_handle) == 0) {
return true;
}
result->AddCommitItem(item_handle, item.Get(syncable::ID),
item.GetModelType());
return true;
}
bool GetCommitIdsCommand::AddItemThenPredecessors(
syncable::BaseTransaction* trans,
const std::set<int64>& ready_unsynced_set,
const syncable::Entry& item,
OrderedCommitSet* result) const {
int64 item_handle = item.Get(syncable::META_HANDLE);
if (commit_set_->HaveCommitItem(item_handle)) {
return true;
}
if (!AddItem(ready_unsynced_set, item, result))
return false;
if (item.Get(syncable::IS_DEL))
return true;
syncable::Id prev_id = item.Get(syncable::PREV_ID);
while (!prev_id.IsRoot()) {
syncable::Entry prev(trans, syncable::GET_BY_ID, prev_id);
CHECK(prev.good()) << "Bad id when walking predecessors.";
if (!prev.Get(syncable::IS_UNSYNCED))
break;
int64 handle = prev.Get(syncable::META_HANDLE);
if (commit_set_->HaveCommitItem(handle)) {
return true;
}
if (!AddItem(ready_unsynced_set, prev, result))
return false;
prev_id = prev.Get(syncable::PREV_ID);
}
return true;
}
bool GetCommitIdsCommand::AddPredecessorsThenItem(
syncable::BaseTransaction* trans,
const ModelSafeRoutingInfo& routes,
const std::set<int64>& ready_unsynced_set,
const syncable::Entry& item,
OrderedCommitSet* result) const {
OrderedCommitSet item_dependencies(routes);
if (!AddItemThenPredecessors(trans, ready_unsynced_set, item,
&item_dependencies)) {
DVLOG(1) << "Predecessor was in conflict, omitting " << item;
return false;
}
result->AppendReverse(item_dependencies);
return true;
}
bool GetCommitIdsCommand::IsCommitBatchFull() const {
return commit_set_->Size() >= requested_commit_batch_size_;
}
void GetCommitIdsCommand::AddCreatesAndMoves(
syncable::WriteTransaction* write_transaction,
const ModelSafeRoutingInfo& routes,
const std::set<int64>& ready_unsynced_set) {
for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
!IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) {
int64 metahandle = *iter;
if (commit_set_->HaveCommitItem(metahandle))
continue;
syncable::Entry entry(write_transaction,
syncable::GET_BY_HANDLE,
metahandle);
if (!entry.Get(syncable::IS_DEL)) {
OrderedCommitSet item_dependencies(routes);
if (AddUncommittedParentsAndTheirPredecessors(
write_transaction,
routes,
ready_unsynced_set,
entry,
&item_dependencies) &&
AddPredecessorsThenItem(write_transaction,
routes,
ready_unsynced_set,
entry,
&item_dependencies)) {
commit_set_->Append(item_dependencies);
}
}
}
commit_set_->Truncate(requested_commit_batch_size_);
}
void GetCommitIdsCommand::AddDeletes(
syncable::WriteTransaction* write_transaction,
const std::set<int64>& ready_unsynced_set) {
set<syncable::Id> legal_delete_parents;
for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
!IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) {
int64 metahandle = *iter;
if (commit_set_->HaveCommitItem(metahandle))
continue;
syncable::Entry entry(write_transaction, syncable::GET_BY_HANDLE,
metahandle);
if (entry.Get(syncable::IS_DEL)) {
syncable::Entry parent(write_transaction, syncable::GET_BY_ID,
entry.Get(syncable::PARENT_ID));
if (parent.good() &&
parent.Get(syncable::IS_DEL) &&
parent.Get(syncable::IS_UNSYNCED)) {
if (entry.Get(syncable::ID).ServerKnows() &&
entry.Get(syncable::PARENT_ID) !=
entry.Get(syncable::SERVER_PARENT_ID)) {
DVLOG(1) << "Inserting moved and deleted entry, will be missed by "
<< "delete roll." << entry.Get(syncable::ID);
commit_set_->AddCommitItem(metahandle,
entry.Get(syncable::ID),
entry.GetModelType());
}
continue;
}
legal_delete_parents.insert(entry.Get(syncable::PARENT_ID));
}
}
for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
!IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) {
int64 metahandle = *iter;
if (commit_set_->HaveCommitItem(metahandle))
continue;
syncable::MutableEntry entry(write_transaction, syncable::GET_BY_HANDLE,
metahandle);
if (entry.Get(syncable::IS_DEL)) {
syncable::Id parent_id = entry.Get(syncable::PARENT_ID);
if (legal_delete_parents.count(parent_id)) {
commit_set_->AddCommitItem(metahandle, entry.Get(syncable::ID),
entry.GetModelType());
}
}
}
}
void GetCommitIdsCommand::BuildCommitIds(
syncable::WriteTransaction* write_transaction,
const ModelSafeRoutingInfo& routes,
const std::set<int64>& ready_unsynced_set) {
AddCreatesAndMoves(write_transaction, routes, ready_unsynced_set);
AddDeletes(write_transaction, ready_unsynced_set);
}
}